delorean_lang 0.0.43 → 0.1.00

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,6 +22,8 @@ grammar Delorean
22
22
  end
23
23
 
24
24
  rule expression
25
+ 'ERR(' sp? args:fn_args sp? ')' <ErrorOp>
26
+ /
25
27
  op:unary_op sp? e:expression <UnOp>
26
28
  /
27
29
  'if' sp? v:expression sp?
@@ -30,13 +32,27 @@ grammar Delorean
30
32
  /
31
33
  v:getattr_exp sp? op:binary_op sp? e:expression <BinOp>
32
34
  /
33
- v:getattr_exp sp? '[' sp? args:fn_args sp? ']' <IndexOp>
34
- /
35
35
  getattr_exp
36
36
  end
37
37
 
38
38
  rule getattr_exp
39
- v:value ga:(('.' identifier)*) <ExpGetAttr>
39
+ v:value dotted:dotted <GetattrExp>
40
+ /
41
+ value
42
+ end
43
+
44
+ rule dotted
45
+ d:dot_exp d_rest:dotted? <Dotted>
46
+ end
47
+
48
+ rule dot_exp
49
+ '[' sp? args:fn_args sp? ']' <IndexOp>
50
+ /
51
+ '(' sp? al:kw_args? sp? ')' <NodeCall>
52
+ /
53
+ '.' sp? i:identifier '(' sp? al:kw_args? sp? ')' <Call>
54
+ /
55
+ '.' sp? i:identifier <GetAttr>
40
56
  end
41
57
 
42
58
  rule list_expr
@@ -73,49 +89,20 @@ grammar Delorean
73
89
 
74
90
  rule value
75
91
  number /
76
- string /
77
- boolean /
92
+ string /
93
+ boolean /
78
94
  nil_val /
79
- script_call /
80
- fn /
81
- model_fn /
82
95
  identifier /
83
96
  list_expr /
84
97
  hash_expr /
85
- mod:(m:class_name '::')? c:class_name <NodeAsValue> /
98
+ mod:(m:class_name '::')? c:class_name <NodeAsValue> /
86
99
  '(' sp? e:expression sp? ')' <Expr>
87
100
  end
88
101
 
89
- # built-in functions
90
- rule fn
91
- fn:fn_name '(' sp? args:fn_args? sp? ')' <Fn>
92
- end
93
-
94
102
  rule fn_args
95
103
  arg0:expression args_rest:(sp? ',' sp? args:fn_args?)? <FnArgs>
96
104
  end
97
105
 
98
- rule model_fn
99
- m:model_name '.' fn:identifier '(' sp? ')' <ModelFn>
100
- /
101
- m:model_name '.' fn:identifier '(' sp? args:fn_args sp? ')' <ModelFn>
102
- end
103
-
104
- rule model_name
105
- class_name ('::' model_name)?
106
- end
107
-
108
- rule fn_name
109
- [A-Z] [A-Z0-9]*
110
- end
111
-
112
- # script calling
113
- rule script_call
114
- '@' mod:(m:class_name '::')? c:class_name '(' sp? al:kw_args sp? ')' <ScriptCallNode>
115
- /
116
- '@' i:identifier? '(' sp? al:kw_args sp? ')' <ScriptCall>
117
- end
118
-
119
106
  rule hash_args
120
107
  e0:expression sp? ':' sp? e1:expression args_rest:(sp? ',' sp? al:hash_args?)? <HashArgs>
121
108
  end
@@ -153,8 +140,8 @@ grammar Delorean
153
140
  end
154
141
 
155
142
  rule string
156
- '"' ('\"' / !'"' .)* '"' <String>
143
+ '"' ('\"' / !'"' .)* '"' <DString>
157
144
  /
158
- "'" [^']* "'" <String>
145
+ "'" [^']* "'" <DString>
159
146
  end
160
147
  end
@@ -54,11 +54,15 @@ module Delorean
54
54
  @imports[name] || err(ParseError, "#{name} not imported")
55
55
  end
56
56
 
57
- # Check to see if node with given name is defined. flag tell the
57
+ def is_node_defined(name)
58
+ @pm.constants.member? name.to_sym
59
+ end
60
+
61
+ # Check to see if node with given name is defined. flag tells the
58
62
  # method about our expectation. flag=true means that we make sure
59
63
  # that name is defined. flag=false is the opposite.
60
64
  def parse_check_defined_node(name, flag)
61
- isdef = @pm.constants.member? name.to_sym
65
+ isdef = is_node_defined(name)
62
66
 
63
67
  if isdef != flag
64
68
  isdef ? err(RedefinedError, "#{name} already defined") :
@@ -81,8 +85,7 @@ module Delorean
81
85
 
82
86
  sname = pname ? super_name(pname, mname) : 'Object'
83
87
 
84
- code = "class #{name} < #{sname}; end"
85
- @pm.module_eval(code)
88
+ @pm.module_eval("class #{name} < #{sname}; end")
86
89
 
87
90
  # latest defined node
88
91
  @last_node = name
@@ -118,15 +118,28 @@ eos
118
118
 
119
119
  class NodeAsValue < SNode
120
120
  def check(context, *)
121
+ node_name = c.text_value
121
122
  mname = mod.m.text_value if defined?(mod.m)
122
- context.parse_check_defined_mod_node(c.text_value, mname)
123
+ begin
124
+ context.parse_check_defined_mod_node(node_name, mname)
125
+ rescue UndefinedError, ParseError
126
+ # Node is a non-Delorean ruby class
127
+ context.parse_class(text_value)
128
+ end
123
129
  []
124
130
  end
125
131
 
126
132
  def rewrite(context)
127
133
  node_name = c.text_value
128
134
  mname = mod.m.text_value if defined?(mod.m)
129
- context.super_name(node_name, mname)
135
+ begin
136
+ context.parse_check_defined_mod_node(node_name, mname)
137
+ context.super_name(node_name, mname)
138
+ rescue UndefinedError, ParseError
139
+ # kind of hacky, we wrap the class name in a list so Call will
140
+ # be able to tell it apart from a regular value.
141
+ [text_value]
142
+ end
130
143
  end
131
144
  end
132
145
 
@@ -153,14 +166,24 @@ eos
153
166
  end
154
167
  end
155
168
 
169
+ # hacky, for backwards compatibility
170
+ class ErrorOp < SNode
171
+ def check(context, *)
172
+ args.check(context)
173
+ end
174
+
175
+ def rewrite(context, *)
176
+ "_err(#{args.rewrite(context)})"
177
+ end
178
+ end
179
+
156
180
  class IndexOp < SNode
157
181
  def check(context, *)
158
- vc, ac = v.check(context), args.check(context)
159
- ac + vc
182
+ args.check(context)
160
183
  end
161
184
 
162
- def rewrite(context)
163
- "_index(#{v.rewrite(context)}, [#{args.rewrite(context)}], _e)"
185
+ def rewrite(context, vcode)
186
+ "_index(#{vcode}, [#{args.rewrite(context)}], _e)"
164
187
  end
165
188
  end
166
189
 
@@ -175,7 +198,7 @@ eos
175
198
  end
176
199
  end
177
200
 
178
- class String < Literal
201
+ class DString < Literal
179
202
  def rewrite(context)
180
203
  # remove the quotes and requote. We don't want the likes of #{}
181
204
  # evals to just pass through.
@@ -199,33 +222,111 @@ eos
199
222
  end
200
223
  end
201
224
 
202
- class ExpGetAttr < SNode
225
+ ######################################################################
226
+
227
+ class GetattrExp < SNode
203
228
  def check(context, *)
204
229
  v.check(context)
230
+ dotted.check(context)
205
231
  end
206
232
 
207
233
  def rewrite(context)
208
- attr_list = ga.text_value.split('.')
234
+ vcode = v.rewrite(context)
235
+ dotted.rewrite(context, vcode)
236
+ end
237
+ end
209
238
 
210
- # If ga.text_value is not "", then we need to drop the 1st
211
- # element since it'll be "".
212
- attr_list.shift
239
+ class Dotted < SNode
240
+ def check(context, *)
241
+ d.check(context)
242
+ d_rest.check(context) unless d_rest.text_value.empty?
243
+ []
244
+ end
213
245
 
214
- attr_list.inject(v.rewrite(context)) {|x, y| "_get_attr(#{x}, '#{y}', _e)"}
246
+ def rewrite(context, vcode)
247
+ dcode = d.rewrite(context, vcode)
248
+
249
+ if d_rest.text_value.empty?
250
+ dcode
251
+ else
252
+ d_rest.rewrite(context, dcode)
253
+ end
254
+ end
255
+ end
256
+
257
+ class GetAttr < SNode
258
+ def check(context, *)
259
+ []
260
+ end
261
+
262
+ def rewrite(context, vcode)
263
+ "_get_attr(#{vcode}, '#{i.text_value}', _e)"
215
264
  end
216
265
  end
217
266
 
218
- class Fn < SNode
267
+ class Call < SNode
219
268
  def check(context, *)
220
- acount, res =
221
- defined?(args) ? [args.arg_count, args.check(context)] : [0, []]
269
+ al.check(context) unless al.text_value.empty?
270
+ []
271
+ end
222
272
 
223
- context.parse_check_call_fn(fn.text_value, acount)
224
- res
273
+ def rewrite(context, vcode)
274
+ args, kw = al.text_value.empty? ? [[], {}] : al.rewrite(context)
275
+
276
+ raise "Keyword arguments not supported" unless
277
+ kw.empty?
278
+
279
+ args_str = args.reverse.join(',')
280
+
281
+ if vcode.is_a? Array
282
+ # ruby class call
283
+ class_name = vcode[0]
284
+ context.parse_check_call_fn(i.text_value, args.count, class_name)
285
+ "#{class_name}.#{i.text_value}(#{args_str})"
286
+ else
287
+ "_instance_call(#{vcode}, '#{i.text_value}', [#{args_str}])"
288
+ end
289
+
290
+ end
291
+ end
292
+
293
+ class NodeCall < SNode
294
+ def check(context, *)
295
+ al.check(context) unless al.text_value.empty?
296
+ []
297
+ end
298
+
299
+ def rewrite(context, node_name)
300
+ do_rewrite(context, node_name)
301
+ end
302
+
303
+ def do_rewrite(context, node_name, mname="nil")
304
+ args, kw = al.text_value.empty? ? [[], {}] : al.rewrite(context)
305
+
306
+ raise "No positional arguments to node call" unless
307
+ args.empty?
308
+
309
+ kw_str = '{' + kw.map {|k, v| "'#{k}' => #{v}" }.join(',') + '}'
310
+
311
+ "_node_call(#{node_name}, #{mname}, _e, #{kw_str})"
312
+ end
313
+ end
314
+
315
+ ######################################################################
316
+
317
+ class ExpGetAttr < SNode
318
+ def check(context, *)
319
+ v.check(context)
225
320
  end
226
321
 
227
322
  def rewrite(context)
228
- fn.text_value + "(_e, " + (defined?(args) ? args.rewrite(context) : "") + ")"
323
+ attr_list = ga.text_value.split('.')
324
+
325
+ # If ga.text_value is not "", then we need to drop the 1st
326
+ # element since it'll be "".
327
+ attr_list.shift
328
+
329
+ attr_list.inject(v.rewrite(context)) {|x, y| "_get_attr(#{x}, '#{y}', _e)"}
229
330
  end
230
331
  end
231
332
 
@@ -247,21 +348,6 @@ eos
247
348
  end
248
349
  end
249
350
 
250
- class ModelFn < SNode
251
- def check(context, *)
252
- acount, res =
253
- defined?(args) ? [args.arg_count, args.check(context)] : [0, []]
254
-
255
- context.parse_check_call_fn(fn.text_value, acount, m.text_value)
256
- res
257
- end
258
-
259
- def rewrite(context)
260
- m.text_value + "." + fn.text_value +
261
- "(" + (defined?(args) ? args.rewrite(context) : "") + ")"
262
- end
263
- end
264
-
265
351
  class IfElse < SNode
266
352
  def check(context, *)
267
353
  vc, e1c, e2c =
@@ -397,45 +483,4 @@ eos
397
483
  end
398
484
  end
399
485
 
400
- class ScriptCall < SNode
401
- def check(context, *)
402
- i.check(context) unless i.text_value.empty?
403
- al.check(context) if defined?(al)
404
- []
405
- end
406
-
407
- def rewrite(context)
408
- node_name = i.text_value.empty? ? "nil" : i.rewrite(context)
409
- do_rewrite(context, node_name)
410
- end
411
-
412
- def do_rewrite(context, node_name, mname="nil")
413
- args, kw = al.rewrite(context)
414
-
415
- args_str = '[' + args.reverse.join(',') + ']'
416
- kw_str = '{' + kw.map {|k, v| "'#{k}' => #{v}" }.join(',') + '}'
417
-
418
- "_script_call(#{node_name}, #{mname}, _e, #{args_str}, #{kw_str})"
419
- end
420
- end
421
-
422
- class ScriptCallNode < ScriptCall
423
- def check(context, *)
424
- # FIXME: for both this and when node_name is nil, should check
425
- # to see if attributes exist on the node before allowing the
426
- # call. Also, can check parameters.
427
-
428
- mname = mod.m.text_value if defined?(mod.m)
429
- context.parse_check_defined_mod_node(c.text_value, mname)
430
-
431
- al.check(context) if defined?(al)
432
- []
433
- end
434
-
435
- def rewrite(context)
436
- node_name = c.text_value.inspect
437
- mname = defined?(mod.m) ? mod.m.text_value.inspect : "nil"
438
- do_rewrite(context, node_name, mname)
439
- end
440
- end
441
486
  end
@@ -1,3 +1,3 @@
1
1
  module Delorean
2
- VERSION = "0.0.43"
2
+ VERSION = "0.1.00"
3
3
  end
@@ -4,7 +4,6 @@ require 'treetop'
4
4
  require 'delorean/delorean'
5
5
  require 'delorean/nodes'
6
6
  require 'delorean/engine'
7
- require 'delorean/functions'
8
7
  require 'delorean/base'
9
8
  require 'delorean/error'
10
9
  require 'delorean/container'
@@ -440,7 +440,7 @@ eof
440
440
 
441
441
  it "should handle invalid expression evaluation" do
442
442
  # Should handle errors on expression such as -[] or -"xxx" or ("x"
443
- # + []) better. Currently, it raised NoMethodError.
443
+ # + []) better. Currently, it raises NoMethodError.
444
444
  pending
445
445
  end
446
446
 
@@ -557,14 +557,25 @@ eof
557
557
  engine.evaluate("A", "c").should == {0.5=>50}
558
558
  end
559
559
 
560
+ it "should eval node calls as intermediate results" do
561
+ engine.parse defn("A:",
562
+ " a =?",
563
+ " e = A(a: 13)",
564
+ " d = e.a * 2",
565
+ " f = e.d / e.a",
566
+ )
567
+
568
+ engine.evaluate_attrs("A", ["d", "f"]).should == [26, 2]
569
+ end
570
+
560
571
  it "should eval module calls 1" do
561
572
  engine.parse defn("A:",
562
573
  " a = 123",
563
574
  " n = A",
564
- " d = @n('a')",
575
+ " d = n().a",
565
576
  )
566
577
 
567
- engine.evaluate_attrs("A", %w{d}).should == [{"a" => 123}]
578
+ engine.evaluate_attrs("A", %w{d}).should == [123]
568
579
  end
569
580
 
570
581
  it "should eval module calls 2" do
@@ -572,19 +583,30 @@ eof
572
583
  " a = 123",
573
584
  " b = 456 + a",
574
585
  " n = 'A'",
575
- " c = @('a', 'b', x: 123, y: 456)",
576
- " d = @n('a', 'b', x: 123, y: 456)",
577
- " e = @('b')",
586
+ " c = nil(x: 123, y: 456) % ['a', 'b']",
587
+ " d = n(x: 123, y: 456) % ['a', 'b']",
588
+ " e = nil() % ['b']",
578
589
  )
579
590
 
580
591
  engine.evaluate_attrs("A", %w{n c d e}).should ==
581
592
  ["A", {"a"=>123, "b"=>579}, {"a"=>123, "b"=>579}, {"b"=>579}]
582
593
  end
583
594
 
595
+ it "should eval module calls 3" do
596
+ engine.parse defn("A:",
597
+ " a = 123",
598
+ "B:",
599
+ " n = 'A'",
600
+ " d = n().a",
601
+ )
602
+
603
+ engine.evaluate_attrs("B", %w{d}).should == [123]
604
+ end
605
+
584
606
  it "should be possible to implement recursive calls" do
585
607
  engine.parse defn("A:",
586
608
  " n =?",
587
- " fact = if n <= 1 then 1 else n * @('fact', n: n-1).fact",
609
+ " fact = if n <= 1 then 1 else n * A(n: n-1).fact",
588
610
  )
589
611
 
590
612
  engine.evaluate("A", "fact", "n" => 10).should == 3628800
@@ -593,9 +615,9 @@ eof
593
615
  it "should eval module calls by node name" do
594
616
  engine.parse defn("A:",
595
617
  " a = 123",
596
- " b = @A('a')",
618
+ " b = A().a",
597
619
  )
598
- engine.evaluate("A", "b").should == {"a"=>123}
620
+ engine.evaluate("A", "b").should == 123
599
621
  end
600
622
 
601
623
  it "should eval multiline expressions" do
@@ -614,16 +636,12 @@ eof
614
636
  " b = 456 + ",
615
637
  " a",
616
638
  " n = 'A'",
617
- " c = @('a', ",
618
- " 'b', ",
619
- " x: 123, ",
620
- " y: 456)",
621
- " d = @n('a', ",
622
- " 'b', ",
623
- " x: 123, y: 456)",
624
- " e = @(",
625
- " 'b'",
626
- " )",
639
+ " c = nil(x: 123,",
640
+ " y: 456) % ['a', 'b']",
641
+ " d = n(",
642
+ " x: 123, y: 456) % ['a', 'b']",
643
+ " e = nil(",
644
+ " ) % ['b']",
627
645
  )
628
646
 
629
647
  engine.evaluate_attrs("A", %w{n c d e}).should ==
@@ -636,10 +654,10 @@ eof
636
654
  " b = 456",
637
655
  "B: AAA::X",
638
656
  " a = 111",
639
- " c = @AAA::X('b', a: 456)",
657
+ " c = AAA::X(a: 456).b",
640
658
  ), sset
641
659
  engine.evaluate_attrs("B", ["a", "b", "c"], {}).should ==
642
- [111, 222, {"b"=>456*2}]
660
+ [111, 222, 456*2]
643
661
  end
644
662
 
645
663
  it "should eval imports (2)" do
@@ -648,7 +666,7 @@ eof
648
666
  defn("import AAA 0001",
649
667
  "B: AAA::X",
650
668
  " a = 111",
651
- " c = @AAA::X('b', a: -1)",
669
+ " c = AAA::X(a: -1).b",
652
670
  " d = a * 2",
653
671
  ),
654
672
  ["CCC", "0003"] =>
@@ -664,7 +682,7 @@ eof
664
682
  e2 = sset.get_engine("BBB", "0002")
665
683
 
666
684
  e2.evaluate_attrs("B", ["a", "b", "c", "d"]).should ==
667
- [111, 222, {"b"=>-2}, 222]
685
+ [111, 222, -2, 222]
668
686
 
669
687
  engine.parse defn("import BBB 0002",
670
688
  "B: BBB::B",
@@ -672,12 +690,12 @@ eof
672
690
  ), sset
673
691
 
674
692
  engine.evaluate_attrs("B", ["a", "b", "c", "d", "e"]).should ==
675
- [111, 222, {"b"=>-2}, 222, 225]
693
+ [111, 222, -2, 222, 225]
676
694
 
677
695
  e4 = sset.get_engine("CCC", "0003")
678
696
 
679
697
  e4.evaluate_attrs("B", ["a", "b", "c", "d", "e"]).should ==
680
- [111, 222, {"b"=>-2}, 222, 666]
698
+ [111, 222, -2, 222, 666]
681
699
 
682
700
  e4.evaluate_attrs("C", ["a", "b", "d"]).should == [123, 123*2, 123*3*2]
683
701
  end
@@ -688,7 +706,7 @@ eof
688
706
  ["CCC", "0003"] =>
689
707
  defn("import BBB 0002",
690
708
  "X:",
691
- " xx = [n.x for n in @BBB::D('xs').xs]",
709
+ " xx = [n.x for n in BBB::D().xs]",
692
710
  " yy = [n.x for n in BBB::D.xs]",
693
711
  ),
694
712
  })
@@ -710,4 +728,14 @@ eof
710
728
  r.should == [2, 3, 456]
711
729
  end
712
730
 
731
+ it "can eval indexing 2" do
732
+ engine.parse defn("A:",
733
+ " a = 1",
734
+ " b = {'x' : 123, 'y': 456}",
735
+ " c = A() % ['a', 'b']",
736
+ " d = c['b'].x * c['a'] - c['b'].y",
737
+ )
738
+ r = engine.evaluate_attrs("A", ["a", "b", "c", "d"])
739
+ r.should == [1, {"x"=>123, "y"=>456}, {"a"=>1, "b"=>{"x"=>123, "y"=>456}}, -333]
740
+ end
713
741
  end