eggshell 1.0.3 → 1.1.1
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/bin/eggshell +2 -1
- data/lib/eggshell.rb +6 -0
- data/lib/eggshell/block-handler.rb +20 -0
- data/lib/eggshell/bundles/basic-functions.rb +31 -0
- data/lib/eggshell/bundles/basics.rb +192 -39
- data/lib/eggshell/compiler.rb +452 -0
- data/lib/eggshell/expression-evaluator.rb +246 -832
- data/lib/eggshell/expression-evaluator/evaluator.rb +45 -0
- data/lib/eggshell/expression-evaluator/lexer.rb +473 -0
- data/lib/eggshell/expression-evaluator/parser.rb +422 -0
- data/lib/eggshell/macro-handler.rb +13 -1
- data/lib/eggshell/parse-tree.rb +62 -11
- data/lib/eggshell/processor.rb +48 -70
- data/lib/eggshell/stream.rb +43 -0
- metadata +8 -2
@@ -0,0 +1,422 @@
|
|
1
|
+
module Eggshell; class ExpressionEvaluator;
|
2
|
+
module Parser
|
3
|
+
ST_NULL = 0
|
4
|
+
ST_NUM = 1
|
5
|
+
ST_STRING = 2
|
6
|
+
ST_STRING_EMBED = 4
|
7
|
+
ST_STRING_BLOCK = 8
|
8
|
+
ST_LABEL = 16
|
9
|
+
ST_LABEL_CALL = 17
|
10
|
+
ST_LABEL_MEMBER = 19
|
11
|
+
ST_OPERATOR = 32
|
12
|
+
ST_OPERATOR_TERN = 33
|
13
|
+
ST_GROUP = 64
|
14
|
+
ST_HASH = 128
|
15
|
+
ST_ARRAY = 256
|
16
|
+
ST_INDEX_ACCESS = ST_ARRAY|ST_LABEL
|
17
|
+
|
18
|
+
CONSTS = {
|
19
|
+
'true' => true,
|
20
|
+
'false' => false,
|
21
|
+
'nil' => nil,
|
22
|
+
'null' => nil
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
# Indicates only one statement allowed
|
26
|
+
FL_ONE_STATEMENT = 1
|
27
|
+
# Indicates that for an opening brace (e.g. `if (...) {`), collect remaining items after brace.
|
28
|
+
FL_BRACE_OP_COLLECT = 2
|
29
|
+
|
30
|
+
# Builds a parse tree from the lexer.
|
31
|
+
class DefaultParser
|
32
|
+
def initialize()
|
33
|
+
@lexer = Eggshell::ExpressionEvaluator::Lexer.new(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset()
|
37
|
+
@tree = []
|
38
|
+
@ptr = @tree
|
39
|
+
@last_ptr = [@tree]
|
40
|
+
@tokens = []
|
41
|
+
@state = [ST_NULL]
|
42
|
+
@term_last = [nil]
|
43
|
+
@term_str = nil
|
44
|
+
@quote_delim = nil
|
45
|
+
@word_pos = 0
|
46
|
+
@expect_label = false
|
47
|
+
@array_state = [0]
|
48
|
+
@hash_state = [nil]
|
49
|
+
@last_comma = false
|
50
|
+
end
|
51
|
+
|
52
|
+
def emit(type, data = nil, ts = nil, te = nil)
|
53
|
+
word = data[ts...te].pack('c*')
|
54
|
+
@tokens << word # if type != :space
|
55
|
+
lst = @state[-1]
|
56
|
+
|
57
|
+
# before inserting term, need to make sure it follows semantics of function/array
|
58
|
+
# @todo look out for case of '(,' or '[,'
|
59
|
+
arg_check = true
|
60
|
+
if lst == ST_LABEL_CALL
|
61
|
+
arg_check = @ptr.length == 0 || @last_comma
|
62
|
+
elsif lst == ST_ARRAY
|
63
|
+
arg_check = @ptr.length == 1 || @last_comma
|
64
|
+
end
|
65
|
+
#arg_check = arg_check && (@hash_state[-1] == :colon || @hash_state[-1] == :comma)
|
66
|
+
@last_comma = false if type != :space
|
67
|
+
|
68
|
+
expr_frag = ''
|
69
|
+
if @tokens.length > 5
|
70
|
+
expr_frag = @tokens[@tokens.length-5..-2].join('')
|
71
|
+
else
|
72
|
+
expr_frag = @tokens[0..-2].join('')
|
73
|
+
end
|
74
|
+
|
75
|
+
# @todo need to track last literal/var/func for operator syntax check
|
76
|
+
if type == :escape
|
77
|
+
raise Exception.new("expecting identifier after '#{@ptr[-1][1]}'") if @expect_label
|
78
|
+
raise Exception.new("escaping character outside of string") if lst != ST_STRING
|
79
|
+
word_unesc = ESCAPE_MAP[word]
|
80
|
+
raise Exception.new("invalid escape sequence: #{word}") if !word_unesc
|
81
|
+
|
82
|
+
@term_str += word_unesc
|
83
|
+
return
|
84
|
+
elsif type == :str_delim
|
85
|
+
raise Exception.new("expecting identifier after '#{@ptr[-1][1]}', not #{word}") if @expect_label
|
86
|
+
if !@quote_delim
|
87
|
+
@term_last.pop if lst != ST_INDEX_ACCESS
|
88
|
+
raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
|
89
|
+
if @hash_state[-1] == :key
|
90
|
+
@hash_state[-1] = :colon
|
91
|
+
elsif @hash_state[-1] == :value
|
92
|
+
@hash_state[-1] = :comma
|
93
|
+
elsif @hash_state[-1] == :comma
|
94
|
+
raise Exception.new("expecting comma after: #{expr_frag}")
|
95
|
+
end
|
96
|
+
@quote_delim = word
|
97
|
+
@state << ST_STRING
|
98
|
+
@term_str = ''
|
99
|
+
elsif @quote_delim == word
|
100
|
+
@quote_delim = nil
|
101
|
+
@state.pop
|
102
|
+
@ptr << @term_str
|
103
|
+
@term_str = nil
|
104
|
+
end
|
105
|
+
elsif lst == ST_STRING
|
106
|
+
@term_str += word
|
107
|
+
elsif type == :number_literal
|
108
|
+
@term_last.pop if lst != ST_INDEX_ACCESS
|
109
|
+
raise Exception.new("expecting identifier after: '#{@ptr[-1][1]}'") if @expect_label
|
110
|
+
raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
|
111
|
+
@ptr << (word.index('.') ? word.to_f : word.to_i)
|
112
|
+
|
113
|
+
if @hash_state[-1] == :key
|
114
|
+
@hash_state[-1] = :colon
|
115
|
+
elsif @hash_state[-1] == :value
|
116
|
+
@hash_state[-1] = :comma
|
117
|
+
end
|
118
|
+
elsif type == :identifier
|
119
|
+
if lst == ST_LABEL_MEMBER
|
120
|
+
@state.pop
|
121
|
+
@ptr << word
|
122
|
+
@last_ptr[-1][-1] << @ptr
|
123
|
+
@ptr = @last_ptr.pop
|
124
|
+
elsif @expect_label
|
125
|
+
@ptr[-1][1] += word
|
126
|
+
@expect_label = false
|
127
|
+
else
|
128
|
+
raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
|
129
|
+
|
130
|
+
if CONSTS.has_key?(word)
|
131
|
+
@ptr << CONSTS[word]
|
132
|
+
else
|
133
|
+
@ptr << [:var, word]
|
134
|
+
end
|
135
|
+
|
136
|
+
if @hash_state[-1] == :key
|
137
|
+
@hash_state[-1] = :colon
|
138
|
+
elsif @hash_state[-1] == :value
|
139
|
+
@hash_state[-1] = :comma
|
140
|
+
end
|
141
|
+
end
|
142
|
+
@term_last << ST_LABEL
|
143
|
+
elsif type == :logical_op
|
144
|
+
# STORED AS: `[:op, [operand1, operator1, operand2, operator2, ...]]
|
145
|
+
# > @last_ptr holds entire structure, @ptr holds structure[1]
|
146
|
+
# @todo deal with - prefix
|
147
|
+
@term_last.pop
|
148
|
+
raise Exception.new("expecting identifier after '#{@ptr[-1][1]}'") if @expect_label
|
149
|
+
if word == '?'
|
150
|
+
tern = [:op_tern, nil, [], nil]
|
151
|
+
tern[1] = @last_ptr[-1].pop
|
152
|
+
@last_ptr[-1] << tern
|
153
|
+
@ptr = tern[2]
|
154
|
+
@state[-1] = ST_OPERATOR_TERN
|
155
|
+
elsif lst == ST_OPERATOR
|
156
|
+
op = word.to_sym
|
157
|
+
prec_l = OPERATOR_MAP[@ptr[-2]]
|
158
|
+
prec_r = OPERATOR_MAP[op]
|
159
|
+
|
160
|
+
if prec_r > prec_l
|
161
|
+
# need to group next 2 terms since this operator has higher precedence
|
162
|
+
nop = [:op, [@ptr.pop]]
|
163
|
+
@state << ST_OPERATOR
|
164
|
+
@ptr << nop
|
165
|
+
@last_ptr << @ptr
|
166
|
+
@ptr = nop[1]
|
167
|
+
elsif prec_l > prec_r
|
168
|
+
# check if @last_ptr[-1] is an op; pop if so
|
169
|
+
type = @last_ptr[-2].is_a?(Array) ? @last_ptr[-2][0][0] : nil
|
170
|
+
if type == :op
|
171
|
+
@state.pop
|
172
|
+
@last_ptr.pop
|
173
|
+
@ptr = @last_ptr[-1][0][1]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
@ptr << op
|
177
|
+
else
|
178
|
+
lele = @ptr.pop
|
179
|
+
op = [:op, [lele, word.to_sym]]
|
180
|
+
@last_ptr << @ptr
|
181
|
+
@ptr << op
|
182
|
+
@ptr = op[1]
|
183
|
+
@state << ST_OPERATOR
|
184
|
+
end
|
185
|
+
elsif type == :paren_group
|
186
|
+
# STORED AS: [:group, [nested_statement]]
|
187
|
+
# STORED AS: [:func, 'funcname', [args*]]
|
188
|
+
raise Exception.new("expecting identifier after '#{@ptr[-1][1]}'") if @expect_label
|
189
|
+
if word == '('
|
190
|
+
if @term_last[-1] == ST_LABEL
|
191
|
+
@state << ST_LABEL_CALL
|
192
|
+
@ptr[-1][0] = :func
|
193
|
+
@ptr[-1][2] = []
|
194
|
+
|
195
|
+
@last_ptr << @ptr
|
196
|
+
@ptr = @ptr[-1][2]
|
197
|
+
@expect_separator = ','
|
198
|
+
else
|
199
|
+
raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
|
200
|
+
@state << ST_GROUP
|
201
|
+
@last_ptr << @ptr
|
202
|
+
@ptr << [:group, []]
|
203
|
+
@ptr = @ptr[-1][1]
|
204
|
+
end
|
205
|
+
@term_last.pop
|
206
|
+
else
|
207
|
+
do_close = false
|
208
|
+
if lst == ST_OPERATOR
|
209
|
+
do_close = @state[-2] == ST_GROUP
|
210
|
+
if do_close
|
211
|
+
@state.pop
|
212
|
+
@ptr = @last_ptr.pop[1]
|
213
|
+
end
|
214
|
+
else
|
215
|
+
do_close = lst == ST_LABEL_CALL || lst == ST_GROUP
|
216
|
+
end
|
217
|
+
|
218
|
+
if do_close
|
219
|
+
s = @state.pop
|
220
|
+
@ptr = @last_ptr.pop
|
221
|
+
@expect_separator = nil
|
222
|
+
|
223
|
+
if @hash_state[-1] == :key
|
224
|
+
@hash_state[-1] = :colon
|
225
|
+
elsif @hash_state[-1] == :value
|
226
|
+
@hash_state[-1] = :comma
|
227
|
+
end
|
228
|
+
else
|
229
|
+
# @todo exception
|
230
|
+
end
|
231
|
+
end
|
232
|
+
elsif type == :brace_group
|
233
|
+
raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
|
234
|
+
if word == '{'
|
235
|
+
if @hash_state[-1] && @hash_state[-1] != :value
|
236
|
+
raise Exception.new("invalid hash start")
|
237
|
+
end
|
238
|
+
|
239
|
+
@state << ST_HASH
|
240
|
+
@last_ptr << @ptr
|
241
|
+
@ptr = [:hash]
|
242
|
+
@hash_state << :key
|
243
|
+
else
|
244
|
+
if lst == ST_HASH
|
245
|
+
if @hash_state[-1] != :comma
|
246
|
+
msg = "missing value for key #{@ptr[-1]}"
|
247
|
+
msg = "missing ':' and a value for key #{@ptr[-1]}" if @hash_state[-1] == :key
|
248
|
+
msg = "missing a value for key #{@ptr[-1]}" if @hash_state[-1] == :value
|
249
|
+
raise Exception.new(msg)
|
250
|
+
end
|
251
|
+
|
252
|
+
@last_ptr[-1] << @ptr
|
253
|
+
@ptr = @last_ptr.pop
|
254
|
+
@state.pop
|
255
|
+
@hash_state.pop
|
256
|
+
|
257
|
+
if @hash_state[-1] == :key
|
258
|
+
@hash_state[-1] = :colon
|
259
|
+
elsif @hash_state[-1] == :value
|
260
|
+
@hash_state[-1] = :comma
|
261
|
+
end
|
262
|
+
else
|
263
|
+
# @todo throw exception
|
264
|
+
end
|
265
|
+
end
|
266
|
+
elsif type == :index_group
|
267
|
+
if word == '['
|
268
|
+
if @term_last[-1] == ST_LABEL
|
269
|
+
@state << ST_INDEX_ACCESS
|
270
|
+
@last_ptr << @ptr
|
271
|
+
@ptr = [:index_access]
|
272
|
+
else
|
273
|
+
raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
|
274
|
+
# @todo check if array being defined within index_access
|
275
|
+
if @hash_state[-1] && @hash_state[-1] != :value
|
276
|
+
raise Exception.new("invalid array start near: #{expr_frag}")
|
277
|
+
end
|
278
|
+
|
279
|
+
@ptr << [:array]
|
280
|
+
@state << ST_ARRAY
|
281
|
+
@last_ptr << @ptr
|
282
|
+
@ptr = @ptr[-1]
|
283
|
+
end
|
284
|
+
else
|
285
|
+
if lst == ST_INDEX_ACCESS
|
286
|
+
acc = @ptr
|
287
|
+
@ptr = @last_ptr.pop
|
288
|
+
@ptr[-1] << acc
|
289
|
+
@state.pop
|
290
|
+
elsif lst == ST_ARRAY
|
291
|
+
@ptr = @last_ptr.pop
|
292
|
+
@state.pop
|
293
|
+
|
294
|
+
if @hash_state[-1] == :key
|
295
|
+
@hash_state[-1] = :colon
|
296
|
+
elsif @hash_state[-1] == :value
|
297
|
+
@hash_state[-1] = :comma
|
298
|
+
end
|
299
|
+
else
|
300
|
+
# @todo throw exception
|
301
|
+
end
|
302
|
+
end
|
303
|
+
elsif type == :separator
|
304
|
+
# @todo need to ensure proper separation!!!
|
305
|
+
if word == '.'
|
306
|
+
if @term_last[-1] == ST_LABEL
|
307
|
+
@state << ST_LABEL_MEMBER
|
308
|
+
@last_ptr << @ptr
|
309
|
+
@ptr = [:member_access]
|
310
|
+
else
|
311
|
+
# @todo throw exception
|
312
|
+
end
|
313
|
+
elsif word == ','
|
314
|
+
if lst == ST_HASH
|
315
|
+
if @hash_state[-1] == :comma
|
316
|
+
@hash_state[-1] = :key
|
317
|
+
else
|
318
|
+
raise Exception.new("misplaced comma near: #{expr_frag}")
|
319
|
+
end
|
320
|
+
elsif lst == ST_ARRAY
|
321
|
+
@last_comma = true
|
322
|
+
elsif lst == ST_LABEL_CALL
|
323
|
+
@last_comma = true
|
324
|
+
end
|
325
|
+
elsif word == ';'
|
326
|
+
end
|
327
|
+
elsif type == :modifier
|
328
|
+
if word == ':'
|
329
|
+
if lst == ST_HASH
|
330
|
+
if @hash_state[-1] == :colon
|
331
|
+
@hash_state[-1] = :value
|
332
|
+
else
|
333
|
+
raise Exception.new("misplaced colon near: #{expr_frag} -- #{@hash_state[-1]}")
|
334
|
+
end
|
335
|
+
elsif lst == ST_OPERATOR_TERN
|
336
|
+
# assumes [:op_tern, cond, true, false] structure
|
337
|
+
tern = @last_ptr[-1][-1]
|
338
|
+
if tern[3] == nil
|
339
|
+
tern[3] = []
|
340
|
+
@ptr = tern[3]
|
341
|
+
else
|
342
|
+
# @todo throw exception. more than 1 ':' encountered
|
343
|
+
end
|
344
|
+
elsif @term_last[-1] == ST_LABEL
|
345
|
+
@ptr[-1][1] += ':'
|
346
|
+
@expect_label = true
|
347
|
+
@term_last[-1] = nil
|
348
|
+
end
|
349
|
+
end
|
350
|
+
elsif type == :space
|
351
|
+
else
|
352
|
+
puts "woooo"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def parse(src, flags = 0)
|
357
|
+
reset
|
358
|
+
begin
|
359
|
+
@lexer.process(src)
|
360
|
+
@tree
|
361
|
+
rescue => ex
|
362
|
+
$stderr.write("parse exception: #{ex}\n")
|
363
|
+
$stderr.write("\t#{ex.backtrace.join("\n\t")}\n")
|
364
|
+
nil
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def debug
|
369
|
+
puts "STATE : #{@state.inspect}"
|
370
|
+
puts "TREE : #{@tree.inspect}"
|
371
|
+
puts " PTR : #{@ptr.inspect}"
|
372
|
+
puts "TOKENS: #{@tokens.inspect}"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def self.reassemble(struct, sep = '')
|
377
|
+
buff = []
|
378
|
+
s = ''
|
379
|
+
struct.each do |ele|
|
380
|
+
if ele.is_a?(Array)
|
381
|
+
if ele[0] == :op
|
382
|
+
buff << reassemble(ele[1])
|
383
|
+
elsif ele[0] == :op_tern
|
384
|
+
buff << reassemble(ele[1])
|
385
|
+
buff << ' ? '
|
386
|
+
buff << reassemble(ele[2])
|
387
|
+
buff << ' : '
|
388
|
+
buff << reassemble(ele[3])
|
389
|
+
elsif ele[0] == :func
|
390
|
+
buff << ele[1] + '('
|
391
|
+
buff << reassemble(ele[2], ',')
|
392
|
+
buff << ')'
|
393
|
+
elsif ele[0] == :group
|
394
|
+
buff << '('
|
395
|
+
buff << reassemble(ele[1..-1])
|
396
|
+
buff << ')'
|
397
|
+
elsif ele[0] == :var
|
398
|
+
if ele.length > 2
|
399
|
+
buff << reassemble(ele[1..-1])
|
400
|
+
else
|
401
|
+
buff << ele[1]
|
402
|
+
end
|
403
|
+
elsif ele[0] == :index_access
|
404
|
+
buff << '['
|
405
|
+
if ele[1].is_a?(Array)
|
406
|
+
buff << reassemble(ele[1])
|
407
|
+
else
|
408
|
+
buff << ele[1]
|
409
|
+
end
|
410
|
+
buff << ']'
|
411
|
+
end
|
412
|
+
else
|
413
|
+
buff << s
|
414
|
+
buff << (ele.is_a?(String) ? '"' + ele.gsub('"', '\\"') + '"' : ele)
|
415
|
+
s = sep
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
buff.join('')
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end; end;
|
@@ -21,7 +21,6 @@ module Eggshell::MacroHandler
|
|
21
21
|
include Eggshell::BaseHandler
|
22
22
|
include Eggshell::ProcessHandler
|
23
23
|
|
24
|
-
|
25
24
|
COLLECT_NORMAL = :collect_normal
|
26
25
|
COLLECT_RAW_MACRO = :collect_raw_macro
|
27
26
|
COLLECT_RAW = :collect_raw
|
@@ -35,6 +34,19 @@ module Eggshell::MacroHandler
|
|
35
34
|
COLLECT_NORMAL
|
36
35
|
end
|
37
36
|
|
37
|
+
CHAIN_NONE = 0
|
38
|
+
CHAIN_START = 1
|
39
|
+
CHAIN_CONTINUE = 2
|
40
|
+
CHAIN_END = 3
|
41
|
+
|
42
|
+
# If a sequence of macros are related by conditional execution (if/elsif/else, for instance),
|
43
|
+
# this provides a hint to the processor in how to group and evaluate the macros.
|
44
|
+
#
|
45
|
+
# @return Array In the form `[CHAIN_TYPE, MACRO_START]`
|
46
|
+
def chain_type(macro)
|
47
|
+
[CHAIN_NONE, nil]
|
48
|
+
end
|
49
|
+
|
38
50
|
module Defaults
|
39
51
|
class NoOpHandler
|
40
52
|
include Eggshell::MacroHandler
|
data/lib/eggshell/parse-tree.rb
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
# ]
|
12
12
|
class Eggshell::ParseTree
|
13
13
|
BH = Eggshell::BlockHandler
|
14
|
+
MH = Eggshell::MacroHandler
|
14
15
|
|
15
16
|
IDX_TYPE = 0
|
16
17
|
IDX_NAME = 1
|
@@ -30,23 +31,23 @@ class Eggshell::ParseTree
|
|
30
31
|
@ptr = @tree
|
31
32
|
end
|
32
33
|
|
33
|
-
def new_macro(line_obj, line_start)
|
34
|
+
def new_macro(line_obj, line_start, macro, args, delim, mode)
|
34
35
|
line = line_obj.line
|
35
|
-
macro, args, delim = Eggshell::Processor.parse_macro_start(line)
|
36
|
+
#macro, args, delim = Eggshell::Processor.parse_macro_start(line)
|
36
37
|
|
37
38
|
push_block
|
38
39
|
|
39
40
|
if delim
|
40
|
-
@modes << :macro
|
41
|
+
@modes << (mode == MH::COLLECT_RAW_MACRO ? :macro_raw : macro)
|
41
42
|
@macro_delims << delim.reverse.gsub('[', ']').gsub('(', ')').gsub('{', '}')
|
42
43
|
@macro_open << line
|
43
44
|
@macro_ptr << @ptr
|
44
45
|
# set ptr to entry's tree
|
45
|
-
entry = [:macro, macro, args, [], line_start]
|
46
|
+
entry = [:macro, macro, args, [], line_start, line_start]
|
46
47
|
@ptr << entry
|
47
48
|
@ptr = entry[IDX_LINES]
|
48
49
|
else
|
49
|
-
@ptr << [:macro, macro, args, [], line_start]
|
50
|
+
@ptr << [:macro, macro, args, [], line_start, line_start]
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -65,24 +66,26 @@ class Eggshell::ParseTree
|
|
65
66
|
return false
|
66
67
|
end
|
67
68
|
|
68
|
-
def new_block(handler, type, line_obj, consume_mode, line_start)
|
69
|
-
block_type, args, line =
|
69
|
+
def new_block(handler, type, line_obj, consume_mode, line_start, eggshell)
|
70
|
+
block_type, args, line = eggshell.parse_block_start(line_obj.line)
|
70
71
|
nline = Eggshell::Line.new(line, line_obj.tab_str, line_obj.indent_lvl, line_obj.line_num)
|
71
72
|
|
72
73
|
if consume_mode != BH::DONE
|
73
|
-
|
74
|
+
#@modes << :block
|
74
75
|
if line != ''
|
75
76
|
@lines << nline
|
76
77
|
line_start -= 1
|
77
78
|
end
|
78
79
|
@cur_block = [handler, type, args, line_start]
|
79
80
|
if consume_mode == BH::COLLECT_RAW
|
80
|
-
mode = :raw
|
81
|
+
#mode = :raw
|
82
|
+
@modes << :raw
|
81
83
|
else consume_mode == BH::COLLECT
|
82
|
-
mode = :block
|
84
|
+
#mode = :block
|
85
|
+
@modes << :block
|
83
86
|
end
|
84
87
|
else
|
85
|
-
@ptr << [:block, type, args, [nline], line_start]
|
88
|
+
@ptr << [:block, type, args, [nline], line_start, line_start]
|
86
89
|
end
|
87
90
|
end
|
88
91
|
|
@@ -113,6 +116,10 @@ class Eggshell::ParseTree
|
|
113
116
|
more
|
114
117
|
end
|
115
118
|
|
119
|
+
def collect_macro_raw(line_obj)
|
120
|
+
@ptr << line_obj
|
121
|
+
end
|
122
|
+
|
116
123
|
def raw_line(line_obj)
|
117
124
|
@ptr << line_obj
|
118
125
|
end
|
@@ -127,6 +134,10 @@ class Eggshell::ParseTree
|
|
127
134
|
@modes[-1]
|
128
135
|
end
|
129
136
|
|
137
|
+
def collect_mode
|
138
|
+
@collect_modes[-1]
|
139
|
+
end
|
140
|
+
|
130
141
|
# Does basic output of parse tree structure to visually inspect parsed info.
|
131
142
|
def self.walk(struct = nil, indent = 0, out = nil)
|
132
143
|
out = $stdout if !out
|
@@ -146,4 +157,44 @@ class Eggshell::ParseTree
|
|
146
157
|
end
|
147
158
|
end
|
148
159
|
end
|
160
|
+
|
161
|
+
# Groups together chained macros. This ensures proper block-chain flow. Note that
|
162
|
+
# a related sequence is grouped within a {{pipe}} macro.
|
163
|
+
def self.condense(processor, units)
|
164
|
+
condensed = []
|
165
|
+
|
166
|
+
last_macro = nil
|
167
|
+
units.each do |unit|
|
168
|
+
if !unit.is_a?(Array) || unit[0] == :block
|
169
|
+
condensed << unit
|
170
|
+
else
|
171
|
+
mhandler = processor.get_macro_handler(unit[1])
|
172
|
+
chain_type = nil
|
173
|
+
chain_macro = nil
|
174
|
+
chain_type, chain_macro = mhandler.chain_type(unit[1]) if mhandler
|
175
|
+
if chain_type != MH::CHAIN_NONE
|
176
|
+
if chain_type == MH::CHAIN_START
|
177
|
+
unit[3] = condense(processor, unit[3])
|
178
|
+
condensed << [:macro, 'pipe', [{'chained'=>chain_macro}], [unit], unit[4], -1]
|
179
|
+
last_macro = unit[1]
|
180
|
+
elsif chain_type == MH::CHAIN_CONTINUE && chain_macro == last_macro
|
181
|
+
unit[3] = condense(processor, unit[3])
|
182
|
+
condensed[-1][3] << unit
|
183
|
+
elsif chain_type == MH::CHAIN_END && chain_macro == last_macro
|
184
|
+
unit[3] = condense(processor, unit[3])
|
185
|
+
condensed[-1][3] << unit
|
186
|
+
condensed[-1][5] = unit[5]
|
187
|
+
last_macro = nil
|
188
|
+
else
|
189
|
+
condensed << unit
|
190
|
+
last_macro = nil
|
191
|
+
end
|
192
|
+
else
|
193
|
+
condensed << unit
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
condensed
|
199
|
+
end
|
149
200
|
end
|