cadenza 0.7.0.rc1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cadenza.rb +15 -0
- data/lib/cadenza/base_renderer.rb +26 -1
- data/lib/cadenza/context.rb +141 -9
- data/lib/cadenza/error.rb +13 -0
- data/lib/cadenza/filesystem_loader.rb +27 -0
- data/lib/cadenza/lexer.rb +52 -1
- data/lib/cadenza/nodes/block_node.rb +17 -3
- data/lib/cadenza/nodes/boolean_inverse_node.rb +11 -0
- data/lib/cadenza/nodes/constant_node.rb +10 -0
- data/lib/cadenza/nodes/document_node.rb +22 -0
- data/lib/cadenza/nodes/filter_node.rb +20 -1
- data/lib/cadenza/nodes/for_node.rb +42 -26
- data/lib/cadenza/nodes/generic_block_node.rb +22 -1
- data/lib/cadenza/nodes/if_node.rb +55 -29
- data/lib/cadenza/nodes/inject_node.rb +24 -1
- data/lib/cadenza/nodes/operation_node.rb +41 -1
- data/lib/cadenza/nodes/text_node.rb +9 -0
- data/lib/cadenza/nodes/variable_node.rb +13 -0
- data/lib/cadenza/parser.rb +82 -940
- data/lib/cadenza/racc_parser.rb +911 -0
- data/lib/cadenza/text_renderer.rb +12 -2
- data/lib/cadenza/version.rb +1 -1
- metadata +21 -30
data/lib/cadenza.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'cadenza/error'
|
1
2
|
require 'cadenza/token'
|
2
3
|
require 'cadenza/lexer'
|
4
|
+
require 'cadenza/racc_parser'
|
3
5
|
require 'cadenza/parser'
|
4
6
|
require 'cadenza/context'
|
5
7
|
require 'cadenza/base_renderer'
|
@@ -15,6 +17,13 @@ Dir[File.join File.dirname(__FILE__), 'cadenza', 'nodes', '*.rb'].each {|f| requ
|
|
15
17
|
module Cadenza
|
16
18
|
BaseContext = Context.new
|
17
19
|
|
20
|
+
# this utility method sets up the standard Cadenza lexer/parser/renderer
|
21
|
+
# stack and renders the given template text with the given variable scope
|
22
|
+
# using the {BaseContext}. the result of rendering is returned as a string.
|
23
|
+
#
|
24
|
+
# @param [String] template_text the content of the template to parse/render
|
25
|
+
# @param [Hash] scope any variables to define as a new scope for {BaseContext}
|
26
|
+
# in this template.
|
18
27
|
def self.render(template_text, scope=nil)
|
19
28
|
template = Parser.new.parse(template_text)
|
20
29
|
|
@@ -29,6 +38,12 @@ module Cadenza
|
|
29
38
|
output.string
|
30
39
|
end
|
31
40
|
|
41
|
+
# similar to {#render} except the given template name will be loaded using
|
42
|
+
# {BaseContext}s predefined list of loaders.
|
43
|
+
#
|
44
|
+
# @param [String] template_name the name of the template to load then parse and render
|
45
|
+
# @param [Hash] scope any variables to define as a new scope for {BaseContext}
|
46
|
+
# in this template.
|
32
47
|
def self.render_template(template_name, scope=nil)
|
33
48
|
context = BaseContext.clone
|
34
49
|
|
@@ -1,12 +1,35 @@
|
|
1
1
|
module Cadenza
|
2
|
+
# BaseRenderer is a class you can use to more easily and cleanly implement
|
3
|
+
# your own rendering class. To use this then subclass {BaseRenderer} and
|
4
|
+
# implement the appropriate render_xyz methods (see {#render} for details).
|
2
5
|
class BaseRenderer
|
3
|
-
|
6
|
+
# @return [IO] the io object that is being written to
|
7
|
+
attr_reader :output
|
4
8
|
|
9
|
+
# @deprecated temporary hack, will be removed later
|
10
|
+
# @return [DocumentNode] the node which is at the root of the AST
|
11
|
+
attr_reader :document
|
12
|
+
|
13
|
+
# creates a new renderer and assigns the given output io object to it
|
14
|
+
# @param [IO] output_io the IO object which will be written to
|
5
15
|
def initialize(output_io)
|
6
16
|
@output = output_io
|
7
17
|
end
|
8
18
|
|
19
|
+
# renders the given document node to the output stream given in the
|
20
|
+
# constructor. this method will call the render_xyz method for the node
|
21
|
+
# given, where xyz is the demodulized underscored version of the node's
|
22
|
+
# class name. for example: given a Cadenza::DocumentNode this method will
|
23
|
+
# call render_document_node
|
24
|
+
#
|
25
|
+
# @param [Node] node the node to render
|
26
|
+
# @param [Context] context the context to render with
|
27
|
+
# @param [Hash] blocks a mapping of the block names to the matching
|
28
|
+
# {BlockNode}. The blocks given should be rendered instead
|
29
|
+
# of blocks of the same name in the given document.
|
9
30
|
def render(node, context, blocks={})
|
31
|
+
#TODO: memoizing this is a terrible smell, add a "parent" hierarchy so
|
32
|
+
# we can always find the root node from any node in the AST
|
10
33
|
@document ||= node
|
11
34
|
|
12
35
|
node_type = node.class.name.split("::").last
|
@@ -16,6 +39,8 @@ module Cadenza
|
|
16
39
|
send("render_#{node_name}", node, context, blocks)
|
17
40
|
end
|
18
41
|
|
42
|
+
private
|
43
|
+
|
19
44
|
# very stripped down form of ActiveSupport's underscore method
|
20
45
|
def underscore(word)
|
21
46
|
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2').downcase!
|
data/lib/cadenza/context.rb
CHANGED
@@ -1,21 +1,49 @@
|
|
1
1
|
|
2
2
|
module Cadenza
|
3
|
-
class TemplateNotFoundError <
|
3
|
+
class TemplateNotFoundError < Cadenza::Error
|
4
4
|
end
|
5
5
|
|
6
|
-
class FilterNotDefinedError <
|
6
|
+
class FilterNotDefinedError < Cadenza::Error
|
7
7
|
end
|
8
8
|
|
9
|
-
class FunctionalVariableNotDefinedError <
|
9
|
+
class FunctionalVariableNotDefinedError < Cadenza::Error
|
10
10
|
end
|
11
11
|
|
12
|
-
class BlockNotDefinedError <
|
12
|
+
class BlockNotDefinedError < Cadenza::Error
|
13
13
|
end
|
14
14
|
|
15
|
+
# The {Context} class is an essential class in Cadenza that contains all the
|
16
|
+
# data necessary to render a template to it's output. The context holds all
|
17
|
+
# defined variable names (see {#stack}), {#filters}, {#functional_variables},
|
18
|
+
# generic {#blocks}, {#loaders} and configuration data as well as all the
|
19
|
+
# methods you should need to define and evaluate those.
|
15
20
|
class Context
|
16
|
-
|
21
|
+
# @return [Array] the variable stack
|
22
|
+
attr_accessor :stack
|
23
|
+
|
24
|
+
# @return [Hash] the filter names mapped to their implementing procs
|
25
|
+
attr_accessor :filters
|
26
|
+
|
27
|
+
# @return [Hash] the functional variable names mapped to their implementing procs
|
28
|
+
attr_accessor :functional_variables
|
29
|
+
|
30
|
+
# @return [Hash] the block names mapped to their implementing procs
|
31
|
+
attr_accessor :blocks
|
32
|
+
|
33
|
+
# @return [Array] the list of loaders
|
34
|
+
attr_accessor :loaders
|
35
|
+
|
36
|
+
# @return [Boolean] true if a {TemplateNotFoundError} should still be
|
37
|
+
# raised if not calling the bang form of {#load_source}
|
38
|
+
# or {#load_template}
|
17
39
|
attr_accessor :whiny_template_loading
|
18
40
|
|
41
|
+
# creates a new context object with an empty stack, filter list, functional
|
42
|
+
# variable list, block list, loaders list and default configuration options.
|
43
|
+
#
|
44
|
+
# When created you can push an optional scope onto as the initial stack
|
45
|
+
#
|
46
|
+
# @param [Hash] initial_scope the initial scope for the context
|
19
47
|
def initialize(initial_scope={})
|
20
48
|
@stack = []
|
21
49
|
@filters = {}
|
@@ -29,6 +57,8 @@ module Cadenza
|
|
29
57
|
|
30
58
|
# creates a new instance of the context with the stack, loaders, filters,
|
31
59
|
# functional variables and blocks shallow copied.
|
60
|
+
#
|
61
|
+
# @return [Context] the cloned context
|
32
62
|
def clone
|
33
63
|
copy = super
|
34
64
|
copy.stack = stack.dup
|
@@ -40,6 +70,12 @@ module Cadenza
|
|
40
70
|
copy
|
41
71
|
end
|
42
72
|
|
73
|
+
# retrieves the value of the given identifier by inspecting the variable
|
74
|
+
# stack from top to bottom. Identifiers with dots in them are separated
|
75
|
+
# on that dot character and looked up as a path. If no value could be
|
76
|
+
# found then nil is returned.
|
77
|
+
#
|
78
|
+
# @return [Object] the object matching the identifier or nil if not found
|
43
79
|
def lookup(identifier)
|
44
80
|
@stack.reverse_each do |scope|
|
45
81
|
value = lookup_identifier(scope, identifier)
|
@@ -50,64 +86,143 @@ module Cadenza
|
|
50
86
|
nil
|
51
87
|
end
|
52
88
|
|
89
|
+
# assigns the given value to the given identifier at the highest current
|
90
|
+
# scope of the stack.
|
91
|
+
#
|
92
|
+
# @param [String] identifier the name of the variable to store
|
93
|
+
# @param [Object] value the value to assign to the given name
|
53
94
|
def assign(identifier, value)
|
54
95
|
@stack.last[identifier.to_sym] = value
|
55
96
|
end
|
56
97
|
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
98
|
+
# creates a new scope on the variable stack and assigns the given hash
|
99
|
+
# to it.
|
100
|
+
#
|
101
|
+
# @param [Hash] scope the mapping of names to values for the new scope
|
102
|
+
# @return nil
|
61
103
|
def push(scope)
|
104
|
+
# TODO: symbolizing strings is slow so consider symbolizing here to improve
|
105
|
+
# the speed of the lookup method (its more important than push)
|
106
|
+
|
107
|
+
# TODO: since you can assign with the #assign method then make the scope
|
108
|
+
# variable optional (assigns an empty hash)
|
62
109
|
@stack.push(scope)
|
110
|
+
|
111
|
+
nil
|
63
112
|
end
|
64
113
|
|
114
|
+
# removes the highest scope from the variable stack
|
115
|
+
# @return [Hash] the removed scope
|
65
116
|
def pop
|
66
117
|
@stack.pop
|
67
118
|
end
|
68
119
|
|
120
|
+
# defines a filter proc with the given name
|
121
|
+
#
|
122
|
+
# @param [Symbol] name the name for the template to use for this filter
|
123
|
+
# @yield [String, *args] the block will receive the input string and a
|
124
|
+
# variable number of arguments passed to the filter.
|
125
|
+
# @return nil
|
69
126
|
def define_filter(name, &block)
|
70
127
|
@filters[name.to_sym] = block
|
128
|
+
nil
|
71
129
|
end
|
72
130
|
|
131
|
+
# calls the defined filter proc with the given parameters and returns the
|
132
|
+
# result.
|
133
|
+
#
|
134
|
+
# @raise [FilterNotDefinedError] if the named filter doesn't exist
|
135
|
+
# @param [Symbol] name the name of the filter to evaluate
|
136
|
+
# @param [Array] params a list of parameters to pass to the filter
|
137
|
+
# block when calling it.
|
138
|
+
# @return [String] the result of evaluating the filter
|
73
139
|
def evaluate_filter(name, params=[])
|
74
140
|
filter = @filters[name.to_sym]
|
75
141
|
raise FilterNotDefinedError.new("undefined filter '#{name}'") if filter.nil?
|
76
142
|
filter.call(*params)
|
77
143
|
end
|
78
144
|
|
145
|
+
# defines a functional variable proc with the given name
|
146
|
+
#
|
147
|
+
# @param [Symbol] name the name for the template to use for this variable
|
148
|
+
# @yield [Context, *args] the block will receive the context object and a
|
149
|
+
# variable number of arguments passed to the variable.
|
150
|
+
# @return nil
|
79
151
|
def define_functional_variable(name, &block)
|
80
152
|
@functional_variables[name.to_sym] = block
|
153
|
+
nil
|
81
154
|
end
|
82
155
|
|
156
|
+
# calls the defined functional variable proc with the given parameters and
|
157
|
+
# returns the result.
|
158
|
+
#
|
159
|
+
# @raise [FunctionalVariableNotDefinedError] if the named variable doesn't exist
|
160
|
+
# @param [Symbol] name the name of the functional variable to evaluate
|
161
|
+
# @param [Array] params a list of parameters to pass to the variable
|
162
|
+
# block when calling it
|
163
|
+
# @return [Object] the result of evaluating the functional variable
|
83
164
|
def evaluate_functional_variable(name, params=[])
|
84
165
|
var = @functional_variables[name.to_sym]
|
85
166
|
raise FunctionalVariableNotDefinedError.new("undefined functional variable '#{name}'") if var.nil?
|
86
167
|
var.call([self] + params)
|
87
168
|
end
|
88
169
|
|
170
|
+
# defines a generic block proc with the given name
|
171
|
+
#
|
172
|
+
# @param [Symbol] name the name for the template to use for this block
|
173
|
+
# @yield [Context, Array, *args] the block will receive the context object,
|
174
|
+
# a list of Node objects (it's children), and
|
175
|
+
# a variable number of aarguments passed to
|
176
|
+
# the block.
|
177
|
+
# @return nil
|
89
178
|
def define_block(name, &block)
|
90
179
|
@blocks[name.to_sym] = block
|
180
|
+
nil
|
91
181
|
end
|
92
182
|
|
183
|
+
# calls the defined generic block proc with the given name and children
|
184
|
+
# nodes.
|
185
|
+
#
|
186
|
+
# @raise [BlockNotDefinedError] if the named block does not exist
|
187
|
+
# @param [Symbol] name the name of the block to evaluate
|
188
|
+
# @param [Array] nodes the child nodes of the block
|
189
|
+
# @param [Array, []] params a list of parameters to pass to the block
|
190
|
+
# when calling it.
|
191
|
+
# @return [String] the result of evaluating the block
|
93
192
|
def evaluate_block(name, nodes, parameters)
|
94
193
|
block = @blocks[name.to_sym]
|
95
194
|
raise BlockNotDefinedError.new("undefined block '#{name}") if block.nil?
|
96
195
|
block.call(self, nodes, parameters)
|
97
196
|
end
|
98
197
|
|
198
|
+
# adds the given loader to the end of the loader list. If the argument
|
199
|
+
# passed is a string then a {FilesystemLoader} will be constructed with
|
200
|
+
# the string given as a path for it.
|
201
|
+
#
|
202
|
+
# @param [Loader,String] loader the loader to add
|
203
|
+
# @return nil
|
99
204
|
def add_loader(loader)
|
100
205
|
if loader.is_a?(String)
|
101
206
|
@loaders.push FilesystemLoader.new(loader)
|
102
207
|
else
|
103
208
|
@loaders.push loader
|
104
209
|
end
|
210
|
+
nil
|
105
211
|
end
|
106
212
|
|
213
|
+
# removes all loaders from the context
|
214
|
+
# @return nil
|
107
215
|
def clear_loaders
|
108
216
|
@loaders.reject! { true }
|
217
|
+
nil
|
109
218
|
end
|
110
219
|
|
220
|
+
# loads and returns the given template but does not parse it
|
221
|
+
#
|
222
|
+
# @raise [TemplateNotFoundError] if {#whiny_template_loading} is enabled and
|
223
|
+
# the template could not be loaded.
|
224
|
+
# @param [String] template_name the name of the template to load
|
225
|
+
# @return [String] the template text or nil if the template could not be loaded
|
111
226
|
def load_source(template_name)
|
112
227
|
source = nil
|
113
228
|
|
@@ -123,10 +238,22 @@ module Cadenza
|
|
123
238
|
end
|
124
239
|
end
|
125
240
|
|
241
|
+
# loads and returns the given template but does not parse it
|
242
|
+
#
|
243
|
+
# @raise [TemplateNotFoundError] if the template could not be loaded
|
244
|
+
# @param [String] template_name the name of the template to load
|
245
|
+
# @return [String] the template text
|
126
246
|
def load_source!(template_name)
|
127
247
|
load_source(template_name) || raise(TemplateNotFoundError.new(template_name))
|
128
248
|
end
|
129
249
|
|
250
|
+
# loads, parses and returns the given template
|
251
|
+
#
|
252
|
+
# @raise [TemplateNotFoundError] if {#whiny_template_loading} is enabled and
|
253
|
+
# the template could not be loaded.
|
254
|
+
# @param [String] template_name the name of the template to load
|
255
|
+
# @return [DocumentNode] the root of the parsed document or nil if the
|
256
|
+
# template could not be loaded.
|
130
257
|
def load_template(template_name)
|
131
258
|
template = nil
|
132
259
|
|
@@ -142,6 +269,11 @@ module Cadenza
|
|
142
269
|
end
|
143
270
|
end
|
144
271
|
|
272
|
+
# loads, parses and returns the given template
|
273
|
+
#
|
274
|
+
# @raise [TemplateNotFoundError] if the template could not be loaded
|
275
|
+
# @param [String] template_name the name of the template ot load
|
276
|
+
# @return [DocumentNode] the root of the parsed document
|
145
277
|
def load_template!(template_name)
|
146
278
|
load_template(template_name) || raise(TemplateNotFoundError.new(template_name))
|
147
279
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Cadenza
|
2
|
+
# The {Error} class is the base class of all types of errors Cadenza will
|
3
|
+
# raise, this should make exception handling much simpler for you.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# begin
|
7
|
+
# Cadenza.parse("some {{ invalid template")
|
8
|
+
# rescue Cadenza::Error => e
|
9
|
+
# puts "oh noes!"
|
10
|
+
# end
|
11
|
+
class Error < StandardError
|
12
|
+
end
|
13
|
+
end
|
@@ -1,12 +1,35 @@
|
|
1
1
|
|
2
2
|
module Cadenza
|
3
|
+
# The {FilesystemLoader} is a very simple loader object which takes a given
|
4
|
+
# "root" directory and loads templates using the filesystem. Relative file
|
5
|
+
# paths from this directory should be used for template names.
|
6
|
+
#
|
7
|
+
# This implemenation makes no attempt to be secure so upwards relative file
|
8
|
+
# paths could be used to load sensitive files into the output template.
|
9
|
+
#
|
10
|
+
# ```django
|
11
|
+
# {# assuming you add /home/someuser as a loaded path #}
|
12
|
+
# {{ load '../../etc/passwd' }}
|
13
|
+
# ```
|
14
|
+
#
|
15
|
+
# If you allow loading to be used for insecure user content then consider
|
16
|
+
# using a more secure loader class such as {ZipLoader} or writing a simple
|
17
|
+
# loader for your database connection.
|
3
18
|
class FilesystemLoader
|
19
|
+
# @return [String] the path on the filesystem to load relative to
|
4
20
|
attr_accessor :path
|
5
21
|
|
22
|
+
# creates a new {FilesystemLoader} with the given filesystem directory
|
23
|
+
# to load templates relative to.
|
24
|
+
# @param [String] path see {#path}
|
6
25
|
def initialize(path)
|
7
26
|
@path = path
|
8
27
|
end
|
9
28
|
|
29
|
+
# loads and returns the given template's content or nil if the file was
|
30
|
+
# not a file object (such as a directory).
|
31
|
+
# @param [String] template the name of the template to load
|
32
|
+
# @return [String] the content of the template
|
10
33
|
def load_source(template)
|
11
34
|
filename = File.join(path, template)
|
12
35
|
|
@@ -15,6 +38,10 @@ module Cadenza
|
|
15
38
|
File.read filename
|
16
39
|
end
|
17
40
|
|
41
|
+
# loads and parses the given template name using {Parser}. If the template
|
42
|
+
# could not be loaded then nil is returned.
|
43
|
+
# @param [String] template the name of the template to load
|
44
|
+
# @return [DocumentNode] the root node of the parsed AST
|
18
45
|
def load_template(template)
|
19
46
|
source = load_source(template)
|
20
47
|
|
data/lib/cadenza/lexer.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
require 'strscan'
|
2
2
|
|
3
3
|
module Cadenza
|
4
|
-
|
4
|
+
# The {Lexer} class accepts in input {IO} object which it will parse simple
|
5
|
+
# {Token}s from for use in a {Parser} class.
|
5
6
|
class Lexer
|
7
|
+
#TODO: look at using the CodeRay scanner instead, it supports reading from
|
8
|
+
# an IO object (unlike StringScanner which must have a string in memory)
|
9
|
+
# http://coderay.rubychan.de/doc/classes/CodeRay/Scanners/Scanner.html
|
10
|
+
|
11
|
+
# constructs a new parser and sets it to the position (0, 0)
|
6
12
|
def initialize
|
7
13
|
@line = 0
|
8
14
|
@column = 0
|
9
15
|
end
|
10
16
|
|
17
|
+
# assigns a new string to retrieve tokens and resets the line and column
|
18
|
+
# counters to (1, 1)
|
19
|
+
# @param [String] source the string from which to parse tokens
|
11
20
|
def source=(source)
|
12
21
|
@scanner = ::StringScanner.new(source || "")
|
13
22
|
|
@@ -17,10 +26,47 @@ module Cadenza
|
|
17
26
|
@context = :body
|
18
27
|
end
|
19
28
|
|
29
|
+
# gives the current line and column counter as a two element array
|
30
|
+
# @return [Array] the line and column
|
20
31
|
def position
|
21
32
|
[@line, @column]
|
22
33
|
end
|
23
34
|
|
35
|
+
# Gets the next token and returns it. Tokens are two element arrays where
|
36
|
+
# the first element is one of the following symbols and the second is an
|
37
|
+
# instance of {Cadenza::Token} containing the value of the token.
|
38
|
+
#
|
39
|
+
# valid tokens:
|
40
|
+
# - :VAR_OPEN - for opening an inject tag ex. "{{"
|
41
|
+
# - :VAR_CLOSE - for closing an inject tag ex. "}}"
|
42
|
+
# - :STMT_OPEN - for opening a control tag ex. "{%"
|
43
|
+
# - :STMT_CLOSE - for closing a control tag ex. "%}"
|
44
|
+
# - :TEXT_BLOCK - for a block of raw text
|
45
|
+
# - :OP_EQ - for an equivalence symbol ex. "=="
|
46
|
+
# - :OP_NEQ - for a nonequivalence symbol ex. "!="
|
47
|
+
# - :OP_GEQ - for a greater than or equal to symbol ex. ">="
|
48
|
+
# - :OP_LEQ - for a less than or equal to symbol ex. "<="
|
49
|
+
# - :REAL - for a number with a decimal value ex. "123.45"
|
50
|
+
# - :INTEGER - for a number without a decimal value ex. "12345"
|
51
|
+
# - :STRING - for a string literal, either from single quotes or double quotes ex. "'foo'"
|
52
|
+
# - :IDENTIFIER - for a variable name ex. "foo"
|
53
|
+
# - :IF - for the 'if' keyword
|
54
|
+
# - :UNLESS - for the 'unless' keyword
|
55
|
+
# - :ELSE - for the 'else' keyword
|
56
|
+
# - :ENDIF - for the 'endif' keyword
|
57
|
+
# - :ENDUNLESS - for the 'endunless' keyword
|
58
|
+
# - :FOR - for the 'for' keyword
|
59
|
+
# - :IN - for the 'in' keyword
|
60
|
+
# - :ENDFOR - for the 'endfor' keyword
|
61
|
+
# - :BLOCK - for the 'block' keyword
|
62
|
+
# - :ENDBLOCK - for the 'endblock' keyword
|
63
|
+
# - :EXTENDS - for the 'extends' keyword
|
64
|
+
# - :END - for the 'end' keyword
|
65
|
+
# - :AND - for the 'and' keyword
|
66
|
+
# - :OR - for the 'or' keyword
|
67
|
+
# - :NOT - for the 'not' keyword
|
68
|
+
#
|
69
|
+
# if no tokens are left the return value will be [false, false]
|
24
70
|
def next_token
|
25
71
|
if @scanner.eos?
|
26
72
|
[false, false]
|
@@ -29,6 +75,11 @@ module Cadenza
|
|
29
75
|
end
|
30
76
|
end
|
31
77
|
|
78
|
+
# returns an array of all remaining tokens left to be parsed. See {#next_token}
|
79
|
+
# for details regarding the definition of a token. The array will always end in
|
80
|
+
# [false, false].
|
81
|
+
#
|
82
|
+
# @return [Array] a list of all remaining tokens
|
32
83
|
def remaining_tokens
|
33
84
|
result = []
|
34
85
|
|