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,452 @@
|
|
1
|
+
# Interfaces and methods to convert a parsed Eggshell document into a class for reuse
|
2
|
+
# as a template.
|
3
|
+
#
|
4
|
+
# The reference implementation, {@see DefaultAssembler} generates high-level code as follows:
|
5
|
+
#
|
6
|
+
# pre.
|
7
|
+
# main_function(args*)
|
8
|
+
# call_block_handler*
|
9
|
+
# call_macro_method*
|
10
|
+
# macro_method(out, call_depth)
|
11
|
+
# call_macro_handler
|
12
|
+
# macro_method_expanded(out, call_depth)
|
13
|
+
# native_expansion_of_macro*
|
14
|
+
module Eggshell; module Compiler
|
15
|
+
module Assembler
|
16
|
+
# Sets assembler-specific options. This should initialize the main method
|
17
|
+
# via {@see add_func()}.
|
18
|
+
# @param Eggshell::Processor A reference processor instance to validate certain conditions
|
19
|
+
# (e.g. what to do in a block-macro-block chain).
|
20
|
+
def init(processor, opts = {})
|
21
|
+
end
|
22
|
+
|
23
|
+
# Iterates over parse tree to generate compiler events.
|
24
|
+
def assemble(parse_tree)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generates a new function that statements will be inserted into.
|
28
|
+
def add_func(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Pops the current function off stack, sending statements to the main function.
|
32
|
+
def pop_func
|
33
|
+
end
|
34
|
+
|
35
|
+
# Inserts a raw line into output.
|
36
|
+
def do_line(line)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initializes handler and lines for a block.
|
40
|
+
def start_block(name, args, lines)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Initializes handler and lines for a macro.
|
44
|
+
def start_macro(name, args, lines)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Inject lines into current handler. If a line is either a block or macro, call
|
48
|
+
# {@see assemble()} on it.
|
49
|
+
def add_lines(lines)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Inserts lines from an equivalent
|
53
|
+
def chain_append(lines)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Inserts statements to prepare for inlined macro output.
|
57
|
+
def pipe_inline_start
|
58
|
+
end
|
59
|
+
|
60
|
+
# Inserts statements to inject inlined macro output into previous block.
|
61
|
+
def pipe_inline_end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Inserts statements to append macro output into previous block.
|
65
|
+
def pipe_append_start
|
66
|
+
end
|
67
|
+
|
68
|
+
def pipe_append_end
|
69
|
+
end
|
70
|
+
|
71
|
+
def commit_handler(name, args)
|
72
|
+
@pending_funcs[-1][1] << insert_statement(HANDLER_COMMIT, 'HANDLER_NAME' => name, 'ARGS' => args.inspect)
|
73
|
+
end
|
74
|
+
|
75
|
+
def write(stream)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Iterates over each line/block/macro in the parsed document and generates events. This should
|
79
|
+
# also take care of detecting block-macro chains (appending and inlining macros).
|
80
|
+
def assemble(parse_tree)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class DefaultAssembler
|
85
|
+
include Assembler
|
86
|
+
|
87
|
+
# :func_main: entry point to processing data
|
88
|
+
# :func_main_body: starting body initialization
|
89
|
+
# @todo :exception_handler: defaults to 'raise Exception.new'
|
90
|
+
# @todo :
|
91
|
+
def init(processor, opts = {})
|
92
|
+
@processor = processor
|
93
|
+
@opts = opts
|
94
|
+
@pending_funcs = []
|
95
|
+
@funcs = []
|
96
|
+
@macro_counter = 1
|
97
|
+
@handler_stack = []
|
98
|
+
@call_depth = 0
|
99
|
+
@chained_macros = []
|
100
|
+
add_func(opts[:func_main] ? opts[:func_main] : 'process()', opts[:func_main_body] ? opts[:func_main_body] : BODY)
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_func(name, func_body)
|
104
|
+
@pending_funcs << [name, [func_body]]
|
105
|
+
end
|
106
|
+
|
107
|
+
def pop_func
|
108
|
+
@funcs << @pending_funcs.pop
|
109
|
+
end
|
110
|
+
|
111
|
+
def do_line(line)
|
112
|
+
@pending_funcs[-1][1] << insert_statement(LINE_OUT, 'LINE' => line.inspect)
|
113
|
+
end
|
114
|
+
|
115
|
+
def start_block(name, args, lines)
|
116
|
+
#puts "> b: #{name}"
|
117
|
+
@handler_stack << :block
|
118
|
+
@pending_funcs[-1][1] << insert_statement(BLOCK_HANDLER, 'HANDLER_NAME' => name, 'ARGS' => args.inspect, 'RAW_LINES' => lines.inspect)
|
119
|
+
end
|
120
|
+
|
121
|
+
def start_macro(name, args, lines)
|
122
|
+
#puts "> m: #{name}"
|
123
|
+
name_esc = name.gsub(/[^\w]+/, '_')
|
124
|
+
func_name = "__macro_#{name_esc}_#{@macro_counter}"
|
125
|
+
@pending_funcs[-1][1] << "\t#{func_name}(out, call_depth + 1)"
|
126
|
+
|
127
|
+
add_func("#{func_name}(out, call_depth)", BODY_MACRO)
|
128
|
+
@macro_counter += 1
|
129
|
+
@handler_stack << :macro
|
130
|
+
@call_depth += 1
|
131
|
+
|
132
|
+
# @todo handle for/while/loop as well
|
133
|
+
if name == 'pipe' && args && args[0].is_a?(Hash) && args[0]['chained'] == 'if'
|
134
|
+
@pending_funcs[-1][1] << "\t# chained macros: #{args[0]['chained']}"
|
135
|
+
@chained_macros << {:type=>args[0]['chained'], :args=>args}
|
136
|
+
end
|
137
|
+
|
138
|
+
@pending_funcs[-1][1] << insert_statement(MACRO_HANDLER, 'HANDLER_NAME' => name, 'ARGS' => args.inspect, 'RAW_LINES' => lines.inspect)
|
139
|
+
end
|
140
|
+
|
141
|
+
# takes chained if-elsif-else macros and creates actual if/elsif/else code
|
142
|
+
# @param Array args Arguments given to @pipe chain
|
143
|
+
def _expand_if(args, lines)
|
144
|
+
id = Time.new.to_i.to_s
|
145
|
+
@pending_funcs[-1][1] << insert_statement(HANDLER_SAVE_REF, 'ID' => id)
|
146
|
+
lines.each do |line|
|
147
|
+
type = line[1]
|
148
|
+
cond_args = line[2]
|
149
|
+
cond = cond_args.is_a?(Array) ? cond_args[0] : nil
|
150
|
+
# @todo expand condition check as natively as possible
|
151
|
+
if type != 'else'
|
152
|
+
@pending_funcs[-1][1] << "\t#{type} (processor.expr_eval(#{cond.inspect}))"
|
153
|
+
else
|
154
|
+
@pending_funcs[-1][1] << "\telse"
|
155
|
+
end
|
156
|
+
|
157
|
+
#puts ">> #{line[3][0..2].inspect}"
|
158
|
+
assemble(line[3])
|
159
|
+
|
160
|
+
if type == 'else'
|
161
|
+
@pending_funcs[-1][1] << "\tend"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
@pending_funcs[-1][1] << insert_statement(HANDLER_RESTORE_REF, 'ID' => id)
|
165
|
+
end
|
166
|
+
|
167
|
+
def _expand_for(args, lines)
|
168
|
+
end
|
169
|
+
|
170
|
+
def _expand_while(args, lines)
|
171
|
+
end
|
172
|
+
|
173
|
+
def add_lines(lines)
|
174
|
+
if @chained_macros.length > 0
|
175
|
+
info = @chained_macros.pop
|
176
|
+
_expand_if(info[:args], lines)
|
177
|
+
else
|
178
|
+
lines.each do |line|
|
179
|
+
if line.is_a?(String)
|
180
|
+
@pending_funcs[-1][1] << insert_statement(LINE, 'LINE' => line.inspect)
|
181
|
+
elsif line.is_a?(Eggshell::Line)
|
182
|
+
@pending_funcs[-1][1] << insert_statement(LINE_EGG, 'LINE' => line.line.inspect, 'TAB' => line.tab_str.inspect, 'INDENT' => line.indent_lvl, 'LINE_NUM' => line.line_num)
|
183
|
+
elsif line.is_a?(Array)
|
184
|
+
id = Time.new.to_i.to_s
|
185
|
+
@pending_funcs[-1][1] << insert_statement(HANDLER_SAVE_REF, 'ID' => id)
|
186
|
+
assemble([line])
|
187
|
+
@pending_funcs[-1][1] << insert_statement(HANDLER_RESTORE_REF, 'ID' => id)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def chain_append(lines)
|
194
|
+
add_lines(lines)
|
195
|
+
end
|
196
|
+
|
197
|
+
def pipe_inline_start
|
198
|
+
@pending_funcs[-1][1] << PIPE_INLINE_START
|
199
|
+
end
|
200
|
+
|
201
|
+
def pipe_inline_end
|
202
|
+
@pending_funcs[-1][1] << PIPE_INLINE_END
|
203
|
+
end
|
204
|
+
|
205
|
+
def pipe_append_start
|
206
|
+
@pending_funcs[-1][1] << PIPE_APPEND_START
|
207
|
+
end
|
208
|
+
|
209
|
+
def pipe_append_end
|
210
|
+
@pending_funcs[-1][1] << PIPE_APPEND_END
|
211
|
+
end
|
212
|
+
|
213
|
+
def commit_handler(name, args)
|
214
|
+
type = @handler_stack.pop
|
215
|
+
@pending_funcs[-1][1] << insert_statement(HANDLER_COMMIT, 'HANDLER_NAME' => name, 'ARGS' => args.inspect)
|
216
|
+
@pending_funcs[-1][1] << "# COMMIT #{name} (#{type})\n"
|
217
|
+
if type == :macro
|
218
|
+
pop_func()
|
219
|
+
@call_depth -= 1
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def insert_statement(str, kv)
|
224
|
+
kv.each do |key, val|
|
225
|
+
str = str.gsub("@@#{key}@@", val.to_s)
|
226
|
+
end
|
227
|
+
|
228
|
+
str
|
229
|
+
end
|
230
|
+
|
231
|
+
def assemble(parse_tree)
|
232
|
+
joiner = @opts[:join] || "\n"
|
233
|
+
|
234
|
+
parse_tree = parse_tree.tree if parse_tree.is_a?(Eggshell::ParseTree)
|
235
|
+
raise Exception.new("input not an array or ParseTree") if !parse_tree.is_a?(Array)
|
236
|
+
|
237
|
+
last_type = nil
|
238
|
+
last_line = 0
|
239
|
+
deferred = nil
|
240
|
+
inline_count = -1
|
241
|
+
|
242
|
+
parse_tree.each do |unit|
|
243
|
+
if unit.is_a?(String)
|
244
|
+
do_line(unit)
|
245
|
+
last_line += 1
|
246
|
+
last_type = nil
|
247
|
+
elsif unit.is_a?(Eggshell::Line)
|
248
|
+
do_line(unit.to_s)
|
249
|
+
last_line = unit.line_nameum
|
250
|
+
last_type = nil
|
251
|
+
elsif unit.is_a?(Array)
|
252
|
+
name = unit[1]
|
253
|
+
|
254
|
+
args_o = unit[2] || []
|
255
|
+
args = args_o
|
256
|
+
|
257
|
+
lines = unit[ParseTree::IDX_LINES]
|
258
|
+
lines_start = unit[ParseTree::IDX_LINES_START]
|
259
|
+
lines_end = unit[ParseTree::IDX_LINES_END]
|
260
|
+
|
261
|
+
_handler, _name, _args, _lines = deferred
|
262
|
+
|
263
|
+
if unit[0] == :block
|
264
|
+
handler = @processor.get_block_handler(name)
|
265
|
+
if deferred
|
266
|
+
if last_type == :macro && (lines_start - last_line <= 1) && _handler.equal?(handler, name)
|
267
|
+
chain_append([])
|
268
|
+
add_lines(lines)
|
269
|
+
else
|
270
|
+
inline_count = -1
|
271
|
+
commit_handler(_name, args_o)
|
272
|
+
#@pending_funcs[-1][1] << "# block: #{name} (LINE: #{lines_start})"
|
273
|
+
start_block(name, args_o, [])
|
274
|
+
add_lines(lines)
|
275
|
+
deferred = [handler, name, args, lines]
|
276
|
+
end
|
277
|
+
else
|
278
|
+
#@pending_funcs[-1][1] << "# block: #{name} (LINE: #{lines_start})"
|
279
|
+
inline_count = -1
|
280
|
+
start_block(name, args_o, [])
|
281
|
+
add_lines(lines)
|
282
|
+
deferred = [handler, name, args, lines]
|
283
|
+
end
|
284
|
+
|
285
|
+
last_line = lines_end
|
286
|
+
else
|
287
|
+
handler = @processor.get_macro_handler(name)
|
288
|
+
@pending_funcs[-1][1] << "\t# macro: #{name} (LINE: #{lines_start})"
|
289
|
+
if deferred && lines_start - last_line == 1
|
290
|
+
_last = _lines[-1]
|
291
|
+
pinline = false
|
292
|
+
|
293
|
+
# check last line of last block for number of inlines
|
294
|
+
if inline_count == -1
|
295
|
+
inline_count = 0
|
296
|
+
_last.to_s.gsub(Eggshell::Processor::PIPE_INLINE) do |m|
|
297
|
+
inline_count += 1
|
298
|
+
end
|
299
|
+
inline_count = -1 if inline_count == 0
|
300
|
+
end
|
301
|
+
|
302
|
+
if inline_count > 0
|
303
|
+
pinline = true
|
304
|
+
inline_count -= 1
|
305
|
+
pipe_inline_start()
|
306
|
+
else
|
307
|
+
pipe_append_start()
|
308
|
+
end
|
309
|
+
|
310
|
+
start_macro(name, args_o, [])
|
311
|
+
add_lines(lines)
|
312
|
+
commit_handler(name, args_o)
|
313
|
+
|
314
|
+
# inline pipe; join output with literal \n to avoid processing lines in block process
|
315
|
+
if pinline
|
316
|
+
pipe_inline_end()
|
317
|
+
else
|
318
|
+
pipe_append_end()
|
319
|
+
end
|
320
|
+
else
|
321
|
+
if deferred
|
322
|
+
#start_block(_name, _args, [])
|
323
|
+
#add_lines(_lines)
|
324
|
+
commit_handler(_name, _args)
|
325
|
+
deferred = nil
|
326
|
+
end
|
327
|
+
|
328
|
+
start_macro(name, args_o, [])
|
329
|
+
add_lines(lines)
|
330
|
+
commit_handler(name, args_o)
|
331
|
+
end
|
332
|
+
last_line = lines_end
|
333
|
+
end
|
334
|
+
|
335
|
+
last_type = unit[0]
|
336
|
+
elsif unit
|
337
|
+
$stderr.write "not sure how to handle #{unit.class}\n"
|
338
|
+
$stderr.write unit.inspect
|
339
|
+
$stderr.write "\n"
|
340
|
+
last_type = nil
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
if deferred
|
345
|
+
_handler, _name, _args, _lines = deferred
|
346
|
+
commit_handler(_name, _args)
|
347
|
+
deferred = nil
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def write(stream)
|
352
|
+
while @pending_funcs.length > 0
|
353
|
+
pop_func()
|
354
|
+
end
|
355
|
+
|
356
|
+
@funcs.reverse.each do |func_entry|
|
357
|
+
stream.write("def #{func_entry[0]}\n")
|
358
|
+
stream.write(func_entry[1].join("\n"))
|
359
|
+
stream.write("end\n\n")
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
BODY = <<-DOC
|
365
|
+
processor = @eggshell
|
366
|
+
out = @out
|
367
|
+
call_depth = 0
|
368
|
+
DOC
|
369
|
+
|
370
|
+
BODY_MACRO = <<-DOC
|
371
|
+
processor = @eggshell
|
372
|
+
DOC
|
373
|
+
|
374
|
+
LINE_OUT = <<-DOC
|
375
|
+
out << @@LINE@@
|
376
|
+
DOC
|
377
|
+
|
378
|
+
LINE = <<-DOC
|
379
|
+
lines << @@LINE@@
|
380
|
+
DOC
|
381
|
+
|
382
|
+
LINE_EGG = <<-DOC
|
383
|
+
lines << Eggshell::Line.new(@@LINE@@, @@TAB@@, @@INDENT@@, @@LINE_NUM@@)
|
384
|
+
DOC
|
385
|
+
|
386
|
+
BLOCK_HANDLER = <<-DOC
|
387
|
+
handler = processor.get_block_handler('@@HANDLER_NAME@@')
|
388
|
+
lines = @@RAW_LINES@@
|
389
|
+
DOC
|
390
|
+
|
391
|
+
HANDLER_COMMIT = <<-DOC
|
392
|
+
handler.process('@@HANDLER_NAME@@', @@ARGS@@, lines, out, call_depth)
|
393
|
+
DOC
|
394
|
+
|
395
|
+
MACRO_HANDLER = <<-DOC
|
396
|
+
handler = processor.get_macro_handler('@@HANDLER_NAME@@')
|
397
|
+
lines = @@RAW_LINES@@
|
398
|
+
DOC
|
399
|
+
|
400
|
+
PIPE_APPEND_START = <<-DOC
|
401
|
+
# pipe:append
|
402
|
+
_handler = handler
|
403
|
+
_out = out
|
404
|
+
out = lines
|
405
|
+
DOC
|
406
|
+
|
407
|
+
PIPE_APPEND_END = <<-DOC
|
408
|
+
lines = out
|
409
|
+
out = _out
|
410
|
+
handler = _handler
|
411
|
+
_out = nil
|
412
|
+
_handler = nil
|
413
|
+
# pipe:append:end
|
414
|
+
DOC
|
415
|
+
|
416
|
+
PIPE_INLINE_START = <<-DOC
|
417
|
+
# pipe:inline
|
418
|
+
_lines = lines
|
419
|
+
_out = out
|
420
|
+
out = []
|
421
|
+
DOC
|
422
|
+
|
423
|
+
HANDLER_SAVE_REF = <<-DOC
|
424
|
+
handler_@@ID@@ = handler
|
425
|
+
lines_@@ID@@ = lines
|
426
|
+
DOC
|
427
|
+
|
428
|
+
HANDLER_RESTORE_REF = <<-DOC
|
429
|
+
handler = handler_@@ID@@
|
430
|
+
lines = lines_@@ID@@
|
431
|
+
DOC
|
432
|
+
|
433
|
+
PIPE_INLINE_END = <<-DOC
|
434
|
+
if _lines[-1].is_a?(Eggshell::Line)
|
435
|
+
_lines[-1] = lines[-1].replace(_lines[-1].line.sub(Eggshell::Processor::PIPE_INLINE, out.join('\\n')))
|
436
|
+
else
|
437
|
+
_lines[-1] = _lines[-1].sub(Eggshell::Processor::PIPE_INLINE, out.join('\\n'))
|
438
|
+
end
|
439
|
+
|
440
|
+
lines = _lines
|
441
|
+
out = _out
|
442
|
+
_out = nil
|
443
|
+
_lines = nil
|
444
|
+
# pipe:inline:end
|
445
|
+
DOC
|
446
|
+
|
447
|
+
# When a block immediately follows in a block-macro chain and this block is same as initial block
|
448
|
+
BLOCK_CHAIN_APPEND = <<-DOC
|
449
|
+
lines += @@RAW_LINES@@
|
450
|
+
DOC
|
451
|
+
|
452
|
+
end; end
|
@@ -1,895 +1,309 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
'
|
16
|
-
'
|
17
|
-
'+' => 51, '-' => 50,
|
18
|
-
'<' => 49, '>' => 49, '<=' => 49, '=>' => 49,
|
19
|
-
'==' => 48, '!=' => 48,
|
20
|
-
'&' => 39,
|
21
|
-
'^' => 38,
|
22
|
-
'|' => 37,
|
23
|
-
'&&' => 36,
|
24
|
-
'||' => 35
|
1
|
+
# This class is the root namespace for parsing and evaluating expressions.
|
2
|
+
# Each instance has its own parser and evaluator and provides convenience
|
3
|
+
# methods to quickly run an expresion.
|
4
|
+
module Eggshell; class ExpressionEvaluator
|
5
|
+
ESCAPE_MAP = {
|
6
|
+
'\\n'=> "\n",
|
7
|
+
'\\r' => "\r",
|
8
|
+
'\\t' => "\t",
|
9
|
+
"\\(" => "(",
|
10
|
+
"\\)" => ")",
|
11
|
+
"\\{" => "{",
|
12
|
+
"\\}" => "}",
|
13
|
+
"\\[" => "[",
|
14
|
+
"\\]" => "]",
|
15
|
+
"\\'" => "'",
|
16
|
+
'\\"' => '"'
|
25
17
|
}.freeze
|
26
|
-
|
27
|
-
|
18
|
+
|
19
|
+
OPERATOR_MAP = {
|
20
|
+
'='.to_sym => 1000,
|
21
|
+
'=='.to_sym => 999,
|
22
|
+
'!='.to_sym => 999,
|
23
|
+
'<'.to_sym => 990,
|
24
|
+
'<='.to_sym => 990,
|
25
|
+
'>'.to_sym => 990,
|
26
|
+
'>='.to_sym => 990,
|
27
|
+
'?'.to_sym => 900,
|
28
|
+
':'.to_sym => 899,
|
29
|
+
'<<'.to_sym => 200,
|
30
|
+
'>>'.to_sym => 200,
|
31
|
+
'~'.to_sym => 200,
|
32
|
+
'&'.to_sym => 200,
|
33
|
+
'|'.to_sym => 199,
|
34
|
+
'*'.to_sym => 150,
|
35
|
+
'/'.to_sym => 150,
|
36
|
+
'%'.to_sym => 150,
|
37
|
+
'^'.to_sym => 150,
|
38
|
+
'+'.to_sym => 100,
|
39
|
+
'-'.to_sym => 100,
|
40
|
+
'&&'.to_sym => 99,
|
41
|
+
'||'.to_sym => 99
|
28
42
|
}.freeze
|
29
|
-
|
30
|
-
class ExprArray < Array
|
31
|
-
attr_accessor :dynamic
|
32
|
-
attr_reader :dyn_keys
|
33
|
-
|
34
|
-
# Adds a term to the array. If it's a dynamic statement, its position is
|
35
|
-
# marked and this array is marked as dynamic for evaluation later.
|
36
|
-
def <<(term)
|
37
|
-
@dyn_keys = [] if !@dyn_keys
|
38
|
-
|
39
|
-
# cascade dynamic status
|
40
|
-
if term.is_a?(ExprArray) || term.is_a?(ExprHash)
|
41
|
-
if term.dynamic
|
42
|
-
@dynamic = true
|
43
|
-
@dyn_keys << self.length
|
44
|
-
end
|
45
|
-
elsif term.is_a?(Array)
|
46
|
-
if term[0] == :op
|
47
|
-
|
48
|
-
end
|
49
|
-
term, dyn = Eggshell::ExpressionEvaluator::struct_compact(term)
|
50
|
-
if dyn
|
51
|
-
@dynamic = true
|
52
|
-
@dyn_keys << self.length
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
self.push(term)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
class ExprHash < Hash
|
61
|
-
attr_accessor :dynamic
|
62
|
-
attr_reader :dyn_keys
|
63
|
-
|
64
|
-
def add_term(key, term)
|
65
|
-
@dyn_keys = [] if !@dyn_keys
|
66
|
-
|
67
|
-
if term.is_a?(ExprArray) || term.is_a?(ExprHash)
|
68
|
-
if term.dynamic
|
69
|
-
@dynamic = true
|
70
|
-
@dyn_keys << key
|
71
|
-
end
|
72
|
-
elsif term.is_a?(Array)
|
73
|
-
term, dyn = Eggshell::ExpressionEvaluator::struct_compact(term)
|
74
|
-
if dyn
|
75
|
-
@dynamic = true
|
76
|
-
@dyn_keys << key
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
self[key] = term
|
81
|
-
end
|
82
|
-
end
|
83
43
|
|
84
44
|
def initialize(vars = nil, funcs = nil)
|
85
45
|
@vars = vars || {}
|
86
46
|
@funcs = funcs || {}
|
87
47
|
@cache = {}
|
48
|
+
@parser = Parser::DefaultParser.new
|
49
|
+
#@evaluator = Evaluator.new(@vars, @funcs)
|
88
50
|
end
|
89
51
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
52
|
+
# Maps 1 or more virtual function names to a handler.
|
53
|
+
#
|
54
|
+
# Virtual functions take the form of `funcname` or `ns:funcname`. When
|
55
|
+
# registering functions, all function names will be mapped to the same
|
56
|
+
# namespace that's passed in. If no names are given, the handler will
|
57
|
+
# be set to handle all function calls in the namespace.
|
58
|
+
#
|
59
|
+
# The handler should either handle 1-to-1 function calls (e.g. `ns:func`
|
60
|
+
# goes to `handler.func()`) or it should contain the following method
|
61
|
+
# signature: `def exec_func(funcname, args = [])`. If there's a 1-to-1
|
62
|
+
# match, arguments are expanded into individual arguments instead of
|
63
|
+
# an array.
|
64
|
+
#
|
65
|
+
# @todo expand mapping of functions so that we don't have to check each
|
66
|
+
# time a func is referenced whether handler can handle it (only applies
|
67
|
+
# to explicit names)
|
68
|
+
def register_functions(handler, names = nil, ns = '')
|
69
|
+
names = names.split(',') if names.is_a?(String) && names.strip != ''
|
70
|
+
if names.is_a?(Array)
|
71
|
+
names.each do |name|
|
72
|
+
@funcs["#{ns}:#{name}"] = handler
|
103
73
|
end
|
104
74
|
else
|
105
|
-
@funcs[
|
75
|
+
@funcs["#{ns}:"] = handler
|
106
76
|
end
|
107
77
|
end
|
108
78
|
|
109
|
-
attr_reader :vars, :funcs
|
110
|
-
|
79
|
+
attr_reader :vars, :funcs, :parser
|
80
|
+
|
111
81
|
def parse(statement, cache = true)
|
112
82
|
parsed = @cache[statement]
|
113
83
|
return parsed if cache && parsed
|
114
|
-
|
115
|
-
parsed =
|
84
|
+
|
85
|
+
parsed = @parser.parse(statement)
|
116
86
|
@cache[statement] = parsed if cache
|
117
87
|
return parsed
|
118
88
|
end
|
119
89
|
|
120
|
-
def evaluate(statement, cache = true)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# Normalizes a term.
|
126
|
-
# @param Object term If `String`, attempts to infer type (either `Fixnum`, `Float`, or `[:var, varname]`)
|
127
|
-
# @param Boolean preserve_str If true and input is string but not number, return string literal.
|
128
|
-
def self.term_val(term, preserve_str = false)
|
129
|
-
if term.is_a?(String)
|
130
|
-
if preserve_str
|
131
|
-
return term
|
132
|
-
elsif term.match(/^-?\d+$/)
|
133
|
-
return term.to_i
|
134
|
-
elsif term.match(/^-?\d*\.\d+$/)
|
135
|
-
return term.to_f
|
136
|
-
elsif term == 'null' || term == 'nil'
|
137
|
-
return nil
|
138
|
-
elsif term == 'true'
|
139
|
-
return true
|
140
|
-
elsif term == 'false'
|
141
|
-
return false
|
142
|
-
end
|
143
|
-
|
144
|
-
if term[0] == '-'
|
145
|
-
return [:op, '-', 0, [:var, term[1..-1]], :group]
|
146
|
-
else
|
147
|
-
return [:var, term]
|
148
|
-
end
|
149
|
-
end
|
150
|
-
return term
|
151
|
-
end
|
152
|
-
|
153
|
-
# Inserts a term into the right-most operand.
|
154
|
-
#
|
155
|
-
# Operator structure: `[:op, 'operator', 'left-term', 'right-term']
|
156
|
-
def self.op_insert(frag, term)
|
157
|
-
while frag[3].is_a?(Array)
|
158
|
-
frag = frag[3]
|
159
|
-
end
|
160
|
-
frag[3] = term
|
161
|
-
end
|
162
|
-
|
163
|
-
# Restructures operands if new operator has higher predence than previous operator.
|
164
|
-
# @param String nop New operator.
|
165
|
-
# @param Array frag Operator fragment.
|
166
|
-
# @param Array stack Fragment stack.
|
167
|
-
def self.op_precedence(nop, frag, stack)
|
168
|
-
topfrag = frag
|
169
|
-
lptr = topfrag
|
170
|
-
errs "op_precedence: frag=#{frag.inspect}", 1
|
90
|
+
def evaluate(statement, do_parse = true, cache = true, vtable = nil, ftable = nil)
|
91
|
+
vtable = @vars if !vtable.is_a?(Hash)
|
92
|
+
ftable = @funcs if !ftable.is_a?(Hash)
|
93
|
+
parsed = statement
|
171
94
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
# @todo look out for :nested in [4]?
|
176
|
-
while frag[3].is_a?(Array) && frag[3][0] == :op && frag[3][-1] != :group
|
177
|
-
lptr = frag
|
178
|
-
frag = frag[3]
|
95
|
+
if !statement.is_a?(Array)
|
96
|
+
return statement if !do_parse || !statement.is_a?(String)
|
97
|
+
parsed = parse(statement, cache)
|
179
98
|
end
|
180
99
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
lptr[3] = nil
|
194
|
-
else
|
195
|
-
frag[3] = [:op, nop, frag[3], nil]
|
196
|
-
end
|
197
|
-
stack << topfrag
|
198
|
-
else
|
199
|
-
stack << [:op, nop, frag, nil]
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
def self.struct(str)
|
204
|
-
errs "struct parse: #{str}", 9
|
205
|
-
toks = str.split(REGEX_EXPR_STATEMENT)
|
206
|
-
char_pos = 0
|
207
|
-
state = [:nil]
|
208
|
-
last_state = nil
|
209
|
-
d = 0
|
210
|
-
|
211
|
-
# each element on the stack points to a nested level (so root is 0, first parenthesis is 1, second is 2, etc.)
|
212
|
-
# ptr points to the deepest level
|
213
|
-
stack = [ [] ]
|
214
|
-
ptr = stack[0]
|
215
|
-
term = nil
|
216
|
-
term_state = nil
|
217
|
-
|
218
|
-
quote_delim = nil
|
219
|
-
|
220
|
-
# last_key tracks the last key being built up in a map expression. this allows maps to contain maps
|
221
|
-
map_key = nil
|
222
|
-
last_key = []
|
223
|
-
|
224
|
-
i = 0
|
225
|
-
toks.delete('')
|
226
|
-
|
227
|
-
# push a new operator or a term onto the stack
|
228
|
-
_op_push = lambda {|operator|
|
229
|
-
errs "_op_push: #{operator} (term=#{term}) (ptr[-1]=#{ptr[-1].inspect})", 1
|
230
|
-
if term
|
231
|
-
# check what the last item is.
|
232
|
-
frag = ptr.pop
|
233
|
-
term = term_val(term, term_state == :quote)
|
234
|
-
if frag.is_a?(Array) && frag[0] == :op
|
235
|
-
op_insert(frag, term)
|
236
|
-
if operator
|
237
|
-
op_precedence(operator, frag, ptr)
|
238
|
-
else
|
239
|
-
ptr << frag
|
240
|
-
end
|
241
|
-
else
|
242
|
-
ptr << frag if frag
|
243
|
-
ptr << [:op, operator, term, nil]
|
244
|
-
end
|
245
|
-
|
246
|
-
errs ">> state=#{state.inspect}, stack=#{stack.inspect}", 0
|
247
|
-
errs ">> frag=#{frag.inspect}", 0
|
248
|
-
term = nil
|
249
|
-
term_state = nil
|
250
|
-
elsif operator
|
251
|
-
# no term, so pushing operator. either generate new operator fragment or put in precedence order
|
252
|
-
frag = ptr.pop
|
253
|
-
if frag.is_a?(Array)
|
254
|
-
if frag[0] == :op
|
255
|
-
# preserve parenthesized group
|
256
|
-
if frag[4] == :group
|
257
|
-
ptr << [:op, operator, frag, nil]
|
258
|
-
else
|
259
|
-
op_precedence(operator, frag, ptr)
|
260
|
-
end
|
261
|
-
else
|
262
|
-
ptr << [:op, operator, frag, nil]
|
263
|
-
end
|
264
|
-
elsif frag
|
265
|
-
ptr << [:op, operator, frag, nil]
|
266
|
-
else
|
267
|
-
# @todo throw exception
|
268
|
-
end
|
269
|
-
end
|
270
|
-
}
|
271
|
-
|
272
|
-
# closes out a state
|
273
|
-
_transition = lambda {|st|
|
274
|
-
errs "_transition: :#{st} (term=#{term.class}, ts=#{term_state})", 1
|
275
|
-
errs "state=#{state.inspect}"
|
276
|
-
ret = nil
|
277
|
-
if st == :op
|
278
|
-
ret = st
|
279
|
-
if term
|
280
|
-
_op_push.call(nil)
|
281
|
-
state.pop
|
282
|
-
term = nil
|
283
|
-
term_state = nil
|
284
|
-
errs "_transition: ptr=#{ptr.inspect}", 1
|
285
|
-
else
|
286
|
-
# @todo throw exception
|
287
|
-
end
|
288
|
-
# @todo throw exception -- no closing ')'
|
289
|
-
elsif st == :tern
|
290
|
-
if term
|
291
|
-
ptr << term_val(term, term_state == :quote)
|
292
|
-
term = nil
|
293
|
-
term_state = nil
|
294
|
-
end
|
295
|
-
|
296
|
-
if ptr[-4] == :tern
|
297
|
-
right = ptr.pop
|
298
|
-
left = ptr.pop
|
299
|
-
cond = ptr.pop
|
300
|
-
ptr.pop
|
301
|
-
ptr << [:op_tern, cond, left, right]
|
302
|
-
else
|
303
|
-
# @todo throw exception
|
100
|
+
ret = nil
|
101
|
+
parsed.each do |frag|
|
102
|
+
ftype = frag[0]
|
103
|
+
if ftype == :op
|
104
|
+
op_val = frag[1][0].is_a?(Array) ? evaluate([frag[1][0]], false) : frag[1][0]
|
105
|
+
z = 1
|
106
|
+
while z < frag[1].length
|
107
|
+
op = frag[1][z]
|
108
|
+
rop = frag[1][z+1]
|
109
|
+
rop = evaluate([rop]) if rop.is_a?(Array)
|
110
|
+
op_val = self.class.op_eval(op_val, op, rop)
|
111
|
+
z += 2
|
304
112
|
end
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
term_state = nil
|
311
|
-
elsif st == :fnop || st == :nest
|
312
|
-
else
|
313
|
-
# @todo throw exception?
|
314
|
-
end
|
315
|
-
|
316
|
-
return ret
|
317
|
-
}
|
318
|
-
|
319
|
-
errs "toks = #{toks.inspect}", 5
|
320
|
-
while (i < toks.length)
|
321
|
-
tok = toks[i]
|
322
|
-
i += 1
|
323
|
-
char_pos += tok.length
|
324
|
-
errs "tok: #{tok} (state=#{state[-1]}, term=#{term})", 6
|
325
|
-
if tok == '\\'
|
326
|
-
i += 1 if toks[i] == ''
|
327
|
-
char_pos += toks[i].length
|
328
|
-
term += toks[i]
|
329
|
-
i += 1
|
330
|
-
elsif term_state == :quote
|
331
|
-
if tok != quote_delim
|
332
|
-
term += tok
|
113
|
+
ret = op_val
|
114
|
+
elsif ftype == :op_tern
|
115
|
+
cond = frag[1].is_a?(Array) ? evaluate(frag[1]) : frag[1]
|
116
|
+
if cond
|
117
|
+
ret = frag[2].is_a?(Array) ? evaluate(frag[2], false) : frag[2]
|
333
118
|
else
|
334
|
-
|
335
|
-
_transition.call(state[-1])
|
336
|
-
term = nil
|
337
|
-
term_state = nil
|
338
|
-
end
|
339
|
-
elsif (tok == "'" || tok == '"')
|
340
|
-
quote_delim = tok
|
341
|
-
term = ''
|
342
|
-
term_state = :quote
|
343
|
-
elsif tok == ','
|
344
|
-
errs "comma: ptr=#{ptr.inspect}", 5
|
345
|
-
_transition.call(state[-1])
|
346
|
-
term = nil
|
347
|
-
term_state = nil
|
348
|
-
elsif tok == '{'
|
349
|
-
if state[-1] == :nil #&& stack[0][-1][0] == :fn
|
350
|
-
delim = '{'
|
351
|
-
while toks[i]
|
352
|
-
delim += toks[i]
|
353
|
-
i += 1
|
354
|
-
end
|
355
|
-
|
356
|
-
# @todo is it safe to do stack[0]? stick to ptr instead?
|
357
|
-
if term
|
358
|
-
stack[0] << term_val(term, term_state == :quote)
|
359
|
-
term = nil
|
360
|
-
term_state = nil
|
361
|
-
end
|
362
|
-
stack[0] << [:brace_op, delim]
|
363
|
-
break
|
364
|
-
end
|
365
|
-
|
366
|
-
stack << []
|
367
|
-
state << :hash
|
368
|
-
ptr = stack[-1]
|
369
|
-
elsif tok == '}'
|
370
|
-
errs "}", 5
|
371
|
-
_transition.call(state[-1])
|
372
|
-
|
373
|
-
rawvals = stack.pop
|
374
|
-
errs "rawvals=#{rawvals.inspect}", 4
|
375
|
-
map = ExprHash.new
|
376
|
-
imap = 0
|
377
|
-
while (imap < rawvals.length)
|
378
|
-
# @todo make sure key is scalar
|
379
|
-
# @todo make sure length is even (indicates unbalanced map def otherwise)
|
380
|
-
mkey = rawvals[imap]
|
381
|
-
mkey = mkey[1] if mkey.is_a?(Array)
|
382
|
-
map[mkey] = rawvals[imap+1]
|
383
|
-
imap += 2
|
119
|
+
ret = frag[3].is_a?(Array) ? evaluate(frag[3], false) : frag[3]
|
384
120
|
end
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
ptr << :tern
|
393
|
-
ptr << term_val(term, term_state == :quote)
|
394
|
-
term = nil
|
395
|
-
term_state = nil
|
121
|
+
elsif ftype == :func
|
122
|
+
fname = frag[1]
|
123
|
+
ns, name = fname.split(':')
|
124
|
+
if !name
|
125
|
+
name = ns
|
126
|
+
ns = ':'
|
127
|
+
fname = ':' + name
|
396
128
|
else
|
397
|
-
|
398
|
-
ptr << :tern
|
399
|
-
ptr << last
|
129
|
+
ns += ':'
|
400
130
|
end
|
401
131
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
if state[-1] == :hash || state[-1] == :tern
|
406
|
-
# case for this: when a key is quoted, the term is committed to the pointer already, so at ':' term is nil
|
407
|
-
if term != nil
|
408
|
-
ptr << term_val(term, term_state == :quote)
|
409
|
-
term = nil
|
410
|
-
term_state = nil
|
411
|
-
end
|
412
|
-
# @todo validate ternary stack length?
|
413
|
-
else
|
414
|
-
# @todo throw exception
|
415
|
-
end
|
416
|
-
errs "ptr=#{ptr.inspect}", 4
|
417
|
-
elsif tok == '['
|
418
|
-
stack << ExprArray.new
|
419
|
-
state << :arr
|
420
|
-
ptr = stack[-1]
|
421
|
-
elsif tok == ']'
|
422
|
-
last_state = _transition.call(state[-1])
|
423
|
-
state.pop
|
424
|
-
vals = stack.pop
|
425
|
-
ptr = stack[-1]
|
426
|
-
ptr << vals
|
427
|
-
elsif tok == '('
|
428
|
-
errs "(", 4
|
429
|
-
if term
|
430
|
-
# @todo throw exception if term is a quote?
|
431
|
-
# @todo throw exception if term is not a valid word?
|
432
|
-
frag = [:fn, term, nil]
|
433
|
-
stack << frag
|
434
|
-
stack << []
|
435
|
-
state << :fnop
|
436
|
-
ptr = stack[-1]
|
437
|
-
term = nil
|
438
|
-
term_state = nil
|
439
|
-
else
|
440
|
-
stack << []
|
441
|
-
state << :nest
|
442
|
-
ptr = stack[-1]
|
132
|
+
handler = ftable[fname]
|
133
|
+
if !handler && ftable[ns]
|
134
|
+
handler = ftable[ns]
|
443
135
|
end
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
# break if state[-1] == :fnop || state[-1] == :nest
|
449
|
-
#end
|
450
|
-
nest_statement = stack.pop
|
451
|
-
ptr = stack[-1]
|
452
|
-
_state = state.pop
|
453
|
-
errs ") nest_statement=#{nest_statement[0].inspect}", 5
|
454
|
-
if _state == :fnop
|
455
|
-
frag = stack.pop
|
456
|
-
frag[2] = nest_statement
|
457
|
-
ptr = stack[-1]
|
458
|
-
ptr << frag
|
459
|
-
elsif _state == :nest
|
460
|
-
if nest_statement.length > 1
|
461
|
-
# @todo throw exception, parenthetical expression should be reduced to single term
|
462
|
-
end
|
463
|
-
nest_statement = nest_statement.pop
|
464
|
-
if !nest_statement.is_a?(Array)
|
465
|
-
errs "#{str}", 0
|
466
|
-
errs "!! nest: #{nest_statement}\n#{stack.inspect}", 0
|
467
|
-
end
|
468
|
-
nest_statement << :group
|
469
|
-
inject_into(ptr, nest_statement)
|
470
|
-
elsif _state == :op
|
471
|
-
ptr << nest_statement
|
472
|
-
else
|
473
|
-
# @throw exception?
|
474
|
-
end
|
475
|
-
elsif tok.match(REGEX_OPERATORS)
|
476
|
-
# assumes negative
|
477
|
-
if tok == '-' && !term
|
478
|
-
t = ptr.pop
|
479
|
-
if t == nil
|
480
|
-
term = '-'
|
481
|
-
next
|
482
|
-
else
|
483
|
-
ptr << t
|
484
|
-
end
|
136
|
+
|
137
|
+
_args = []
|
138
|
+
frag[2].each do |ele|
|
139
|
+
_args << (ele.is_a?(Array) && ele[0].is_a?(Symbol) ? evaluate([ele]) : ele)
|
485
140
|
end
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
next
|
141
|
+
|
142
|
+
# 1. check for exact func name
|
143
|
+
# 2. check for :exec_func
|
144
|
+
if handler.respond_to?(name.to_sym)
|
145
|
+
ret = handler.send(name.to_sym, *frag[2])
|
146
|
+
elsif handler.respond_to?(:exec_func)
|
147
|
+
ret = handler.exec_func(name, frag[2])
|
494
148
|
end
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
149
|
+
elsif ftype == :var
|
150
|
+
ret = var_get(frag, false, vtable)
|
151
|
+
elsif ftype == :group
|
152
|
+
ret = evaluate(frag[1], false, cache, vtable)
|
153
|
+
elsif ftype == :array
|
154
|
+
arr = []
|
155
|
+
i = 1
|
156
|
+
while i < frag.length
|
157
|
+
arr << (frag[i].is_a?(Array) ? evaluate(frag[i]) : frag[i])
|
158
|
+
i += 1
|
500
159
|
end
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
else
|
513
|
-
break
|
514
|
-
end
|
160
|
+
ret = arr
|
161
|
+
elsif ftype == :hash
|
162
|
+
map = {}
|
163
|
+
i = 1
|
164
|
+
while i < frag.length
|
165
|
+
k = frag[i]
|
166
|
+
v = frag[i+1]
|
167
|
+
k = evaluate(k) if k.is_a?(Array) && k[0].is_a?(Symbol)
|
168
|
+
v = evaluate(v) if v.is_a?(Array) && v[0].is_a?(Symbol)
|
169
|
+
map[k] = v
|
170
|
+
i += 2
|
515
171
|
end
|
172
|
+
ret = map
|
173
|
+
elsif !ftype.is_a?(Symbol)
|
174
|
+
# assumes a literal
|
175
|
+
ret = ftype
|
516
176
|
end
|
517
177
|
end
|
518
178
|
|
519
|
-
|
520
|
-
if state[1] || term
|
521
|
-
_transition.call(state[-1])
|
522
|
-
end
|
523
|
-
|
524
|
-
errs "*** end state: #{state.inspect} ***", 2
|
525
|
-
errs "*** end stack: #{stack.inspect} ***", 2
|
526
|
-
return stack[0]
|
179
|
+
ret
|
527
180
|
end
|
528
181
|
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
if lterm != nil && rterm != nil
|
579
|
-
return [expr_eval_op(struct[1], lterm, rterm), false]
|
580
|
-
else
|
581
|
-
dyn = true
|
582
|
-
end
|
583
|
-
elsif struct[0] == :fn
|
584
|
-
dyn = true
|
585
|
-
struct[2] = struct_compact(struct[2])[0]
|
586
|
-
else
|
587
|
-
dyn = true
|
588
|
-
end
|
589
|
-
else
|
590
|
-
i = 0
|
591
|
-
while i < struct.length
|
592
|
-
substruct = struct[i]
|
593
|
-
substruct, sdyn = struct_compact(substruct)
|
594
|
-
dyn = true if sdyn
|
595
|
-
struct[i] = substruct
|
596
|
-
i += 1
|
597
|
-
end
|
182
|
+
# @todo have one set for strings, one set for numbers, and a general set for everything else
|
183
|
+
# @todo what to do with unsupported ops?
|
184
|
+
def self.op_eval(lop, op, rop)
|
185
|
+
if (lop.is_a?(Numeric) && rop.is_a?(Numeric)) || lop.is_a?(rop.class)
|
186
|
+
op = op.to_s
|
187
|
+
case op
|
188
|
+
when '==='
|
189
|
+
return lop === rop
|
190
|
+
when '=='
|
191
|
+
return lop == rop
|
192
|
+
when '!='
|
193
|
+
return lop != rop
|
194
|
+
when '+'
|
195
|
+
return lop + rop
|
196
|
+
when '-'
|
197
|
+
return lop - rop
|
198
|
+
when '/'
|
199
|
+
return lop / rop
|
200
|
+
when '*'
|
201
|
+
return lop * rop
|
202
|
+
when '<<'
|
203
|
+
return lop << rop
|
204
|
+
when '>>'
|
205
|
+
return lop >> rop
|
206
|
+
when '<'
|
207
|
+
return lop < rop
|
208
|
+
when '<='
|
209
|
+
return lop <= rop
|
210
|
+
when '>'
|
211
|
+
return lop > rop
|
212
|
+
when '>='
|
213
|
+
return lop >= rop
|
214
|
+
when '|'
|
215
|
+
return lop | rop
|
216
|
+
when '||'
|
217
|
+
return lop || rop
|
218
|
+
when '&'
|
219
|
+
return lop & rop
|
220
|
+
when '&&'
|
221
|
+
return lop && rop
|
222
|
+
when '%'
|
223
|
+
return lop % rop
|
224
|
+
when '^'
|
225
|
+
return lop ^ rop
|
226
|
+
when '='
|
227
|
+
# @todo assign rop to lop
|
598
228
|
end
|
599
229
|
else
|
600
|
-
|
230
|
+
|
601
231
|
end
|
602
|
-
|
603
|
-
return [struct, dyn]
|
604
232
|
end
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
expr = expr[0] if expr.is_a?(Array) && expr.length == 1
|
612
|
-
|
613
|
-
ret = nil
|
614
|
-
if expr.is_a?(ExprArray) || expr.is_a?(ExprHash)
|
615
|
-
ret = expr.clone
|
616
|
-
(expr.dyn_keys || []).each do |key|
|
617
|
-
ret[key] = expr_eval(expr[key], vtable, ftable)
|
233
|
+
|
234
|
+
def var_get(var, do_ptr = false, vtable = nil)
|
235
|
+
vtable = @vars if !vtable
|
236
|
+
if var.is_a?(String)
|
237
|
+
if var.match(/^[a-zA-Z_][a-zA-Z0-9_\.]*$/)
|
238
|
+
return vtable[var] if vtable.has_key?(var)
|
618
239
|
end
|
619
|
-
|
620
|
-
|
621
|
-
ret = []
|
622
|
-
i = 0
|
623
|
-
expr.each do |subexpr|
|
624
|
-
ret[i] = expr_eval(subexpr, vtable, ftable)
|
625
|
-
i += 1
|
626
|
-
end
|
627
|
-
elsif expr[0]
|
628
|
-
frag = expr
|
629
|
-
if frag[0] == :op_tern
|
630
|
-
cond = expr_eval(frag[1], vtable, ftable)
|
631
|
-
if cond
|
632
|
-
ret = expr_eval(frag[2], vtable, ftable)
|
633
|
-
else
|
634
|
-
ret = expr_eval(frag[3], vtable, ftable)
|
635
|
-
end
|
636
|
-
elsif frag[0] == :op
|
637
|
-
op = frag[1]
|
638
|
-
lterm = frag[2]
|
639
|
-
rterm = frag[3]
|
640
|
-
|
641
|
-
errs "lterm >> #{lterm.inspect}", 1
|
642
|
-
errs "rterm >> #{rterm.inspect}", 1
|
643
|
-
if lterm.is_a?(Array)
|
644
|
-
if lterm[0] == :var
|
645
|
-
lterm = retrieve_var(lterm[1], vtable, ftable)
|
646
|
-
else
|
647
|
-
lterm = expr_eval(lterm, vtable, ftable)
|
648
|
-
end
|
649
|
-
end
|
650
|
-
if rterm.is_a?(Array)
|
651
|
-
if rterm[0] == :var
|
652
|
-
rterm = retrieve_var(rterm[1], vtable, ftable)
|
653
|
-
else
|
654
|
-
rterm = expr_eval(rterm, vtable, ftable)
|
655
|
-
end
|
656
|
-
end
|
657
|
-
errs "lterm << #{lterm.inspect}", 0
|
658
|
-
errs "rterm << #{rterm.inspect}", 0
|
659
|
-
ret = expr_eval_op(op, lterm, rterm)
|
660
|
-
elsif frag[0] == :fn
|
661
|
-
# @todo see if fname itself has an entry, and call if it has method `func_call` (?)
|
662
|
-
fkey = frag[1]
|
663
|
-
args = frag[2]
|
664
|
-
ns, fname = fkey.split(':')
|
665
|
-
if !fname
|
666
|
-
fname = ns
|
667
|
-
ns = ''
|
668
|
-
end
|
669
|
-
fname = fname.to_sym
|
670
|
-
|
671
|
-
handler = ftable[fkey]
|
672
|
-
handler = ftable[ns] if !handler
|
240
|
+
var = @parser.parse(var)
|
241
|
+
end
|
673
242
|
|
674
|
-
|
675
|
-
|
676
|
-
args.each do |arg|
|
677
|
-
el = expr_eval(arg, vtable, ftable)
|
678
|
-
cp << el
|
679
|
-
end
|
243
|
+
ptr_lst = nil
|
244
|
+
ptr = vtable
|
680
245
|
|
681
|
-
|
246
|
+
i = 0
|
247
|
+
parts = var
|
248
|
+
# @todo if do_ptr, set it as parts.length - 1? (and avoid having to save ptr_lst)
|
249
|
+
while (i < parts.length)
|
250
|
+
part = parts[i]
|
251
|
+
ptr_lst = ptr
|
252
|
+
if part == :var
|
253
|
+
key = parts[i+1]
|
254
|
+
if ptr.is_a?(Hash) && ptr[key]
|
255
|
+
ptr = ptr[key]
|
256
|
+
if ptr != nil
|
257
|
+
i += 2
|
258
|
+
next
|
682
259
|
else
|
683
|
-
|
684
|
-
# @todo log error or throw exception? maybe this should be a param option
|
685
|
-
end
|
686
|
-
elsif frag[0] == :var
|
687
|
-
ret = retrieve_var(frag[1], vtable, ftable)
|
688
|
-
if ret.is_a?(Array) && ret[0].is_a?(Symbol)
|
689
|
-
ret = expr_eval(ret, vtable, ftable)
|
260
|
+
break
|
690
261
|
end
|
691
|
-
end
|
692
|
-
end
|
693
|
-
else
|
694
|
-
return expr
|
695
|
-
end
|
696
|
-
return ret
|
697
|
-
end
|
698
|
-
|
699
|
-
def self.expr_eval_op(op, lterm, rterm)
|
700
|
-
ret = nil
|
701
|
-
#$stderr.write "** #{lterm.inspect} ( #{op} ) #{rterm.inspect}\n"
|
702
|
-
|
703
|
-
case op
|
704
|
-
when '=='
|
705
|
-
ret = lterm == rterm
|
706
|
-
when '!='
|
707
|
-
ret = lterm != rterm
|
708
|
-
when '<'
|
709
|
-
ret = lterm < rterm
|
710
|
-
when '>'
|
711
|
-
ret = lterm > rterm
|
712
|
-
when '<='
|
713
|
-
ret = lterm <= rterm
|
714
|
-
when '>='
|
715
|
-
ret = lterm >= rterm
|
716
|
-
when '+'
|
717
|
-
ret = lterm + rterm
|
718
|
-
when '-'
|
719
|
-
ret = lterm - rterm
|
720
|
-
when '*'
|
721
|
-
ret = lterm * rterm
|
722
|
-
when '/'
|
723
|
-
ret = lterm / rterm
|
724
|
-
when '%'
|
725
|
-
ret = lterm % rterm
|
726
|
-
when '&'
|
727
|
-
ret = lterm & rterm
|
728
|
-
when '|'
|
729
|
-
ret = lterm | rterm
|
730
|
-
when '&&'
|
731
|
-
ret = lterm && rterm
|
732
|
-
when '||'
|
733
|
-
ret = lterm || rterm
|
734
|
-
end
|
735
|
-
# @todo support other bitwise
|
736
|
-
return ret
|
737
|
-
end
|
738
|
-
|
739
|
-
# Attempts to a resolve a variable in the form `a.b.c.d` from an initial
|
740
|
-
# variable map. Using `.` as a delimiter, the longest match name is attempted
|
741
|
-
# to be matched (e.g. `a.b.c.d`, then `a.b.c`, then `a.b`, then `a`), and any
|
742
|
-
# remaining parts are considered getters than are chained together.
|
743
|
-
#
|
744
|
-
# For instance, if `a.b.c.d` partially resolves to `a.b`, then:
|
745
|
-
# # check if `(a.b).c` is a valid key/index/method
|
746
|
-
# # check if `(a.b.c).d` is a valid key/index/method
|
747
|
-
#
|
748
|
-
# If at any point a `nil` is encountered in the above scenario, a `nil` will
|
749
|
-
# be returned.
|
750
|
-
# @param Boolean return_ptr If true, returns the object holding the value, not
|
751
|
-
# the value itself.
|
752
|
-
# @return Either the resolved value (or nil), or if `return_ptr` is true, a structure
|
753
|
-
# in the form {{[n-1, [ley, type]]}} where `n-1` is the parent of the last key/get,
|
754
|
-
# {{key}} is the key/getter, and {{type}} is either {{:arr}} or {{:get}}/
|
755
|
-
def self.retrieve_var(var, vtable, ftable, return_ptr = false)
|
756
|
-
# @todo only do this when there's no [] and return_ptr is false
|
757
|
-
errs "retrieve_var: #{var}", 5
|
758
|
-
retval = vtable[var]
|
759
|
-
|
760
|
-
if !retval
|
761
|
-
# @todo more validation
|
762
|
-
# @todo support key expressions?
|
763
|
-
val = nil
|
764
|
-
vparts = var.split(/(\.|\[|\])/)
|
765
|
-
vparts.delete("")
|
766
|
-
|
767
|
-
ptr = vtable
|
768
|
-
last_ptr = nil
|
769
|
-
last_key = nil
|
770
|
-
|
771
|
-
i = 0
|
772
|
-
key = ''
|
773
|
-
type = :arr
|
774
|
-
# need to quote first element so that loop doesn't assume this to be a var reference
|
775
|
-
vparts[0] = "'#{vparts[0]}'"
|
776
|
-
|
777
|
-
while i < vparts.length
|
778
|
-
tok = vparts[i]
|
779
|
-
errs "var:tok=#{tok}", 4
|
780
|
-
i += 1
|
781
|
-
check_key = false
|
782
|
-
if tok == '.'
|
783
|
-
type = :get
|
784
|
-
check_key = true
|
785
|
-
elsif tok == '['
|
786
|
-
type = :arr
|
787
|
-
check_key = true
|
788
|
-
elsif tok == ']'
|
789
|
-
check_key = true
|
790
262
|
else
|
791
|
-
|
263
|
+
ptr = nil
|
264
|
+
break
|
792
265
|
end
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
if !ptr.respond_to?(:[]) || ptr.is_a?(Numeric)
|
801
|
-
ptr = nil
|
802
|
-
break
|
803
|
-
end
|
804
|
-
|
805
|
-
# lookup var reference if not quoted and not integer
|
806
|
-
if key[0] == '"' || key[0] == "'"
|
807
|
-
key = key[1...-1]
|
808
|
-
elsif key.match(/[0-9]+/)
|
809
|
-
key = key.to_i
|
810
|
-
else
|
811
|
-
key = retrieve_var(key, vtable, {}, false)
|
812
|
-
end
|
813
|
-
|
814
|
-
# make sure key is numeric for array
|
815
|
-
if ptr.is_a?(Array) && !key.is_a?(Numeric)
|
816
|
-
ptr = nil
|
817
|
-
break
|
818
|
-
end
|
819
|
-
|
820
|
-
ptr = ptr[key]
|
821
|
-
if ptr
|
822
|
-
last_key = [key, :arr]
|
823
|
-
else
|
824
|
-
break
|
825
|
-
end
|
826
|
-
else
|
827
|
-
# @todo sanity check? (no quotes, not numeric or starting with num)
|
828
|
-
# @todo restrict to 'get_*'?
|
829
|
-
meth1 = key.to_sym
|
830
|
-
meth2 = ('get_' + key).to_sym
|
831
|
-
if ptr.respond_to?(meth1)
|
832
|
-
last_ptr = ptr
|
833
|
-
ptr = ptr.send(meth1)
|
834
|
-
elsif ptr.respond_to?(meth2)
|
835
|
-
last_ptr = ptr
|
836
|
-
ptr = ptr.send(meth2)
|
837
|
-
else
|
838
|
-
ptr = nil
|
839
|
-
break
|
840
|
-
end
|
266
|
+
elsif part[0] == :index_access
|
267
|
+
idx = part[1]
|
268
|
+
if idx.is_a?(Array)
|
269
|
+
idx = var_get(idx)
|
270
|
+
if idx == nil
|
271
|
+
ptr = nil
|
272
|
+
break
|
841
273
|
end
|
842
|
-
|
843
|
-
key = ''
|
844
274
|
end
|
845
|
-
|
846
|
-
|
847
|
-
retval = ptr
|
848
|
-
if return_ptr
|
849
|
-
if last_ptr != nil
|
850
|
-
retval = [last_ptr, last_key]
|
275
|
+
if (ptr.is_a?(Array) || ptr.is_a?(Hash)) && ptr[idx]
|
276
|
+
ptr = ptr[idx]
|
851
277
|
else
|
852
|
-
|
278
|
+
ptr = nil
|
279
|
+
break
|
853
280
|
end
|
281
|
+
elsif part[0] == :member_access
|
282
|
+
mname = 'get_' + part[1]
|
283
|
+
if ptr.respond_to?(mname.to_sym)
|
284
|
+
ptr = ptr.send(mname.to_sym)
|
285
|
+
else
|
286
|
+
ptr = nil
|
287
|
+
break
|
288
|
+
end
|
289
|
+
elsif part[0] == :func
|
290
|
+
ptr = evaluate(part, false)
|
291
|
+
break if ptr == nil
|
854
292
|
end
|
293
|
+
i += 1
|
855
294
|
end
|
856
|
-
|
857
|
-
return
|
858
|
-
end
|
859
|
-
|
860
|
-
#
|
861
|
-
def set_var(var, key, type, value)
|
862
|
-
if type == :arr
|
863
|
-
# @todo check key if Array
|
864
|
-
if var.is_a?(Hash) || var.is_a?(Array)
|
865
|
-
var[key] = value
|
866
|
-
end
|
867
|
-
else
|
868
|
-
sym = "set_#{key}".to_sym
|
869
|
-
if var.respond_to?(sym)
|
870
|
-
var.send(sym, value)
|
871
|
-
end
|
872
|
-
end
|
295
|
+
|
296
|
+
return do_ptr ? ptr_lst : ptr
|
873
297
|
end
|
874
298
|
|
875
|
-
def
|
876
|
-
if !struct.is_a?(Array)
|
877
|
-
return struct
|
878
|
-
elsif struct[0] == :op
|
879
|
-
lterm = restructure(struct[2])
|
880
|
-
rterm = restructure(struct[3])
|
881
|
-
return "#{lterm} #{struct[1]} #{rterm}"
|
882
|
-
elsif struct[0] == :var
|
883
|
-
return struct[1]
|
884
|
-
else
|
885
|
-
end
|
299
|
+
def var_set(var, val)
|
886
300
|
end
|
887
301
|
|
888
|
-
def
|
889
|
-
|
890
|
-
$stderr.write "[#{lvl}]#{' ' * ($errs_write_indent-lvl)}#{str}\n"
|
302
|
+
def test_func(*args)
|
303
|
+
puts "test_func: #{args.inspect}"
|
891
304
|
end
|
892
|
-
end
|
305
|
+
end; end
|
893
306
|
|
894
|
-
|
895
|
-
|
307
|
+
require_relative './expression-evaluator/lexer.rb'
|
308
|
+
require_relative './expression-evaluator/parser.rb'
|
309
|
+
require_relative './expression-evaluator/evaluator.rb'
|