delorean_lang 0.0.33
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +145 -0
- data/Rakefile +2 -0
- data/delorean.gemspec +21 -0
- data/lib/delorean/base.rb +54 -0
- data/lib/delorean/container.rb +40 -0
- data/lib/delorean/delorean.rb +3157 -0
- data/lib/delorean/delorean.treetop +172 -0
- data/lib/delorean/engine.rb +359 -0
- data/lib/delorean/error.rb +45 -0
- data/lib/delorean/functions.rb +134 -0
- data/lib/delorean/model.rb +25 -0
- data/lib/delorean/nodes.rb +401 -0
- data/lib/delorean/version.rb +3 -0
- data/lib/delorean_lang.rb +12 -0
- data/spec/dev_spec.rb +98 -0
- data/spec/eval_spec.rb +609 -0
- data/spec/func_spec.rb +192 -0
- data/spec/parse_spec.rb +688 -0
- data/spec/spec_helper.rb +100 -0
- metadata +138 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
grammar Delorean
|
2
|
+
rule line
|
3
|
+
f:formula sp? ('#' .*)? <Line>
|
4
|
+
end
|
5
|
+
|
6
|
+
rule formula
|
7
|
+
sp i:identifier sp? '=?' sp? e:expression <ParameterDefault>
|
8
|
+
/
|
9
|
+
sp i:identifier sp? '=?' <Parameter>
|
10
|
+
/
|
11
|
+
sp i:identifier sp? '=' sp? e:expression <Formula>
|
12
|
+
/
|
13
|
+
n:node_name ':' sp? mod:(m:node_name '::')? p:node_name <SubNode>
|
14
|
+
/
|
15
|
+
n:node_name ':' <BaseNode>
|
16
|
+
/
|
17
|
+
'import' sp n:node_name sp v:integer <Import>
|
18
|
+
end
|
19
|
+
|
20
|
+
rule node_name
|
21
|
+
[A-Z] [a-zA-Z0-9_]*
|
22
|
+
end
|
23
|
+
|
24
|
+
rule expression
|
25
|
+
op:unary_op sp? e:expression <UnOp>
|
26
|
+
/
|
27
|
+
'if' sp? v:expression sp?
|
28
|
+
'then' sp? e1:expression sp?
|
29
|
+
'else' sp? e2:expression <IfElse>
|
30
|
+
/
|
31
|
+
v:value sp? op:binary_op sp? e:expression <BinOp>
|
32
|
+
/
|
33
|
+
value
|
34
|
+
end
|
35
|
+
|
36
|
+
rule list_expr
|
37
|
+
'[]' <ListExpr>
|
38
|
+
/
|
39
|
+
'[' sp? e2:expression sp
|
40
|
+
'for' sp i:identifier sp 'in' sp e1:expression sp?
|
41
|
+
ifexp:('if' sp e3:expression sp?)?
|
42
|
+
']' <ListComprehension>
|
43
|
+
/
|
44
|
+
'[' sp? args:fn_args sp? ']' <ListExpr>
|
45
|
+
end
|
46
|
+
|
47
|
+
rule hash_expr
|
48
|
+
'{}' <HashExpr>
|
49
|
+
/
|
50
|
+
'{' sp? args:hash_args sp? '}' <HashExpr>
|
51
|
+
end
|
52
|
+
|
53
|
+
# NOTE: some operations such as << have side-effects (e.g. on
|
54
|
+
# Arrays). So, be cautious about which opertaions are added.
|
55
|
+
rule binary_op
|
56
|
+
'+' / '-' / '*' / '/' / '%' / '==' / '!=' / '>=' / '<=' / '>' / '<' / '&&' / '||'
|
57
|
+
end
|
58
|
+
|
59
|
+
rule unary_op
|
60
|
+
'!' / '-'
|
61
|
+
end
|
62
|
+
|
63
|
+
rule value
|
64
|
+
number /
|
65
|
+
string /
|
66
|
+
boolean /
|
67
|
+
nil_val /
|
68
|
+
script_call /
|
69
|
+
fn /
|
70
|
+
model_fn_getattr /
|
71
|
+
model_fn /
|
72
|
+
node_getattr /
|
73
|
+
getattr /
|
74
|
+
list_expr /
|
75
|
+
hash_expr /
|
76
|
+
'(' sp? e:expression sp? ')' <Expr>
|
77
|
+
end
|
78
|
+
|
79
|
+
# built-in functions
|
80
|
+
rule fn
|
81
|
+
fn:fn_name '(' sp? ')' <Fn>
|
82
|
+
/
|
83
|
+
fn:fn_name '(' sp? args:fn_args sp? ')' <Fn>
|
84
|
+
end
|
85
|
+
|
86
|
+
rule fn_args
|
87
|
+
arg0:expression args_rest:(sp? ',' sp? args:fn_args?)? <FnArgs>
|
88
|
+
end
|
89
|
+
|
90
|
+
rule model_fn
|
91
|
+
m:model_name '.' fn:identifier '(' sp? ')' <ModelFn>
|
92
|
+
/
|
93
|
+
m:model_name '.' fn:identifier '(' sp? args:fn_args sp? ')' <ModelFn>
|
94
|
+
end
|
95
|
+
|
96
|
+
rule model_name
|
97
|
+
class_name '::' model_name
|
98
|
+
/
|
99
|
+
class_name
|
100
|
+
end
|
101
|
+
|
102
|
+
rule fn_name
|
103
|
+
[A-Z] [A-Z0-9]*
|
104
|
+
end
|
105
|
+
|
106
|
+
rule class_name
|
107
|
+
[A-Z] [a-zA-Z0-9]*
|
108
|
+
end
|
109
|
+
|
110
|
+
# script calling
|
111
|
+
rule script_call
|
112
|
+
'@' mod:(m:node_name '::')? c:class_name '(' sp? al:kw_args sp? ')' <ScriptCallNode>
|
113
|
+
/
|
114
|
+
'@' i:identifier? '(' sp? al:kw_args sp? ')' <ScriptCall>
|
115
|
+
end
|
116
|
+
|
117
|
+
rule hash_args
|
118
|
+
e0:expression sp? ':' sp? e1:expression args_rest:(sp? ',' sp? al:hash_args?)? <HashArgs>
|
119
|
+
end
|
120
|
+
|
121
|
+
rule kw_args
|
122
|
+
k:(i:identifier ':' sp?)? arg0:expression args_rest:(sp? ',' sp? al:kw_args)? <KwArgs>
|
123
|
+
end
|
124
|
+
|
125
|
+
rule node_getattr
|
126
|
+
n:node_name '.' i:identifier <NodeGetAttr>
|
127
|
+
end
|
128
|
+
|
129
|
+
rule model_fn_getattr
|
130
|
+
mfn:model_fn '.' i:identifier <ModelFnGetAttr>
|
131
|
+
end
|
132
|
+
|
133
|
+
rule getattr
|
134
|
+
i:identifier '.' ga:getattr <GetAttr>
|
135
|
+
/
|
136
|
+
identifier
|
137
|
+
end
|
138
|
+
|
139
|
+
rule number
|
140
|
+
decimal / integer
|
141
|
+
end
|
142
|
+
|
143
|
+
rule decimal
|
144
|
+
[0-9]+ '.' [0-9]+ <Literal>
|
145
|
+
end
|
146
|
+
|
147
|
+
rule integer
|
148
|
+
[0-9]+ <Literal>
|
149
|
+
end
|
150
|
+
|
151
|
+
rule identifier
|
152
|
+
[a-z] [a-zA-Z0-9_]* <Identifier>
|
153
|
+
end
|
154
|
+
|
155
|
+
rule boolean
|
156
|
+
'true' <Literal> / 'false' <Literal>
|
157
|
+
end
|
158
|
+
|
159
|
+
rule nil_val
|
160
|
+
'nil' <Literal>
|
161
|
+
end
|
162
|
+
|
163
|
+
rule sp
|
164
|
+
[\s]+
|
165
|
+
end
|
166
|
+
|
167
|
+
rule string
|
168
|
+
'"' ('\"' / !'"' .)* '"' <String>
|
169
|
+
/
|
170
|
+
"'" [^']* "'" <String>
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,359 @@
|
|
1
|
+
require 'delorean/base'
|
2
|
+
|
3
|
+
module Delorean
|
4
|
+
SIG = "_SIG"
|
5
|
+
MOD = "DELOREAN__"
|
6
|
+
POST = "__D"
|
7
|
+
|
8
|
+
class Engine
|
9
|
+
attr_reader :last_node, :module_name, :line_no, :comp_set, :pm, :m, :imports
|
10
|
+
|
11
|
+
def initialize(module_name)
|
12
|
+
# name of current module
|
13
|
+
@module_name = module_name
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset
|
18
|
+
@m, @pm = nil, nil
|
19
|
+
@last_node, @node_attrs = nil, {}
|
20
|
+
@line_no, @multi_no = 0, nil
|
21
|
+
|
22
|
+
# set of comprehension vars
|
23
|
+
@comp_set = Set.new
|
24
|
+
|
25
|
+
# set of all params
|
26
|
+
@param_set = Set.new
|
27
|
+
|
28
|
+
@imports = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def curr_line
|
32
|
+
@multi_no || @line_no
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_import(sset, name, version)
|
36
|
+
err(ParseError, "No script set") unless sset
|
37
|
+
|
38
|
+
err(ParseError, "Module #{name} importing itself") if
|
39
|
+
name == module_name
|
40
|
+
|
41
|
+
begin
|
42
|
+
@imports[name] = sset.import(name, version)
|
43
|
+
rescue => exc
|
44
|
+
err(ImportError, exc.to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
@pm.const_set("#{MOD}#{name}", @imports[name].pm)
|
48
|
+
end
|
49
|
+
|
50
|
+
def gen_import(name, version)
|
51
|
+
@imports.merge!(@imports[name].imports)
|
52
|
+
|
53
|
+
@m.const_set("#{MOD}#{name}", @imports[name].m)
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_import_engine(name)
|
57
|
+
@imports[name] || err(ParseError, "#{name} not imported")
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check to see if node with given name is defined. flag tell the
|
61
|
+
# method about our expectation. flag=true means that we make sure
|
62
|
+
# that name is defined. flag=false is the opposite.
|
63
|
+
def parse_check_defined_node(name, flag)
|
64
|
+
isdef = @pm.constants.member? name.to_sym
|
65
|
+
|
66
|
+
if isdef != flag
|
67
|
+
isdef ? err(RedefinedError, "#{name} already defined") :
|
68
|
+
err(UndefinedError, "#{name} not defined yet")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def super_name(pname, mname)
|
73
|
+
mname ? "#{MOD}#{mname}::#{pname}" : pname
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_check_defined_mod_node(pname, mname)
|
77
|
+
engine = mname ? get_import_engine(mname) : self
|
78
|
+
engine.parse_check_defined_node(pname, true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_define_node(name, pname, mname=nil)
|
82
|
+
parse_check_defined_node(name, false)
|
83
|
+
parse_check_defined_mod_node(pname, mname) if pname
|
84
|
+
|
85
|
+
sname = pname ? super_name(pname, mname) : 'Object'
|
86
|
+
|
87
|
+
code = "class #{name} < #{sname}; end"
|
88
|
+
@pm.module_eval(code)
|
89
|
+
|
90
|
+
# latest defined node
|
91
|
+
@last_node = name
|
92
|
+
|
93
|
+
# mapping of node name to list of attrs it defines
|
94
|
+
@node_attrs[name] = []
|
95
|
+
end
|
96
|
+
|
97
|
+
# Parse-time check to see if attr is available. If not, error is
|
98
|
+
# raised.
|
99
|
+
def parse_call_attr(node_name, attr_name)
|
100
|
+
return [] if comp_set.member?(attr_name)
|
101
|
+
|
102
|
+
# get the class associated with node
|
103
|
+
klass = @pm.module_eval(node_name)
|
104
|
+
|
105
|
+
# puts attr_name, "#{attr_name}#{POST}".to_sym, klass.methods.inspect
|
106
|
+
|
107
|
+
begin
|
108
|
+
klass.send("#{attr_name}#{POST}".to_sym, [])
|
109
|
+
rescue NoMethodError
|
110
|
+
err(UndefinedError, "'#{attr_name}' not defined in #{node_name}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Parse-time check to see if attr is available on current node.
|
115
|
+
def parse_call_last_node_attr(attr_name)
|
116
|
+
err(ParseError, "Not inside a node") unless @last_node
|
117
|
+
parse_call_attr(@last_node, attr_name)
|
118
|
+
end
|
119
|
+
|
120
|
+
def parse_define_var(var_name)
|
121
|
+
err(RedefinedError,
|
122
|
+
"List comprehension can't redefine variable '#{var_name}'") if
|
123
|
+
comp_set.member? var_name
|
124
|
+
|
125
|
+
comp_set.add var_name
|
126
|
+
end
|
127
|
+
|
128
|
+
def parse_undef_var(var_name)
|
129
|
+
err(ParseError, "internal error") unless comp_set.member? var_name
|
130
|
+
comp_set.delete var_name
|
131
|
+
end
|
132
|
+
|
133
|
+
# parse-time attr definition
|
134
|
+
def parse_define_attr(name, spec)
|
135
|
+
err(ParseError, "Can't define '#{name}' outside a node") unless
|
136
|
+
@last_node
|
137
|
+
|
138
|
+
err(RedefinedError, "Can't redefine '#{name}' in node #{@last_node}") if
|
139
|
+
@node_attrs[@last_node].member? name
|
140
|
+
|
141
|
+
@node_attrs[@last_node] << name
|
142
|
+
|
143
|
+
checks = spec.map { |a|
|
144
|
+
n = a.index('.') ? a : (@last_node + "." + a)
|
145
|
+
"_x.member?('#{n}') ? raise('#{n}') : #{a}#{POST}(_x + ['#{n}'])"
|
146
|
+
}.join(';')
|
147
|
+
|
148
|
+
code = "class #{@last_node}; def self.#{name}#{POST}(_x); #{checks}; end; end"
|
149
|
+
|
150
|
+
# pp code
|
151
|
+
|
152
|
+
@pm.module_eval(code)
|
153
|
+
|
154
|
+
begin
|
155
|
+
parse_call_attr(@last_node, name)
|
156
|
+
rescue RuntimeError
|
157
|
+
err(RecursionError, "'#{name}' is recursive")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse_define_param(name, spec)
|
162
|
+
parse_define_attr(name, spec)
|
163
|
+
@param_set.add(name)
|
164
|
+
end
|
165
|
+
|
166
|
+
def parse_class(class_name)
|
167
|
+
begin
|
168
|
+
# need the runtime module here (@m) since we need to
|
169
|
+
# introspect methods/attrs.
|
170
|
+
klass = @m.module_eval(class_name)
|
171
|
+
rescue NoMethodError, NameError
|
172
|
+
err(UndefinedError, "Can't find class: #{class_name}")
|
173
|
+
end
|
174
|
+
|
175
|
+
err(UndefinedError, "Access to non-class: #{class_name}") unless
|
176
|
+
klass.instance_of?(Class)
|
177
|
+
|
178
|
+
klass
|
179
|
+
end
|
180
|
+
|
181
|
+
def err(exc, msg)
|
182
|
+
raise exc.new(msg, @module_name, curr_line)
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_check_call_fn(fn, argcount, class_name=nil)
|
186
|
+
klass = class_name ? parse_class(class_name) : (@m::BaseClass)
|
187
|
+
|
188
|
+
err(UndefinedFunctionError, "Function #{fn} not found") unless
|
189
|
+
klass.methods.member? fn.to_sym
|
190
|
+
|
191
|
+
# signature methods must be named FUNCTION_NAME_SIG
|
192
|
+
sig = "#{fn}#{SIG}".upcase.to_sym
|
193
|
+
|
194
|
+
err(UndefinedFunctionError, "Signature #{sig} not found") unless
|
195
|
+
klass.constants.member? sig
|
196
|
+
|
197
|
+
min, max = klass.const_get(sig)
|
198
|
+
|
199
|
+
err(BadCallError, "Too many args to #{fn} (#{argcount} > #{max})") if
|
200
|
+
argcount > max
|
201
|
+
|
202
|
+
err(BadCallError, "Too few args to #{fn} (#{argcount} < #{min})") if
|
203
|
+
argcount < min
|
204
|
+
end
|
205
|
+
|
206
|
+
def parser
|
207
|
+
@@parser ||= DeloreanParser.new
|
208
|
+
end
|
209
|
+
|
210
|
+
def generate(t, sset=nil)
|
211
|
+
t.check(self, sset)
|
212
|
+
|
213
|
+
# generate ruby code
|
214
|
+
gen = t.rewrite(self)
|
215
|
+
|
216
|
+
# puts gen
|
217
|
+
|
218
|
+
begin
|
219
|
+
# evaluate generated code in @m
|
220
|
+
@m.module_eval(gen, "#{MOD}#{module_name}", curr_line)
|
221
|
+
rescue => exc
|
222
|
+
# bad ruby code generated, shoudn't happen
|
223
|
+
err(ParseError, "codegen error: " + exc.message)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse(source, sset=nil)
|
228
|
+
raise "can't call parse again without reset" if @pm
|
229
|
+
|
230
|
+
# @m module is used at runtime for code evaluation. @pm module
|
231
|
+
# is only used during parsing to check for errors.
|
232
|
+
@m, @pm = BaseModule.clone, Module.new
|
233
|
+
|
234
|
+
multi_line, @multi_no = nil, nil
|
235
|
+
|
236
|
+
source.each_line do |line|
|
237
|
+
@line_no += 1
|
238
|
+
|
239
|
+
# skip comments
|
240
|
+
next if line.match(/^\s*\#/)
|
241
|
+
|
242
|
+
# remove trailing blanks
|
243
|
+
line.rstrip!
|
244
|
+
|
245
|
+
next if line.length == 0
|
246
|
+
|
247
|
+
if multi_line
|
248
|
+
# Inside a multiline and next line doesn't look like a
|
249
|
+
# continuation => syntax error.
|
250
|
+
err(ParseError, "syntax error") unless line =~ /^\s+/
|
251
|
+
|
252
|
+
multi_line += line
|
253
|
+
t = parser.parse(multi_line)
|
254
|
+
|
255
|
+
if t
|
256
|
+
multi_line, @multi_no = nil, nil
|
257
|
+
generate(t, sset)
|
258
|
+
end
|
259
|
+
|
260
|
+
else
|
261
|
+
t = parser.parse(line)
|
262
|
+
|
263
|
+
if !t
|
264
|
+
err(ParseError, "syntax error") unless line =~ /^\s+/
|
265
|
+
|
266
|
+
multi_line = line
|
267
|
+
@multi_no = @line_no
|
268
|
+
else
|
269
|
+
generate(t, sset)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# left over multi_line
|
275
|
+
err(ParseError, "syntax error") if multi_line
|
276
|
+
end
|
277
|
+
|
278
|
+
######################################################################
|
279
|
+
# Script development/testing
|
280
|
+
######################################################################
|
281
|
+
|
282
|
+
# enumerate all nodes
|
283
|
+
def enumerate_nodes
|
284
|
+
n = SortedSet.new
|
285
|
+
@node_attrs.keys.each {|k| n.add(k) }
|
286
|
+
n
|
287
|
+
end
|
288
|
+
|
289
|
+
# enumerate qualified list of all attrs
|
290
|
+
def enumerate_attrs
|
291
|
+
enumerate_attrs_by_node(nil)
|
292
|
+
end
|
293
|
+
|
294
|
+
# enumerate qualified list of attrs by node (or all if nil is passed in)
|
295
|
+
def enumerate_attrs_by_node(node)
|
296
|
+
@node_attrs.keys.inject({}) { |h, n|
|
297
|
+
klass = @m.module_eval(n)
|
298
|
+
h[n] = klass.methods.map(&:to_s).select {|x| x.end_with?(POST)}.map {|x|
|
299
|
+
x.sub(/#{POST}$/, '')
|
300
|
+
} if node == n || node.nil?
|
301
|
+
h
|
302
|
+
}
|
303
|
+
end
|
304
|
+
|
305
|
+
# enumerate all params
|
306
|
+
def enumerate_params
|
307
|
+
@param_set
|
308
|
+
end
|
309
|
+
|
310
|
+
# enumerate params by a single node
|
311
|
+
def enumerate_params_by_node(node)
|
312
|
+
attrs = enumerate_attrs_by_node(node)
|
313
|
+
ps = Set.new
|
314
|
+
attrs.each_value {|v| v.map {|p| ps.add(p) if @param_set.include?(p.to_s)}}
|
315
|
+
ps
|
316
|
+
end
|
317
|
+
|
318
|
+
######################################################################
|
319
|
+
# Runtime
|
320
|
+
######################################################################
|
321
|
+
|
322
|
+
def evaluate(node, attr, params={})
|
323
|
+
evaluate_attrs(node, [attr], params)[0]
|
324
|
+
end
|
325
|
+
|
326
|
+
def evaluate_attrs(node, attrs, params={})
|
327
|
+
raise "bad node '#{node}'" unless node =~ /^[A-Z][a-zA-Z0-9_]*$/
|
328
|
+
|
329
|
+
begin
|
330
|
+
klass = @m.module_eval(node)
|
331
|
+
rescue NameError
|
332
|
+
err(UndefinedNodeError, "node #{node} is undefined")
|
333
|
+
end
|
334
|
+
|
335
|
+
params[:_engine] = self
|
336
|
+
|
337
|
+
attrs.map {|attr|
|
338
|
+
raise "bad attribute '#{attr}'" unless attr =~ /^[a-z][A-Za-z0-9_]*$/
|
339
|
+
klass.send("#{attr}#{POST}".to_sym, params)
|
340
|
+
}
|
341
|
+
end
|
342
|
+
|
343
|
+
# FIXME: should be renamed to grok_runtime_exception so as to not
|
344
|
+
# be confused with other parse_* calls which occur at parse time.
|
345
|
+
def parse_runtime_exception(exc)
|
346
|
+
# parse out the delorean-related backtrace records
|
347
|
+
bt = exc.backtrace.map{ |x|
|
348
|
+
x.match(/^#{MOD}(.+?):(\d+)(|:in `(.+)')$/);
|
349
|
+
$1 && [$1, $2.to_i, $4.sub(/#{POST}$/, '')]
|
350
|
+
}.reject(&:!)
|
351
|
+
|
352
|
+
[exc.message, bt]
|
353
|
+
end
|
354
|
+
|
355
|
+
######################################################################
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|