flavour_saver 4.0.2 → 5.0.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +14 -0
- data/.standard.yml +1 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +48 -5
- data/README.md +153 -271
- data/flavour_saver.gemspec +17 -19
- data/lib/flavour_saver/error.rb +5 -0
- data/lib/flavour_saver/helpers.rb +19 -18
- data/lib/flavour_saver/lexer.rb +123 -50
- data/lib/flavour_saver/nodes.rb +107 -19
- data/lib/flavour_saver/parser.rb +339 -161
- data/lib/flavour_saver/partial.rb +4 -6
- data/lib/flavour_saver/rails_partial.rb +10 -4
- data/lib/flavour_saver/runtime.rb +76 -81
- data/lib/flavour_saver/template.rb +3 -5
- data/lib/flavour_saver/version.rb +1 -1
- data/lib/flavour_saver.rb +17 -17
- data/spec/acceptance/backtrack_spec.rb +9 -9
- data/spec/acceptance/comment_spec.rb +7 -7
- data/spec/acceptance/custom_block_helper_spec.rb +12 -12
- data/spec/acceptance/custom_helper_spec.rb +8 -8
- data/spec/acceptance/ensure_no_rce_spec.rb +64 -53
- data/spec/acceptance/handlebars_qunit_spec.rb +295 -297
- data/spec/acceptance/if_else_spec.rb +6 -6
- data/spec/acceptance/multi_level_with_spec.rb +10 -11
- data/spec/acceptance/nesting_depth_spec.rb +34 -0
- data/spec/acceptance/one_character_identifier_spec.rb +8 -8
- data/spec/acceptance/raw_block_spec.rb +7 -7
- data/spec/acceptance/sections_spec.rb +17 -14
- data/spec/acceptance/segment_literals_spec.rb +5 -5
- data/spec/acceptance/simple_expression_spec.rb +9 -9
- data/spec/acceptance/subexpression_spec.rb +18 -17
- data/spec/acceptance/unless_spec.rb +19 -6
- data/spec/lib/flavour_saver/lexer_spec.rb +103 -89
- data/spec/lib/flavour_saver/nodes_spec.rb +85 -0
- data/spec/lib/flavour_saver/parser_spec.rb +155 -95
- data/spec/lib/flavour_saver/runtime_spec.rb +94 -96
- data/spec/lib/flavour_saver/template_spec.rb +1 -2
- metadata +19 -48
data/lib/flavour_saver/parser.rb
CHANGED
|
@@ -1,202 +1,380 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require 'flavour_saver/nodes'
|
|
1
|
+
require "flavour_saver/error"
|
|
2
|
+
require "flavour_saver/nodes"
|
|
4
3
|
|
|
5
4
|
module FlavourSaver
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
# Recursive descent parser for the token stream produced by Lexer.
|
|
6
|
+
#
|
|
7
|
+
# template := (OUT | raw_block | expression)*
|
|
8
|
+
# raw_block := RAWSTART RAWSTRING RAWEND
|
|
9
|
+
# expression := block | expr | comment | safe_expr | partial
|
|
10
|
+
# block := block_start template [else template] block_end
|
|
11
|
+
# block_start := EXPRST (HASH | HAT) WHITE? IDENT [WHITE arguments] WHITE? EXPRE
|
|
12
|
+
# else := EXPRST WHITE? (ELSE | HAT) WHITE? EXPRE
|
|
13
|
+
# block_end := EXPRST FWSL WHITE? IDENT WHITE? EXPRE
|
|
14
|
+
# expr := EXPRST contents EXPRE
|
|
15
|
+
# safe_expr := TEXPRST contents TEXPRE | EXPRST AMP contents EXPRE
|
|
16
|
+
# comment := EXPRST BANG COMMENT EXPRE
|
|
17
|
+
# partial := EXPRST WHITE? GT WHITE? (STRING | (IDENT | LITERAL) [WHITE? (call | lit)]) WHITE? EXPRE
|
|
18
|
+
# contents := WHITE? call WHITE?
|
|
19
|
+
# call := DOT | object_path [WHITE arguments]
|
|
20
|
+
# arguments := argument (WHITE argument)* [WHITE hash] | hash
|
|
21
|
+
# argument := object_path | lit | OPAR contents CPAR
|
|
22
|
+
# hash := IDENT EQ hash_value (WHITE IDENT EQ hash_value)*
|
|
23
|
+
# hash_value := OPAR contents CPAR | string | NUMBER | object_path
|
|
24
|
+
# object_path := object ((DOT | FWSL) (object | NUMBER))*
|
|
25
|
+
# object := AT IDENT | IDENT | LITERAL | (DOT DOT FWSL)+ (IDENT | LITERAL)
|
|
26
|
+
# lit := STRING | S_STRING | NUMBER | BOOL
|
|
27
|
+
class Parser
|
|
28
|
+
class UnbalancedBlockError < Error; end
|
|
29
|
+
|
|
30
|
+
class NotInLanguage < Error
|
|
31
|
+
def initialize(message = "String not in language.")
|
|
32
|
+
super
|
|
14
33
|
end
|
|
34
|
+
end
|
|
15
35
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
STRINGS = [:STRING, :S_STRING]
|
|
37
|
+
LITERALS = [*STRINGS, :NUMBER, :BOOL]
|
|
38
|
+
OBJECT_STARTS = [:AT, :IDENT, :LITERAL, :DOT]
|
|
39
|
+
ARGUMENT_STARTS = [*OBJECT_STARTS, *LITERALS, :OPAR]
|
|
40
|
+
|
|
41
|
+
# Rendering overflows a Fiber's stack at around 80 levels, and
|
|
42
|
+
# SystemStackError isn't a FlavourSaver::Error.
|
|
43
|
+
MAX_DEPTH = 32
|
|
44
|
+
|
|
45
|
+
def self.parse(tokens)
|
|
46
|
+
new.parse(tokens)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse(tokens)
|
|
50
|
+
@tokens = tokens
|
|
51
|
+
@pos = 0
|
|
52
|
+
@depth = 0
|
|
53
|
+
template = parse_template
|
|
54
|
+
expect(:EOS)
|
|
55
|
+
raise NotInLanguage unless @pos == @tokens.size
|
|
56
|
+
template
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def parse_template
|
|
62
|
+
items = []
|
|
63
|
+
loop do
|
|
64
|
+
case peek
|
|
65
|
+
when :OUT
|
|
66
|
+
items << OutputNode.new(advance.value)
|
|
67
|
+
when :RAWSTART
|
|
68
|
+
items << parse_raw_block
|
|
69
|
+
when :TEXPRST
|
|
70
|
+
items << parse_triple_stash
|
|
71
|
+
when :EXPRST
|
|
72
|
+
break if else? || block_end?
|
|
73
|
+
items << parse_expression
|
|
74
|
+
else
|
|
75
|
+
break
|
|
76
|
+
end
|
|
20
77
|
end
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
78
|
+
TemplateNode.new(items)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def parse_raw_block
|
|
82
|
+
expect(:RAWSTART)
|
|
83
|
+
raw = expect(:RAWSTRING).value
|
|
84
|
+
expect(:RAWEND)
|
|
85
|
+
OutputNode.new(raw)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parse_triple_stash
|
|
89
|
+
expect(:TEXPRST)
|
|
90
|
+
call = parse_contents
|
|
91
|
+
expect(:TEXPRE)
|
|
92
|
+
SafeExpressionNode.new(call)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_expression
|
|
96
|
+
case peek(1)
|
|
97
|
+
when :HASH then parse_block
|
|
98
|
+
when :HAT then parse_block
|
|
99
|
+
when :BANG then parse_comment
|
|
100
|
+
when :AMP
|
|
101
|
+
expect(:EXPRST)
|
|
102
|
+
expect(:AMP)
|
|
103
|
+
call = parse_contents
|
|
104
|
+
expect(:EXPRE)
|
|
105
|
+
SafeExpressionNode.new(call)
|
|
106
|
+
else
|
|
107
|
+
if peek(skip_white(1)) == :GT
|
|
108
|
+
parse_partial
|
|
109
|
+
else
|
|
110
|
+
expect(:EXPRST)
|
|
111
|
+
call = parse_contents
|
|
112
|
+
expect(:EXPRE)
|
|
113
|
+
ExpressionNode.new(call)
|
|
114
|
+
end
|
|
24
115
|
end
|
|
25
116
|
end
|
|
26
117
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
clause('partial') { |e| e }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
production(:partial) do
|
|
64
|
-
clause('EXPRST WHITE? GT WHITE? STRING WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
|
|
65
|
-
clause('EXPRST WHITE? GT WHITE? IDENT WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
|
|
66
|
-
clause('EXPRST WHITE? GT WHITE? IDENT WHITE? call WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,e1,nil) }
|
|
67
|
-
clause('EXPRST WHITE? GT WHITE? IDENT WHITE? lit WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,[],e1) }
|
|
68
|
-
clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
|
|
69
|
-
clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? call WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,e1,nil) }
|
|
70
|
-
clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? lit WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,[],e1) }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
production(:block_expression) do
|
|
74
|
-
clause('expr_bl_start template expr_else template expr_bl_end') { |e0,e1,_,e3,e2| BlockExpressionNodeWithElse.new([e0], e1,e2,e3) }
|
|
75
|
-
clause('expr_bl_start template expr_bl_end') { |e0,e1,e2| BlockExpressionNode.new([e0],e1,e2) }
|
|
76
|
-
clause('expr_bl_inv_start template expr_else template expr_bl_end') { |e0,e1,_,e3,e2| BlockExpressionNodeWithElse.new([e0], e2,e2,e1) }
|
|
77
|
-
clause('expr_bl_inv_start template expr_bl_end') { |e0,e1,e2| BlockExpressionNodeWithElse.new([e0],TemplateNode.new([]),e2,e1) }
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
production(:expr_else) do
|
|
81
|
-
clause('EXPRST WHITE? ELSE WHITE? EXPRE') { |_,_,_,_,_| }
|
|
82
|
-
clause('EXPRST WHITE? HAT WHITE? EXPRE') { |_,_,_,_,_| }
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
production(:expr) do
|
|
86
|
-
clause('EXPRST expression_contents EXPRE') { |_,e,_| e }
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
production(:subexpr) do
|
|
90
|
-
clause('OPAR expression_contents CPAR') { |_,e,_| e }
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
production(:expr_comment) do
|
|
94
|
-
clause('EXPRST BANG COMMENT EXPRE') { |_,_,e,_| e }
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
production(:expr_safe) do
|
|
98
|
-
clause('TEXPRST expression_contents TEXPRE') { |_,e,_| e }
|
|
99
|
-
clause('EXPRST AMP expression_contents EXPRE') { |_,_,e,_| e }
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
production(:expr_bl_start) do
|
|
103
|
-
clause('EXPRST HASH WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| push_block CallNode.new(e,[]) }
|
|
104
|
-
clause('EXPRST HASH WHITE? IDENT WHITE arguments WHITE? EXPRE') { |_,_,_,e,_,a,_,_| push_block CallNode.new(e,a) }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
production(:expr_bl_inv_start) do
|
|
108
|
-
clause('EXPRST HAT WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| push_block CallNode.new(e,[]) }
|
|
109
|
-
clause('EXPRST HAT WHITE? IDENT WHITE arguments WHITE? EXPRE') { |_,_,_,e,_,a,_,_| push_block CallNode.new(e,a) }
|
|
118
|
+
def parse_block
|
|
119
|
+
nested do
|
|
120
|
+
expect(:EXPRST)
|
|
121
|
+
inverted = advance.type == :HAT
|
|
122
|
+
skip(:WHITE)
|
|
123
|
+
name = expect(:IDENT).value
|
|
124
|
+
arguments = []
|
|
125
|
+
if peek == :WHITE && peek(1) != :EXPRE
|
|
126
|
+
advance
|
|
127
|
+
arguments = parse_arguments
|
|
128
|
+
end
|
|
129
|
+
skip(:WHITE)
|
|
130
|
+
expect(:EXPRE)
|
|
131
|
+
opener = CallNode.new(name, arguments)
|
|
132
|
+
|
|
133
|
+
contents = parse_template
|
|
134
|
+
alternate = nil
|
|
135
|
+
if else?
|
|
136
|
+
parse_else
|
|
137
|
+
alternate = parse_template
|
|
138
|
+
end
|
|
139
|
+
closer = parse_block_end(opener)
|
|
140
|
+
|
|
141
|
+
if inverted
|
|
142
|
+
# An inverted section renders its body when the value is falsy, so
|
|
143
|
+
# the body is the alternate and the {{else}} part (if any) the contents.
|
|
144
|
+
BlockExpressionNodeWithElse.new([opener], alternate || TemplateNode.new([]), closer, contents)
|
|
145
|
+
elsif alternate
|
|
146
|
+
BlockExpressionNodeWithElse.new([opener], contents, closer, alternate)
|
|
147
|
+
else
|
|
148
|
+
BlockExpressionNode.new([opener], contents, closer)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
110
151
|
end
|
|
111
152
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
153
|
+
def parse_else
|
|
154
|
+
expect(:EXPRST)
|
|
155
|
+
skip(:WHITE)
|
|
156
|
+
raise NotInLanguage unless [:ELSE, :HAT].include?(peek)
|
|
157
|
+
advance
|
|
158
|
+
skip(:WHITE)
|
|
159
|
+
expect(:EXPRE)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def parse_block_end(opener)
|
|
163
|
+
expect(:EXPRST)
|
|
164
|
+
expect(:FWSL)
|
|
165
|
+
skip(:WHITE)
|
|
166
|
+
name = expect(:IDENT).value
|
|
167
|
+
skip(:WHITE)
|
|
168
|
+
expect(:EXPRE)
|
|
169
|
+
if name != opener.name
|
|
170
|
+
raise UnbalancedBlockError, "Unable to find matching opening for {{/#{name}}}"
|
|
171
|
+
end
|
|
172
|
+
CallNode.new(name, [])
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def parse_comment
|
|
176
|
+
expect(:EXPRST)
|
|
177
|
+
expect(:BANG)
|
|
178
|
+
comment = expect(:COMMENT).value
|
|
179
|
+
expect(:EXPRE)
|
|
180
|
+
CommentNode.new(comment)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def parse_partial
|
|
184
|
+
expect(:EXPRST)
|
|
185
|
+
skip(:WHITE)
|
|
186
|
+
expect(:GT)
|
|
187
|
+
skip(:WHITE)
|
|
188
|
+
if peek == :STRING
|
|
189
|
+
name = advance.value
|
|
190
|
+
skip(:WHITE)
|
|
191
|
+
expect(:EXPRE)
|
|
192
|
+
return PartialNode.new(name, [])
|
|
193
|
+
end
|
|
115
194
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
195
|
+
raise NotInLanguage unless [:IDENT, :LITERAL].include?(peek)
|
|
196
|
+
name = advance.value
|
|
197
|
+
skip(:WHITE)
|
|
198
|
+
partial =
|
|
199
|
+
if peek == :EXPRE
|
|
200
|
+
PartialNode.new(name, [])
|
|
201
|
+
elsif LITERALS.include?(peek)
|
|
202
|
+
PartialNode.new(name, [], parse_literal)
|
|
203
|
+
else
|
|
204
|
+
PartialNode.new(name, parse_call, nil)
|
|
205
|
+
end
|
|
206
|
+
skip(:WHITE)
|
|
207
|
+
expect(:EXPRE)
|
|
208
|
+
partial
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def parse_contents
|
|
212
|
+
skip(:WHITE)
|
|
213
|
+
call = parse_call
|
|
214
|
+
skip(:WHITE)
|
|
215
|
+
call
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def parse_call
|
|
219
|
+
if peek == :DOT && peek(1) != :DOT
|
|
220
|
+
advance
|
|
221
|
+
return [CallNode.new("this", [])]
|
|
222
|
+
end
|
|
119
223
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
224
|
+
path = parse_object_path
|
|
225
|
+
if peek == :WHITE && ARGUMENT_STARTS.include?(peek(1))
|
|
226
|
+
advance
|
|
227
|
+
path.last.arguments = parse_arguments
|
|
228
|
+
end
|
|
229
|
+
path
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def parse_arguments
|
|
233
|
+
arguments = []
|
|
234
|
+
loop do
|
|
235
|
+
if hash_item?
|
|
236
|
+
arguments << parse_hash
|
|
237
|
+
break
|
|
238
|
+
end
|
|
239
|
+
arguments << parse_argument
|
|
240
|
+
break unless peek == :WHITE && ARGUMENT_STARTS.include?(peek(1))
|
|
241
|
+
advance
|
|
242
|
+
end
|
|
243
|
+
arguments
|
|
124
244
|
end
|
|
125
245
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
246
|
+
def parse_argument
|
|
247
|
+
if peek == :OPAR
|
|
248
|
+
parse_subexpression
|
|
249
|
+
elsif LITERALS.include?(peek)
|
|
250
|
+
parse_literal
|
|
251
|
+
else
|
|
252
|
+
parse_object_path
|
|
253
|
+
end
|
|
130
254
|
end
|
|
131
255
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
256
|
+
def parse_subexpression
|
|
257
|
+
nested do
|
|
258
|
+
expect(:OPAR)
|
|
259
|
+
call = parse_contents
|
|
260
|
+
expect(:CPAR)
|
|
261
|
+
call
|
|
262
|
+
end
|
|
138
263
|
end
|
|
139
264
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
265
|
+
def parse_hash
|
|
266
|
+
hash = {}
|
|
267
|
+
loop do
|
|
268
|
+
key = expect(:IDENT).value.to_sym
|
|
269
|
+
expect(:EQ)
|
|
270
|
+
hash[key] =
|
|
271
|
+
case peek
|
|
272
|
+
when :OPAR then parse_subexpression
|
|
273
|
+
when *STRINGS then StringNode.new(advance.value)
|
|
274
|
+
when :NUMBER then NumberNode.new(advance.value)
|
|
275
|
+
else parse_object_path
|
|
276
|
+
end
|
|
277
|
+
break unless peek == :WHITE && hash_item?(1)
|
|
278
|
+
advance
|
|
279
|
+
end
|
|
280
|
+
hash
|
|
143
281
|
end
|
|
144
282
|
|
|
145
|
-
|
|
146
|
-
|
|
283
|
+
def parse_literal
|
|
284
|
+
token = advance
|
|
285
|
+
case token.type
|
|
286
|
+
when *STRINGS then StringNode.new(token.value)
|
|
287
|
+
when :NUMBER then NumberNode.new(token.value)
|
|
288
|
+
when :BOOL then token.value ? TrueNode.new(true) : FalseNode.new(false)
|
|
289
|
+
else raise NotInLanguage
|
|
290
|
+
end
|
|
147
291
|
end
|
|
148
292
|
|
|
149
|
-
|
|
150
|
-
|
|
293
|
+
def parse_object_path
|
|
294
|
+
path = [parse_object]
|
|
295
|
+
while [:DOT, :FWSL].include?(peek)
|
|
296
|
+
advance
|
|
297
|
+
# Accomodates objects dereferenced with a number like foo.0.text
|
|
298
|
+
path << ((peek == :NUMBER) ? LiteralCallNode.new(advance.value, []) : parse_object)
|
|
299
|
+
end
|
|
300
|
+
path
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def parse_object
|
|
304
|
+
case peek
|
|
305
|
+
when :AT
|
|
306
|
+
advance
|
|
307
|
+
LocalVarNode.new(expect(:IDENT).value)
|
|
308
|
+
when :IDENT
|
|
309
|
+
CallNode.new(advance.value, [])
|
|
310
|
+
when :LITERAL
|
|
311
|
+
LiteralCallNode.new(advance.value, [])
|
|
312
|
+
when :DOT
|
|
313
|
+
depth = 0
|
|
314
|
+
while peek == :DOT
|
|
315
|
+
expect(:DOT)
|
|
316
|
+
expect(:DOT)
|
|
317
|
+
expect(:FWSL)
|
|
318
|
+
depth += 1
|
|
319
|
+
end
|
|
320
|
+
raise NotInLanguage unless [:IDENT, :LITERAL].include?(peek)
|
|
321
|
+
ParentCallNode.new(advance.value, [], depth)
|
|
322
|
+
else
|
|
323
|
+
raise NotInLanguage
|
|
324
|
+
end
|
|
151
325
|
end
|
|
152
326
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
327
|
+
def nested
|
|
328
|
+
@depth += 1
|
|
329
|
+
if @depth > MAX_DEPTH
|
|
330
|
+
raise NotInLanguage, "Blocks and subexpressions can't be nested more than #{MAX_DEPTH} levels deep."
|
|
331
|
+
end
|
|
332
|
+
yield
|
|
333
|
+
ensure
|
|
334
|
+
@depth -= 1
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# {{else}}, {{^}} or {{ ^ }}, but not the inverted section {{^foo}}.
|
|
338
|
+
def else?
|
|
339
|
+
return false unless peek == :EXPRST
|
|
340
|
+
i = skip_white(1)
|
|
341
|
+
case peek(i)
|
|
342
|
+
when :ELSE then true
|
|
343
|
+
when :HAT then i > 1 || peek(skip_white(i + 1)) != :IDENT
|
|
344
|
+
else false
|
|
345
|
+
end
|
|
156
346
|
end
|
|
157
347
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
clause('IDENT EQ string') { |e0,_,e1| { e0.to_sym => e1 } }
|
|
161
|
-
clause('IDENT EQ number') { |e0,_,e1| { e0.to_sym => e1 } }
|
|
162
|
-
clause('IDENT EQ object_path') { |e0,_,e1| { e0.to_sym => e1 } }
|
|
348
|
+
def block_end?
|
|
349
|
+
peek == :EXPRST && peek(1) == :FWSL
|
|
163
350
|
end
|
|
164
351
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
clause('FWSL') { |_| }
|
|
352
|
+
def hash_item?(offset = 0)
|
|
353
|
+
peek(offset) == :IDENT && peek(offset + 1) == :EQ
|
|
168
354
|
end
|
|
169
355
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# Accomodates objects dereferenced with a number like foo.0.text
|
|
175
|
-
clause('object_path object_sep number_object') { |e0,_,e1| e0 + [e1] }
|
|
356
|
+
def peek(offset = 0)
|
|
357
|
+
token = @tokens[@pos + offset]
|
|
358
|
+
token&.type
|
|
176
359
|
end
|
|
177
360
|
|
|
178
|
-
|
|
179
|
-
|
|
361
|
+
def skip_white(offset)
|
|
362
|
+
(peek(offset) == :WHITE) ? offset + 1 : offset
|
|
180
363
|
end
|
|
181
364
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
clause('parent_call') { |e| e }
|
|
365
|
+
def advance
|
|
366
|
+
token = @tokens[@pos] or raise NotInLanguage
|
|
367
|
+
@pos += 1
|
|
368
|
+
token
|
|
187
369
|
end
|
|
188
370
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
371
|
+
def expect(type)
|
|
372
|
+
raise NotInLanguage unless peek == type
|
|
373
|
+
advance
|
|
192
374
|
end
|
|
193
375
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
clause('backtrack DOT DOT FWSL') { |i,_,_,_| i += 1 }
|
|
376
|
+
def skip(type)
|
|
377
|
+
advance if peek == type
|
|
197
378
|
end
|
|
198
|
-
|
|
199
|
-
finalize
|
|
200
|
-
|
|
201
379
|
end
|
|
202
380
|
end
|
|
@@ -2,12 +2,11 @@ module FlavourSaver
|
|
|
2
2
|
UnknownPartialException = Class.new(StandardError)
|
|
3
3
|
|
|
4
4
|
class Partial
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
partials[name.to_s] = block
|
|
5
|
+
def self.register_partial(name, content = nil, &block)
|
|
6
|
+
partials[name.to_s] = if block.respond_to? :call
|
|
7
|
+
block
|
|
9
8
|
else
|
|
10
|
-
|
|
9
|
+
Parser.parse(Lexer.lex(content))
|
|
11
10
|
end
|
|
12
11
|
end
|
|
13
12
|
|
|
@@ -24,6 +23,5 @@ module FlavourSaver
|
|
|
24
23
|
raise UnknownPartialException, "I can't find the partial named #{name.inspect}" unless p
|
|
25
24
|
p
|
|
26
25
|
end
|
|
27
|
-
|
|
28
26
|
end
|
|
29
27
|
end
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
module FlavourSaver
|
|
2
2
|
class RailsPartial
|
|
3
3
|
def self.register_partial(*args)
|
|
4
|
-
raise
|
|
4
|
+
raise "No need to register partials inside Rails."
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.reset_partials
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.partials
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.fetch
|
|
5
14
|
end
|
|
6
|
-
def self.reset_partials; end
|
|
7
|
-
def self.partials; end
|
|
8
|
-
def self.fetch; end
|
|
9
15
|
end
|
|
10
16
|
end
|