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.
@@ -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
- # Parses and evaluates statements (expressions). Can be used statically or
2
- # as an instance.
3
- module Eggshell;end
4
- class Eggshell::ExpressionEvaluator
5
- REGEX_EXPR_PLACEHOLDERS = /(\\|\$\[|\$\{|\]|\}|\+|\-|>|<|=|\s+|\(|\)|\*|\/`)/
6
- REGEX_EXPR_STATEMENT = /(\(|\)|,|\[|\]|\+|-|\*|\/|%|<=|>=|==|<|>|"|'|\s+|\\|\{|\}|:|\?)/
7
- REGEX_OPERATORS = /\+|-|\*|\/|%|<=|>=|<|>|==|!=|&&|\|\||&|\|/
8
- REGEX_VARNAME = /[\w\d]+/
9
-
10
- LOG_OP = 2
11
- LOG_LEVEL = 0
12
-
13
- OP_PRECEDENCE = {
14
- '++' => 150, '--' => 150,
15
- '*' => 60, '/' => 60, '%' => 60,
16
- '<<' => 55, '>>' => 55,
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
- OP_RTL = {
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
- # Registers a function for embedded expressions. Functions are grouped into namespaces,
91
- # and a handler can be assigned to handle all function calls within that namespace, or
92
- # a specific set of functions within the namespace. The root namespace is a blank string.
93
- #
94
- # @param String func_key In the form `ns` or `ns:func_name`. For functions in the
95
- # root namespace, do `:func_name`.
96
- # @param Object handler
97
- # @param Array func_names If `func_key` only refers to a namespace but the handler
98
- # needs to only handle a subset of functions, supply the list of function names here.
99
- def register_functions(func_key, handler, func_names = nil)
100
- if !func_key.index(':') && func_names.is_a?(Array)
101
- func_names.each do |fname|
102
- @funcs[func_key+func_name] = handler
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[func_key] = handler
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 = self.class.struct(statement)
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
- parsed = parse(statement, cache)
122
- return self.class.expr_eval(parsed, @vars, @funcs)
123
- end
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
- # retrieve the right-most operand
173
- # lptr = [opB, [opA, left, right], [opC, left, right]]
174
- # frag = [opC, left, right]
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
- if frag[0] == :op
182
- oop = frag[1]
183
- p0 = OP_PRECEDENCE[oop]
184
- p1 = OP_PRECEDENCE[nop]
185
- errs "op_precedence: #{oop} (#{p0}) vs #{nop} (#{p1})", 0
186
-
187
- if p0 > p1
188
- # preserve previous fragment and make as left term of new op
189
- # [opA, left, right] ==> [opB, [opA, left, right], nil]
190
- nfrag = [:op, nop, topfrag.clone, nil]
191
- lptr[1] = nop
192
- lptr[2] = nfrag[2]
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
- elsif term
306
- ret = :term
307
- ptr << term_val(term, term_state == :quote)
308
- errs "_transition: term <-- #{ptr.inspect}", 0
309
- term = nil
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
- quote_delim = nil
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
- state.pop
387
- ptr = stack[-1]
388
- ptr << map
389
- elsif tok == '?'
390
- # mark the position
391
- if term
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
- last = ptr.pop
398
- ptr << :tern
399
- ptr << last
129
+ ns += ':'
400
130
  end
401
131
 
402
- state << :tern
403
- elsif tok == ':'
404
- errs ": #{state[-1]} | ptr=#{ptr.inspect}", 5
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
- elsif tok == ')'
445
- last_state = _transition.call(state[-1])
446
- #while state[-1] != :fnop && state[-1] != :nest
447
- # last_state = _transition.call(state[-1])
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
- # @todo more appropriate way to push stack? otherwise fn(1 + 2 + 3) gets 1 too many :op
487
- state << :op if state[-1] != :op
488
- _op_push.call(tok)
489
- elsif tok != ''
490
- # white space; close out state
491
- if tok.strip == ''
492
- _transition.call(state[-1]) if term
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
- if !term
497
- term = tok
498
- else
499
- term += tok
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
- # look-ahead to build variable reference
503
- ntok = toks[i]
504
- while ntok
505
- #if ntok != ',' && ntok != ':' && ntok != '(' && ntok != ')' && ntok != '}' && ntok != '{' && !ntok.match(REGEX_OPERATORS)
506
- if ntok.match(REGEX_VARNAME)
507
- if ntok != ' ' && ntok != "\t"
508
- term += ntok
509
- end
510
- i += 1
511
- ntok = toks[i]
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
- # @todo validate completeness of state (e.g. no omitted parenthesis)
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
- def self.inject_into(ptr, frag)
530
- if !ptr[-1]
531
- ptr << frag
532
- else
533
- if ptr[-1].is_a?(Array)
534
- if ptr[-1][0] == :op
535
- op_insert(ptr[-1], frag)
536
- end
537
- # @todo is there an else?
538
- end
539
- # @todo is there an else?
540
- end
541
- end
542
-
543
- # Takes the output of {@see struct} and evaluates static expressions to speed up
544
- # {@see expr_eval}.
545
- # @param Object struct The structure to compact.
546
- # @return Array `[struct, dynamic]`
547
- def self.struct_compact(struct)
548
- dyn = false
549
-
550
- if struct.is_a?(ExprArray) || struct.is_a?(ExprHash)
551
- # don't need to do anything -- add_term already compacts terms
552
- dyn = struct.dynamic
553
- elsif struct.is_a?(Array)
554
- # the only term that potentially is static is a logical or mathematical operation.
555
- # the operation will compact if all terms are static (e.g. can be evaluated now)
556
- if struct[0].is_a?(Symbol)
557
- if struct[0] == :op
558
- lterm = struct[2]
559
- if lterm.is_a?(Array)
560
- lstruct, ldyn = struct_compact(lterm)
561
- if !ldyn
562
- lterm = lstruct
563
- else
564
- lterm = nil
565
- end
566
- end
567
-
568
- rterm = struct[3]
569
- if rterm.is_a?(Array)
570
- rstruct, rdyn = struct_compact(rterm)
571
- if !rdyn
572
- rterm = rstruct
573
- else
574
- rterm = nil
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
- return [struct, false]
230
+
601
231
  end
602
-
603
- return [struct, dyn]
604
232
  end
605
-
606
- # @param Array expr An expression structure from @{see struct()}
607
- # @param Map vtable Map for variable references.
608
- # @param Map ftable Map for function calls.
609
- def self.expr_eval(expr, vtable, ftable)
610
- errs "expr_eval: #{expr.inspect}", 3
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
- elsif expr.is_a?(Array)
620
- if expr[0] && !expr[0].is_a?(Symbol)
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
- if handler && handler.respond_to?(fname)
675
- cp = []
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
- ret = ftable[ns].send(fname, *cp)
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
- ret = nil
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
- key += tok
263
+ ptr = nil
264
+ break
792
265
  end
793
-
794
- if check_key || !vparts[i]
795
- next if key == ''
796
- errs "var:check=#{key} (#{type})", 3
797
- # @todo if :arr but item doesn't respond to [], return nil?
798
- last_ptr = ptr
799
- if type == :arr
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
- end
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
- retval = nil
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 retval
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 self.restructure(struct)
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 self.errs(str, lvl = 5)
889
- return if lvl < $errs_write
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
- $errs_write = 10
895
- $errs_write_indent = 10
307
+ require_relative './expression-evaluator/lexer.rb'
308
+ require_relative './expression-evaluator/parser.rb'
309
+ require_relative './expression-evaluator/evaluator.rb'