delorean_lang 0.0.33

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,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