cadenza 0.7.0.rc1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|