delorean_lang 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitlab-ci.yml +1 -2
- data/.rubocop.yml +45 -5
- data/.rubocop_todo.yml +1 -634
- data/Gemfile +3 -1
- data/README.md +22 -0
- data/Rakefile +3 -1
- data/delorean.gemspec +18 -17
- data/lib/delorean/abstract_container.rb +4 -2
- data/lib/delorean/base.rb +30 -27
- data/lib/delorean/cache.rb +2 -0
- data/lib/delorean/cache/adapters.rb +2 -0
- data/lib/delorean/cache/adapters/base.rb +2 -0
- data/lib/delorean/cache/adapters/ruby_cache.rb +5 -0
- data/lib/delorean/const.rb +5 -3
- data/lib/delorean/debug.rb +6 -5
- data/lib/delorean/delorean.rb +466 -147
- data/lib/delorean/delorean.treetop +13 -1
- data/lib/delorean/engine.rb +61 -50
- data/lib/delorean/error.rb +2 -1
- data/lib/delorean/model.rb +12 -9
- data/lib/delorean/nodes.rb +130 -67
- data/lib/delorean/ruby.rb +2 -0
- data/lib/delorean/ruby/whitelists.rb +2 -0
- data/lib/delorean/ruby/whitelists/base.rb +7 -3
- data/lib/delorean/ruby/whitelists/default.rb +6 -6
- data/lib/delorean/ruby/whitelists/empty.rb +3 -2
- data/lib/delorean/ruby/whitelists/matchers.rb +2 -0
- data/lib/delorean/ruby/whitelists/matchers/arguments.rb +2 -0
- data/lib/delorean/ruby/whitelists/matchers/method.rb +5 -2
- data/lib/delorean/ruby/whitelists/whitelist_error.rb +2 -0
- data/lib/delorean/version.rb +3 -1
- data/lib/delorean_lang.rb +3 -1
- data/spec/cache_spec.rb +4 -2
- data/spec/dev_spec.rb +68 -69
- data/spec/eval_spec.rb +824 -729
- data/spec/func_spec.rb +172 -176
- data/spec/parse_spec.rb +516 -522
- data/spec/ruby/whitelist_spec.rb +6 -3
- data/spec/spec_helper.rb +26 -23
- metadata +27 -27
@@ -10,17 +10,28 @@ grammar Delorean
|
|
10
10
|
/
|
11
11
|
sp4 i:identifier sp? '=' sp? e:expression <Formula>
|
12
12
|
/
|
13
|
+
n:class_name ':' sp? mod:(m:class_name_import_nested) <SubNodeNested> # FIXME: requires to be above SubNode statement, otherwise doesn't work
|
14
|
+
/
|
13
15
|
n:class_name ':' sp? mod:(m:class_name '::')? p:class_name <SubNode>
|
14
16
|
/
|
15
17
|
n:class_name ':' <BaseNode>
|
16
18
|
/
|
17
|
-
'import' sp n:
|
19
|
+
'import' sp n:class_name_import <Import>
|
18
20
|
end
|
19
21
|
|
20
22
|
rule class_name
|
21
23
|
[A-Z] [a-zA-Z0-9_]*
|
22
24
|
end
|
23
25
|
|
26
|
+
rule class_name_import
|
27
|
+
[A-Z] [a-zA-Z0-9_]* ('::' [A-Z] [a-zA-Z0-9_]*)*
|
28
|
+
end
|
29
|
+
|
30
|
+
# FIXME: Hacky way to skip Module::Node cases, so only Module::NestedModule::Node will pass
|
31
|
+
rule class_name_import_nested
|
32
|
+
[A-Z] [a-zA-Z0-9_]* (('::' [A-Z] [a-zA-Z0-9_]*) 2..)
|
33
|
+
end
|
34
|
+
|
24
35
|
rule expression
|
25
36
|
'ERR(' sp? args:fn_args? sp? ')' <ErrorOp>
|
26
37
|
/
|
@@ -119,6 +130,7 @@ grammar Delorean
|
|
119
130
|
list_expr /
|
120
131
|
set_expr /
|
121
132
|
hash_expr /
|
133
|
+
c:class_name_import <NodeAsValueNested> / # FIXME: requires to be above NodeAsValue statement, otherwise doesn't work
|
122
134
|
mod:(m:class_name '::')? c:class_name <NodeAsValue> /
|
123
135
|
'(' sp? e:expression sp? ')' <Expr>
|
124
136
|
end
|
data/lib/delorean/engine.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delorean/const'
|
2
4
|
require 'delorean/base'
|
3
5
|
require 'set'
|
@@ -6,9 +8,9 @@ require 'pp'
|
|
6
8
|
module Delorean
|
7
9
|
class Engine
|
8
10
|
attr_reader :last_node, :module_name, :line_no,
|
9
|
-
|
11
|
+
:comp_set, :pm, :m, :imports, :sset
|
10
12
|
|
11
|
-
def initialize(module_name, sset=nil)
|
13
|
+
def initialize(module_name, sset = nil)
|
12
14
|
# name of current module
|
13
15
|
@module_name = module_name
|
14
16
|
@sset = sset
|
@@ -16,9 +18,12 @@ module Delorean
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def reset
|
19
|
-
@m
|
20
|
-
@
|
21
|
-
@
|
21
|
+
@m = nil
|
22
|
+
@pm = nil
|
23
|
+
@last_node = nil
|
24
|
+
@node_attrs = {}
|
25
|
+
@line_no = 0
|
26
|
+
@multi_no = nil
|
22
27
|
|
23
28
|
# set of comprehension vars
|
24
29
|
@comp_set = Set.new
|
@@ -41,24 +46,24 @@ module Delorean
|
|
41
46
|
end
|
42
47
|
|
43
48
|
def parse_import(name)
|
44
|
-
err(ParseError,
|
49
|
+
err(ParseError, 'No script set') unless sset
|
45
50
|
|
46
51
|
err(ParseError, "Module #{name} importing itself") if
|
47
52
|
name == module_name
|
48
53
|
|
49
54
|
begin
|
50
55
|
@imports[name] = sset.get_engine(name)
|
51
|
-
rescue => exc
|
56
|
+
rescue StandardError => exc
|
52
57
|
err(ImportError, exc.to_s)
|
53
58
|
end
|
54
59
|
|
55
|
-
@pm.const_set("#{MOD}#{name}", @imports[name].pm)
|
60
|
+
@pm.const_set("#{MOD}#{name.gsub('::', '__')}", @imports[name].pm)
|
56
61
|
end
|
57
62
|
|
58
63
|
def gen_import(name)
|
59
64
|
@imports.merge!(@imports[name].imports)
|
60
65
|
|
61
|
-
@m.const_set("#{MOD}#{name}", @imports[name].m)
|
66
|
+
@m.const_set("#{MOD}#{name.gsub('::', '__')}", @imports[name].m)
|
62
67
|
end
|
63
68
|
|
64
69
|
def get_import_engine(name)
|
@@ -66,7 +71,7 @@ module Delorean
|
|
66
71
|
@imports[name]
|
67
72
|
end
|
68
73
|
|
69
|
-
def
|
74
|
+
def node_defined?(name)
|
70
75
|
@pm.constants.member? name.to_sym
|
71
76
|
end
|
72
77
|
|
@@ -74,7 +79,7 @@ module Delorean
|
|
74
79
|
# method about our expectation. flag=true means that we make sure
|
75
80
|
# that name is defined. flag=false is the opposite.
|
76
81
|
def parse_check_defined_node(name, flag)
|
77
|
-
isdef =
|
82
|
+
isdef = node_defined?(name)
|
78
83
|
|
79
84
|
if isdef != flag
|
80
85
|
isdef ? err(RedefinedError, "#{name} already defined") :
|
@@ -83,7 +88,7 @@ module Delorean
|
|
83
88
|
end
|
84
89
|
|
85
90
|
def super_name(pname, mname)
|
86
|
-
mname ? "#{MOD}#{mname}::#{pname}" : pname
|
91
|
+
mname ? "#{MOD}#{mname.gsub('::', '__')}::#{pname}" : pname
|
87
92
|
end
|
88
93
|
|
89
94
|
def parse_check_defined_mod_node(pname, mname)
|
@@ -91,13 +96,15 @@ module Delorean
|
|
91
96
|
engine.parse_check_defined_node(pname, true)
|
92
97
|
end
|
93
98
|
|
94
|
-
def parse_define_node(name, pname, mname=nil)
|
99
|
+
def parse_define_node(name, pname, mname = nil)
|
95
100
|
parse_check_defined_node(name, false)
|
96
101
|
parse_check_defined_mod_node(pname, mname) if pname
|
97
102
|
|
98
103
|
sname = pname ? super_name(pname, mname) : 'Object'
|
99
104
|
|
100
|
-
@pm.module_eval
|
105
|
+
@pm.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
106
|
+
class #{name} < #{sname}; end
|
107
|
+
RUBY
|
101
108
|
|
102
109
|
# latest defined node
|
103
110
|
@last_node = name
|
@@ -125,20 +132,21 @@ module Delorean
|
|
125
132
|
|
126
133
|
# Parse-time check to see if attr is available on current node.
|
127
134
|
def parse_call_last_node_attr(attr_name)
|
128
|
-
err(ParseError,
|
135
|
+
err(ParseError, 'Not inside a node') unless @last_node
|
129
136
|
parse_call_attr(@last_node, attr_name)
|
130
137
|
end
|
131
138
|
|
132
139
|
def parse_define_var(var_name)
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
if comp_set.member? var_name
|
141
|
+
err(RedefinedError,
|
142
|
+
"List comprehension can't redefine variable '#{var_name}'")
|
143
|
+
end
|
136
144
|
|
137
145
|
comp_set.add var_name
|
138
146
|
end
|
139
147
|
|
140
148
|
def parse_undef_var(var_name)
|
141
|
-
err(ParseError,
|
149
|
+
err(ParseError, 'internal error') unless comp_set.member? var_name
|
142
150
|
comp_set.delete var_name
|
143
151
|
end
|
144
152
|
|
@@ -152,10 +160,10 @@ module Delorean
|
|
152
160
|
|
153
161
|
@node_attrs[@last_node] << name
|
154
162
|
|
155
|
-
checks = spec.map
|
163
|
+
checks = spec.map do |a|
|
156
164
|
n = a.index('.') ? a : "#{@last_node}.#{a}"
|
157
165
|
"_x.member?('#{n}') ? raise('#{n}') : #{a}#{POST}(_x + ['#{n}'])"
|
158
|
-
|
166
|
+
end.join(';')
|
159
167
|
|
160
168
|
code =
|
161
169
|
"class #{@last_node}; def self.#{name}#{POST}(_x); #{checks}; end; end"
|
@@ -195,7 +203,7 @@ module Delorean
|
|
195
203
|
raise exc.new(msg, @module_name, curr_line)
|
196
204
|
end
|
197
205
|
|
198
|
-
def parse_check_call_fn(fn, argcount, class_name=nil)
|
206
|
+
def parse_check_call_fn(fn, argcount, class_name = nil)
|
199
207
|
klass = case class_name
|
200
208
|
when nil
|
201
209
|
@m::BaseClass
|
@@ -234,7 +242,7 @@ module Delorean
|
|
234
242
|
# generate ruby code
|
235
243
|
gen = t.rewrite(self)
|
236
244
|
rescue RuntimeError => exc
|
237
|
-
err(ParseError,
|
245
|
+
err(ParseError, 'codegen error: ' + exc.message)
|
238
246
|
end
|
239
247
|
|
240
248
|
# puts gen
|
@@ -242,9 +250,9 @@ module Delorean
|
|
242
250
|
begin
|
243
251
|
# evaluate generated code in @m
|
244
252
|
@m.module_eval(gen, "#{MOD}#{module_name}", curr_line)
|
245
|
-
rescue => exc
|
253
|
+
rescue StandardError => exc
|
246
254
|
# bad ruby code generated, shoudn't happen
|
247
|
-
err(ParseError,
|
255
|
+
err(ParseError, 'codegen error: ' + exc.message)
|
248
256
|
end
|
249
257
|
end
|
250
258
|
|
@@ -253,20 +261,22 @@ module Delorean
|
|
253
261
|
|
254
262
|
# @m module is used at runtime for code evaluation. @pm module
|
255
263
|
# is only used during parsing to check for errors.
|
256
|
-
@m
|
264
|
+
@m = BaseModule.clone
|
265
|
+
@pm = Module.new
|
257
266
|
|
258
|
-
multi_line
|
267
|
+
multi_line = nil
|
268
|
+
@multi_no = nil
|
259
269
|
|
260
270
|
source.each_line do |line|
|
261
271
|
@line_no += 1
|
262
272
|
|
263
273
|
# skip comments
|
264
|
-
next if line
|
274
|
+
next if line =~ /^\s*\#/
|
265
275
|
|
266
276
|
# remove trailing blanks
|
267
277
|
line.rstrip!
|
268
278
|
|
269
|
-
next if line.
|
279
|
+
next if line.empty?
|
270
280
|
|
271
281
|
if multi_line
|
272
282
|
# if line starts with >4 spaces, assume it's a multline
|
@@ -276,17 +286,18 @@ module Delorean
|
|
276
286
|
next
|
277
287
|
else
|
278
288
|
t = parser.parse(multi_line)
|
279
|
-
err(ParseError,
|
289
|
+
err(ParseError, 'syntax error') unless t
|
280
290
|
|
281
291
|
generate(t)
|
282
|
-
multi_line
|
292
|
+
multi_line = nil
|
293
|
+
@multi_no = nil
|
283
294
|
end
|
284
295
|
end
|
285
296
|
|
286
297
|
t = parser.parse(line)
|
287
298
|
|
288
299
|
if !t
|
289
|
-
err(ParseError,
|
300
|
+
err(ParseError, 'syntax error') unless line =~ /^\s+/
|
290
301
|
|
291
302
|
multi_line = line
|
292
303
|
@multi_no = @line_no
|
@@ -297,7 +308,7 @@ module Delorean
|
|
297
308
|
|
298
309
|
if multi_line
|
299
310
|
t = parser.parse(multi_line)
|
300
|
-
err(ParseError,
|
311
|
+
err(ParseError, 'syntax error') unless t
|
301
312
|
generate(t)
|
302
313
|
end
|
303
314
|
end
|
@@ -313,14 +324,14 @@ module Delorean
|
|
313
324
|
|
314
325
|
# enumerate qualified list of all attrs
|
315
326
|
def enumerate_attrs
|
316
|
-
@node_attrs.keys.each_with_object({})
|
327
|
+
@node_attrs.keys.each_with_object({}) do |node, h|
|
317
328
|
h[node] = enumerate_attrs_by_node(node)
|
318
|
-
|
329
|
+
end
|
319
330
|
end
|
320
331
|
|
321
332
|
# enumerate qualified list of attrs by node
|
322
333
|
def enumerate_attrs_by_node(node)
|
323
|
-
raise
|
334
|
+
raise 'bad node' unless node
|
324
335
|
|
325
336
|
begin
|
326
337
|
klass = node.is_a?(String) ? @m.module_eval(node) : node
|
@@ -331,11 +342,11 @@ module Delorean
|
|
331
342
|
|
332
343
|
raise "bad node class #{klass}" unless klass.is_a?(Class)
|
333
344
|
|
334
|
-
klass.methods.map(&:to_s).select
|
345
|
+
klass.methods.map(&:to_s).select do |x|
|
335
346
|
x.end_with?(POST)
|
336
|
-
|
347
|
+
end.map do |x|
|
337
348
|
x.sub(/#{POST}$/, '')
|
338
|
-
|
349
|
+
end
|
339
350
|
end
|
340
351
|
|
341
352
|
# enumerate all params
|
@@ -346,15 +357,15 @@ module Delorean
|
|
346
357
|
# enumerate params by a single node
|
347
358
|
def enumerate_params_by_node(node)
|
348
359
|
attrs = enumerate_attrs_by_node(node)
|
349
|
-
Set.new(
|
360
|
+
Set.new(attrs.select { |a| @param_set.include?(a) })
|
350
361
|
end
|
351
362
|
|
352
363
|
######################################################################
|
353
364
|
# Runtime
|
354
365
|
######################################################################
|
355
366
|
|
356
|
-
def evaluate(node, attrs, params={})
|
357
|
-
raise
|
367
|
+
def evaluate(node, attrs, params = {})
|
368
|
+
raise 'bad params' unless params.is_a?(Hash)
|
358
369
|
|
359
370
|
if node.is_a?(Class)
|
360
371
|
klass = node
|
@@ -373,29 +384,29 @@ module Delorean
|
|
373
384
|
type_arr = attrs.is_a?(Array)
|
374
385
|
attrs = [attrs] unless type_arr
|
375
386
|
|
376
|
-
res = attrs.map
|
387
|
+
res = attrs.map do |attr|
|
377
388
|
raise "bad attribute '#{attr}'" unless attr =~ /^[a-z][A-Za-z0-9_]*$/
|
389
|
+
|
378
390
|
klass.send("#{attr}#{POST}".to_sym, params)
|
379
|
-
|
391
|
+
end
|
380
392
|
type_arr ? res : res[0]
|
381
393
|
end
|
382
394
|
|
383
|
-
def eval_to_hash(node, attrs, params={})
|
395
|
+
def eval_to_hash(node, attrs, params = {})
|
384
396
|
res = evaluate(node, attrs, params)
|
385
397
|
Hash[* attrs.zip(res).flatten(1)]
|
386
398
|
end
|
387
399
|
|
388
400
|
def self.grok_runtime_exception(exc)
|
389
401
|
# parse out the delorean-related backtrace records
|
390
|
-
bt = exc.backtrace.map
|
391
|
-
x
|
402
|
+
bt = exc.backtrace.map do |x|
|
403
|
+
x =~ /^#{MOD}(.+?):(\d+)(|:in `(.+)')$/
|
392
404
|
$1 && [$1, $2.to_i, $4.sub(/#{POST}$/, '')]
|
393
|
-
|
405
|
+
end.reject(&:!)
|
394
406
|
|
395
|
-
{
|
407
|
+
{ 'error' => exc.message, 'backtrace' => bt }
|
396
408
|
end
|
397
409
|
|
398
410
|
######################################################################
|
399
|
-
|
400
411
|
end
|
401
412
|
end
|
data/lib/delorean/error.rb
CHANGED
data/lib/delorean/model.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delorean/const'
|
2
4
|
|
3
5
|
module Delorean
|
@@ -7,23 +9,24 @@ module Delorean
|
|
7
9
|
end
|
8
10
|
|
9
11
|
module ClassMethods
|
10
|
-
def delorean_fn(name, options = {}
|
12
|
+
def delorean_fn(name, options = {})
|
11
13
|
define_singleton_method(name) do |*args|
|
12
|
-
|
14
|
+
yield(*args)
|
13
15
|
end
|
14
16
|
|
15
17
|
sig = options[:sig]
|
16
18
|
|
17
|
-
raise
|
19
|
+
raise 'no signature' unless sig
|
18
20
|
|
19
21
|
if sig
|
20
22
|
sig = [sig, sig] if sig.is_a? Integer
|
21
|
-
raise
|
22
|
-
|
23
|
+
raise 'Bad signature' unless sig.is_a?(Array) && (sig.length == 2)
|
24
|
+
|
25
|
+
const_set(name.to_s.upcase + Delorean::SIG, sig)
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
|
-
# FIXME IDEA: we just make :cache an argument to delorean_fn.
|
29
|
+
# FIXME: IDEA: we just make :cache an argument to delorean_fn.
|
27
30
|
# That way, we don't need the cached_ flavors. It'll make all
|
28
31
|
# this code a lot simpler. We should also just add the :private
|
29
32
|
# mechanism here.
|
@@ -33,11 +36,11 @@ module Delorean
|
|
33
36
|
# values are ActiveRecord objects. Query results can be very
|
34
37
|
# large lists which we count as one item in the cache. Caching
|
35
38
|
# mechanism will result in large processes.
|
36
|
-
def cached_delorean_fn(name, options = {}
|
39
|
+
def cached_delorean_fn(name, options = {})
|
37
40
|
delorean_fn(name, options) do |*args|
|
38
41
|
delorean_cache_adapter = ::Delorean::Cache.adapter
|
39
42
|
# Check if caching should be performed
|
40
|
-
next
|
43
|
+
next yield(*args) unless delorean_cache_adapter.cache_item?(
|
41
44
|
klass: self, method_name: name, args: args
|
42
45
|
)
|
43
46
|
|
@@ -50,7 +53,7 @@ module Delorean
|
|
50
53
|
|
51
54
|
next cached_item if cached_item != :NF
|
52
55
|
|
53
|
-
res =
|
56
|
+
res = yield(*args)
|
54
57
|
|
55
58
|
delorean_cache_adapter.cache_item(
|
56
59
|
klass: self, cache_key: cache_key, item: res
|
data/lib/delorean/nodes.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Delorean
|
2
4
|
class SNode < Treetop::Runtime::SyntaxNode
|
3
5
|
end
|
@@ -6,6 +8,7 @@ module Delorean
|
|
6
8
|
def check(context, *a)
|
7
9
|
f.check(context, *a)
|
8
10
|
end
|
11
|
+
|
9
12
|
def rewrite(context)
|
10
13
|
f.rewrite(context)
|
11
14
|
end
|
@@ -24,11 +27,12 @@ module Delorean
|
|
24
27
|
# in _e. If not, to compute it we check for the value in _e
|
25
28
|
# (i.e. check for aname). Otherwise, we use the default value
|
26
29
|
# if any.
|
27
|
-
aname
|
30
|
+
aname = i.text_value
|
31
|
+
cname = context.last_node
|
28
32
|
not_found = defined?(e) ? e.rewrite(context) :
|
29
33
|
"raise UndefinedParamError, 'undefined parameter #{aname}'"
|
30
34
|
|
31
|
-
<<eos
|
35
|
+
<<eos
|
32
36
|
class #{cname}
|
33
37
|
def self.#{aname}#{POST}(_e)
|
34
38
|
_e[self.name+'.#{aname}'] ||= _e.fetch('#{aname}') { #{not_found} }
|
@@ -58,7 +62,7 @@ eos
|
|
58
62
|
|
59
63
|
def rewrite(context)
|
60
64
|
context.gen_import(n.text_value)
|
61
|
-
|
65
|
+
''
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
@@ -71,13 +75,13 @@ eos
|
|
71
75
|
def def_class(context, base_name)
|
72
76
|
# Nodes are simply translated to classes. Define our own
|
73
77
|
# self.name() since it's extremely slow in MRI 2.0.
|
74
|
-
"class #{n.text_value} < #{base_name}; "
|
75
|
-
"def self.module_name; '#{context.module_name}'; end;"
|
78
|
+
"class #{n.text_value} < #{base_name}; " \
|
79
|
+
"def self.module_name; '#{context.module_name}'; end;" \
|
76
80
|
"def self.name; '#{n.text_value}'; end; end"
|
77
81
|
end
|
78
82
|
|
79
83
|
def rewrite(context)
|
80
|
-
def_class(context,
|
84
|
+
def_class(context, 'BaseClass')
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
@@ -96,6 +100,27 @@ eos
|
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
103
|
+
class SubNodeNested < BaseNode
|
104
|
+
def check(context, *)
|
105
|
+
module_names = mod.m.text_value.split('::')
|
106
|
+
node_name = module_names.pop
|
107
|
+
mname = module_names.join('::') if module_names.any?
|
108
|
+
|
109
|
+
context.parse_define_node(n.text_value, node_name, mname)
|
110
|
+
end
|
111
|
+
|
112
|
+
def rewrite(context)
|
113
|
+
module_names = mod.m.text_value.split('::')
|
114
|
+
node_name = module_names.pop
|
115
|
+
mname = module_names.join('::') if module_names.any?
|
116
|
+
|
117
|
+
sname = context.super_name(node_name, mname)
|
118
|
+
|
119
|
+
# A sub-node (derived node) is just a subclass.
|
120
|
+
def_class(context, sname)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
99
124
|
class Formula < SNode
|
100
125
|
def check(context, *)
|
101
126
|
context.parse_define_attr(i.text_value, e.check(context))
|
@@ -106,12 +131,12 @@ eos
|
|
106
131
|
debug = Debug.debug_set.member?(dname)
|
107
132
|
|
108
133
|
# an attr is defined as a class function on the node class.
|
109
|
-
"class #{context.last_node}; "
|
134
|
+
"class #{context.last_node}; " \
|
110
135
|
"def self.#{i.text_value}#{POST}(_e); " +
|
111
|
-
(debug ?
|
136
|
+
(debug ? '_debug =' : '') +
|
112
137
|
"_e[self.name+'.#{i.text_value}'] ||= #{e.rewrite(context)};" +
|
113
138
|
(debug ? 'Delorean::Debug.log(_debug); _debug;' : '') +
|
114
|
-
|
139
|
+
'end; end;'
|
115
140
|
end
|
116
141
|
end
|
117
142
|
|
@@ -133,7 +158,7 @@ eos
|
|
133
158
|
end
|
134
159
|
|
135
160
|
def +(other)
|
136
|
-
|
161
|
+
to_s + other
|
137
162
|
end
|
138
163
|
|
139
164
|
def to_s
|
@@ -168,6 +193,37 @@ eos
|
|
168
193
|
end
|
169
194
|
end
|
170
195
|
|
196
|
+
class NodeAsValueNested < SNode
|
197
|
+
def check(context, *)
|
198
|
+
module_names = c.text_value.split('::')
|
199
|
+
node_name = module_names.pop
|
200
|
+
mname = module_names.join('::') if module_names.any?
|
201
|
+
|
202
|
+
begin
|
203
|
+
context.parse_check_defined_mod_node(node_name, mname)
|
204
|
+
rescue UndefinedError, ParseError
|
205
|
+
# Node is a non-Delorean ruby class
|
206
|
+
context.parse_class(text_value)
|
207
|
+
end
|
208
|
+
[]
|
209
|
+
end
|
210
|
+
|
211
|
+
def rewrite(context)
|
212
|
+
module_names = c.text_value.split('::')
|
213
|
+
node_name = module_names.pop
|
214
|
+
mname = module_names.join('::') if module_names.any?
|
215
|
+
|
216
|
+
begin
|
217
|
+
context.parse_check_defined_mod_node(node_name, mname)
|
218
|
+
context.super_name(node_name, mname)
|
219
|
+
rescue UndefinedError, ParseError
|
220
|
+
# FIXME: wrap the class name so Call will be able to tell it
|
221
|
+
# apart from a regular value.
|
222
|
+
ClassText.new(text_value)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
171
227
|
# unary operator
|
172
228
|
class UnOp < SNode
|
173
229
|
def check(context, *)
|
@@ -181,7 +237,8 @@ eos
|
|
181
237
|
|
182
238
|
class BinOp < SNode
|
183
239
|
def check(context, *)
|
184
|
-
vc
|
240
|
+
vc = v.check(context)
|
241
|
+
ec = e.check(context)
|
185
242
|
# returns list of attrs used in RHS and LHS
|
186
243
|
ec + vc
|
187
244
|
end
|
@@ -198,11 +255,13 @@ eos
|
|
198
255
|
# hacky, for backwards compatibility
|
199
256
|
class ErrorOp < SNode
|
200
257
|
def check(context, *)
|
201
|
-
args.text_value=='' ? [] : args.check(context)
|
258
|
+
args.text_value == '' ? [] : args.check(context)
|
202
259
|
end
|
203
260
|
|
204
261
|
def rewrite(context, *)
|
205
|
-
args.text_value!='' ?
|
262
|
+
args.text_value != '' ?
|
263
|
+
"_err(#{args.rewrite(context)})" :
|
264
|
+
'binding.pry; 0'
|
206
265
|
end
|
207
266
|
end
|
208
267
|
|
@@ -217,41 +276,41 @@ eos
|
|
217
276
|
end
|
218
277
|
|
219
278
|
class Literal < SNode
|
220
|
-
def check(
|
279
|
+
def check(_context, *)
|
221
280
|
[]
|
222
281
|
end
|
223
282
|
|
224
283
|
# Delorean literals have same syntax as Ruby
|
225
|
-
def rewrite(
|
284
|
+
def rewrite(_context)
|
226
285
|
text_value
|
227
286
|
end
|
228
287
|
end
|
229
288
|
|
230
289
|
# _ is self -- a naive implementation of "self" for now.
|
231
290
|
class Self < SNode
|
232
|
-
def check(
|
291
|
+
def check(_context, *)
|
233
292
|
[]
|
234
293
|
end
|
235
294
|
|
236
|
-
def rewrite(
|
237
|
-
|
295
|
+
def rewrite(_context)
|
296
|
+
'_sanitize_hash(_e)'
|
238
297
|
end
|
239
298
|
end
|
240
299
|
|
241
300
|
class Sup < SNode
|
242
|
-
def check(
|
301
|
+
def check(_context, *)
|
243
302
|
[]
|
244
303
|
end
|
245
304
|
|
246
|
-
def rewrite(
|
247
|
-
|
305
|
+
def rewrite(_context)
|
306
|
+
'superclass'
|
248
307
|
end
|
249
308
|
end
|
250
309
|
|
251
310
|
class IString < Literal
|
252
|
-
def rewrite(
|
311
|
+
def rewrite(_context)
|
253
312
|
# FIXME: hacky to just fail
|
254
|
-
raise
|
313
|
+
raise 'String interpolation not supported' if text_value =~ /\#\{.*\}/
|
255
314
|
|
256
315
|
# FIXME: syntax check?
|
257
316
|
text_value
|
@@ -259,7 +318,7 @@ eos
|
|
259
318
|
end
|
260
319
|
|
261
320
|
class DString < Literal
|
262
|
-
def rewrite(
|
321
|
+
def rewrite(_context)
|
263
322
|
# remove the quotes and requote. We don't want the likes of #{}
|
264
323
|
# evals to just pass through.
|
265
324
|
text_value[1..-2].inspect
|
@@ -277,7 +336,7 @@ eos
|
|
277
336
|
# class method calls. POST is used in mangling the attr names.
|
278
337
|
# _e is the environment. Comprehension vars (in comp_set) are
|
279
338
|
# not passed the env arg.
|
280
|
-
arg = context.comp_set.member?(text_value) ?
|
339
|
+
arg = context.comp_set.member?(text_value) ? '' : '(_e)'
|
281
340
|
text_value + POST + arg
|
282
341
|
end
|
283
342
|
end
|
@@ -315,11 +374,11 @@ eos
|
|
315
374
|
end
|
316
375
|
|
317
376
|
class GetAttr < SNode
|
318
|
-
def check(
|
377
|
+
def check(_context, *)
|
319
378
|
[]
|
320
379
|
end
|
321
380
|
|
322
|
-
def rewrite(
|
381
|
+
def rewrite(_context, vcode)
|
323
382
|
attr = i.text_value
|
324
383
|
attr = "'#{attr}'" unless attr =~ /\A[0-9]+\z/
|
325
384
|
"_get_attr(#{vcode}, #{attr}, _e)"
|
@@ -333,9 +392,11 @@ eos
|
|
333
392
|
|
334
393
|
def rewrite(context, vcode)
|
335
394
|
if al.text_value.empty?
|
336
|
-
args_str
|
395
|
+
args_str = ''
|
396
|
+
arg_count = 0
|
337
397
|
else
|
338
|
-
args_str
|
398
|
+
args_str = al.rewrite(context)
|
399
|
+
arg_count = al.arg_count
|
339
400
|
end
|
340
401
|
|
341
402
|
if vcode.is_a?(ClassText)
|
@@ -356,7 +417,7 @@ eos
|
|
356
417
|
|
357
418
|
def rewrite(context, node_name)
|
358
419
|
var = "_h#{context.hcount}"
|
359
|
-
res = al.text_value.empty? ?
|
420
|
+
res = al.text_value.empty? ? '' : al.rewrite(context, var)
|
360
421
|
"(#{var}={}; #{res}; _node_call(#{node_name}, _e, #{var}))"
|
361
422
|
end
|
362
423
|
end
|
@@ -375,7 +436,7 @@ eos
|
|
375
436
|
# element since it'll be "".
|
376
437
|
attrs.shift
|
377
438
|
|
378
|
-
attrs.inject(v.rewrite(context)) {|x, y| "_get_attr(#{x}, '#{y}', _e)"}
|
439
|
+
attrs.inject(v.rewrite(context)) { |x, y| "_get_attr(#{x}, '#{y}', _e)" }
|
379
440
|
end
|
380
441
|
end
|
381
442
|
|
@@ -389,7 +450,7 @@ eos
|
|
389
450
|
end
|
390
451
|
|
391
452
|
def rewrite(context)
|
392
|
-
rest =
|
453
|
+
rest = ', ' + args_rest.args.rewrite(context) if
|
393
454
|
defined?(args_rest.args) && !args_rest.args.text_value.empty?
|
394
455
|
|
395
456
|
[arg0.rewrite(context), rest].compact.sum
|
@@ -403,8 +464,9 @@ eos
|
|
403
464
|
|
404
465
|
class IfElse < SNode
|
405
466
|
def check(context, *)
|
406
|
-
vc
|
407
|
-
|
467
|
+
vc = v.check(context)
|
468
|
+
e1c = e1.check(context)
|
469
|
+
e2c = e2.check(context)
|
408
470
|
vc + e1c + e2c
|
409
471
|
end
|
410
472
|
|
@@ -420,7 +482,7 @@ eos
|
|
420
482
|
end
|
421
483
|
|
422
484
|
def rewrite(context)
|
423
|
-
|
485
|
+
'[' + (defined?(args) ? args.rewrite(context) : '') + ']'
|
424
486
|
end
|
425
487
|
end
|
426
488
|
|
@@ -434,7 +496,7 @@ eos
|
|
434
496
|
def rewrite(context)
|
435
497
|
arg0.rewrite(context) +
|
436
498
|
(defined?(args_rest.args) && !args_rest.args.text_value.empty? ?
|
437
|
-
|
499
|
+
', ' + args_rest.args.rewrite(context) : '')
|
438
500
|
end
|
439
501
|
end
|
440
502
|
|
@@ -442,19 +504,18 @@ eos
|
|
442
504
|
def check(context, *)
|
443
505
|
unpack_vars = args.check(context)
|
444
506
|
e1c = e1.check(context)
|
445
|
-
unpack_vars.each {|vname| context.parse_define_var(vname)}
|
507
|
+
unpack_vars.each { |vname| context.parse_define_var(vname) }
|
446
508
|
|
447
509
|
# need to check e2/e3 in a context where the comprehension var
|
448
510
|
# is defined.
|
449
511
|
e2c = e2.check(context)
|
450
512
|
e3c = defined?(ifexp.e3) ? ifexp.e3.check(context) : []
|
451
513
|
|
452
|
-
unpack_vars.each
|
453
|
-
|vname|
|
514
|
+
unpack_vars.each do |vname|
|
454
515
|
context.parse_undef_var(vname)
|
455
516
|
e2c.delete(vname)
|
456
517
|
e3c.delete(vname)
|
457
|
-
|
518
|
+
end
|
458
519
|
|
459
520
|
e1c + e2c + e3c
|
460
521
|
end
|
@@ -462,13 +523,13 @@ eos
|
|
462
523
|
def rewrite(context)
|
463
524
|
res = ["(#{e1.rewrite(context)})"]
|
464
525
|
unpack_vars = args.check(context)
|
465
|
-
unpack_vars.each {|vname| context.parse_define_var(vname)}
|
526
|
+
unpack_vars.each { |vname| context.parse_define_var(vname) }
|
466
527
|
args_str = args.rewrite(context)
|
467
528
|
|
468
529
|
res << ".select{|#{args_str}|(#{ifexp.e3.rewrite(context)})}" if
|
469
530
|
defined?(ifexp.e3)
|
470
531
|
res << ".map{|#{args_str}| (#{e2.rewrite(context)}) }"
|
471
|
-
unpack_vars.each {|vname| context.parse_undef_var(vname)}
|
532
|
+
unpack_vars.each { |vname| context.parse_undef_var(vname) }
|
472
533
|
res.sum
|
473
534
|
end
|
474
535
|
end
|
@@ -492,7 +553,7 @@ eos
|
|
492
553
|
def check(context, *)
|
493
554
|
unpack_vars = args.check(context)
|
494
555
|
e1c = e1.check(context)
|
495
|
-
unpack_vars.each {|vname| context.parse_define_var(vname)}
|
556
|
+
unpack_vars.each { |vname| context.parse_define_var(vname) }
|
496
557
|
|
497
558
|
# need to check el/er/ei in a context where the comprehension var
|
498
559
|
# is defined.
|
@@ -500,20 +561,19 @@ eos
|
|
500
561
|
erc = er.check(context)
|
501
562
|
eic = defined?(ifexp.ei) ? ifexp.ei.check(context) : []
|
502
563
|
|
503
|
-
unpack_vars.each
|
504
|
-
|vname|
|
564
|
+
unpack_vars.each do |vname|
|
505
565
|
context.parse_undef_var(vname)
|
506
566
|
elc.delete(vname)
|
507
567
|
erc.delete(vname)
|
508
568
|
eic.delete(vname)
|
509
|
-
|
569
|
+
end
|
510
570
|
e1c + elc + erc + eic
|
511
571
|
end
|
512
572
|
|
513
573
|
def rewrite(context)
|
514
574
|
res = ["(#{e1.rewrite(context)})"]
|
515
575
|
unpack_vars = args.check(context)
|
516
|
-
unpack_vars.each {|vname| context.parse_define_var(vname)}
|
576
|
+
unpack_vars.each { |vname| context.parse_define_var(vname) }
|
517
577
|
args_str = args.rewrite(context)
|
518
578
|
|
519
579
|
hid = @@comp_count += 1
|
@@ -523,10 +583,10 @@ eos
|
|
523
583
|
|
524
584
|
unpack_str = unpack_vars.count > 1 ? "(#{args_str})" : args_str
|
525
585
|
|
526
|
-
res << ".each_with_object({}){|#{unpack_str}, _h#{hid}| "
|
527
|
-
|
586
|
+
res << ".each_with_object({}){|#{unpack_str}, _h#{hid}| " \
|
587
|
+
"_h#{hid}[#{el.rewrite(context)}]=(#{er.rewrite(context)})}"
|
528
588
|
|
529
|
-
unpack_vars.each {|vname| context.parse_undef_var(vname)}
|
589
|
+
unpack_vars.each { |vname| context.parse_undef_var(vname) }
|
530
590
|
res.sum
|
531
591
|
end
|
532
592
|
end
|
@@ -537,7 +597,8 @@ eos
|
|
537
597
|
end
|
538
598
|
|
539
599
|
def rewrite(context)
|
540
|
-
return
|
600
|
+
return '{}' unless defined?(args)
|
601
|
+
|
541
602
|
var = "_h#{context.hcount}"
|
542
603
|
"(#{var}={}; " + args.rewrite(context, var) + "; #{var})"
|
543
604
|
end
|
@@ -545,14 +606,15 @@ eos
|
|
545
606
|
|
546
607
|
class KwArgs < SNode
|
547
608
|
def check(context, *)
|
548
|
-
[
|
549
|
-
|
550
|
-
|
551
|
-
|
609
|
+
[
|
610
|
+
arg0.check(context),
|
611
|
+
(ifexp.e3.check(context) if defined?(ifexp.e3)),
|
612
|
+
(args_rest.al.check(context) if
|
613
|
+
defined?(args_rest.al) && !args_rest.al.empty?)
|
552
614
|
].compact.sum
|
553
615
|
end
|
554
616
|
|
555
|
-
def rewrite(context, var, i=0)
|
617
|
+
def rewrite(context, var, i = 0)
|
556
618
|
arg0_rw = arg0.rewrite(context)
|
557
619
|
|
558
620
|
if defined?(splat)
|
@@ -564,7 +626,7 @@ eos
|
|
564
626
|
end
|
565
627
|
|
566
628
|
res += " if (#{ifexp.e3.rewrite(context)})" if defined?(ifexp.e3)
|
567
|
-
res +=
|
629
|
+
res += ';'
|
568
630
|
res += args_rest.al.rewrite(context, var, i) if
|
569
631
|
defined?(args_rest.al) && !args_rest.al.text_value.empty?
|
570
632
|
res
|
@@ -573,22 +635,23 @@ eos
|
|
573
635
|
|
574
636
|
class HashArgs < SNode
|
575
637
|
def check(context, *)
|
576
|
-
[
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
638
|
+
[
|
639
|
+
e0.check(context),
|
640
|
+
(e1.check(context) unless defined?(splat)),
|
641
|
+
(ifexp.e3.check(context) if defined?(ifexp.e3)),
|
642
|
+
(args_rest.al.check(context) if
|
643
|
+
defined?(args_rest.al) && !args_rest.al.empty?),
|
581
644
|
].compact.sum
|
582
645
|
end
|
583
646
|
|
584
647
|
def rewrite(context, var)
|
585
648
|
res = if defined?(splat)
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
649
|
+
"#{var}.merge!(#{e0.rewrite(context)})"
|
650
|
+
else
|
651
|
+
"#{var}[#{e0.rewrite(context)}]=(#{e1.rewrite(context)})"
|
652
|
+
end
|
590
653
|
res += " if (#{ifexp.e3.rewrite(context)})" if defined?(ifexp.e3)
|
591
|
-
res +=
|
654
|
+
res += ';'
|
592
655
|
res += args_rest.al.rewrite(context, var) if
|
593
656
|
defined?(args_rest.al) && !args_rest.al.text_value.empty?
|
594
657
|
res
|