delorean_lang 0.4.2 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -50,7 +50,7 @@ grammar Delorean
50
50
  /
51
51
  '(' sp? al:kw_args? sp? ')' <NodeCall>
52
52
  /
53
- '.' sp? i:identifier '(' sp? al:kw_args? sp? ')' <Call>
53
+ '.' sp? i:identifier '(' sp? al:fn_args? sp? ')' <Call>
54
54
  /
55
55
  '.' sp? i:(identifier / integer) <GetAttr>
56
56
  end
@@ -127,12 +127,22 @@ grammar Delorean
127
127
  end
128
128
 
129
129
  rule hash_args
130
- e0:expression sp? ':' sp? e1:expression
130
+ splat:('**') e0:expression sp?
131
+ ifexp:('if' sp e3:expression sp?)?
132
+ args_rest:(sp? ',' sp? al:hash_args?)? <HashArgs>
133
+ /
134
+ e0:expression sp? ':' sp? e1:expression sp?
135
+ ifexp:('if' sp e3:expression sp?)?
131
136
  args_rest:(sp? ',' sp? al:hash_args?)? <HashArgs>
132
137
  end
133
138
 
134
139
  rule kw_args
135
- k:(i:identifier sp? '=' sp?)? arg0:expression
140
+ splat:('**') arg0:expression sp?
141
+ ifexp:('if' sp e3:expression sp?)?
142
+ args_rest:(sp? ',' sp? al:kw_args?)? <KwArgs>
143
+ /
144
+ k:(i:identifier sp? '=' sp?)? arg0:expression sp?
145
+ ifexp:('if' sp e3:expression sp?)?
136
146
  args_rest:(sp? ',' sp? al:kw_args?)? <KwArgs>
137
147
  end
138
148
 
@@ -27,6 +27,13 @@ module Delorean
27
27
  @param_set = Set.new
28
28
 
29
29
  @imports = {}
30
+
31
+ @hcount = 0
32
+ end
33
+
34
+ # used in counting literal hashes
35
+ def hcount
36
+ @hcount += 1
30
37
  end
31
38
 
32
39
  def curr_line
@@ -240,7 +240,7 @@ eos
240
240
  raise "String interpolation not supported" if text_value =~ /\#\{.*\}/
241
241
 
242
242
  # FIXME: syntax check?
243
- text_value + ".freeze"
243
+ text_value
244
244
  end
245
245
  end
246
246
 
@@ -248,7 +248,7 @@ eos
248
248
  def rewrite(context)
249
249
  # remove the quotes and requote. We don't want the likes of #{}
250
250
  # evals to just pass through.
251
- text_value[1..-2].inspect + ".freeze"
251
+ text_value[1..-2].inspect
252
252
  end
253
253
  end
254
254
 
@@ -314,44 +314,36 @@ eos
314
314
 
315
315
  class Call < SNode
316
316
  def check(context, *)
317
- al.check(context) unless al.text_value.empty?
318
- []
317
+ al.text_value.empty? ? [] : al.check(context)
319
318
  end
320
319
 
321
320
  def rewrite(context, vcode)
322
- args, kw = al.text_value.empty? ? [[], {}] : al.rewrite(context)
323
-
324
- raise "Keyword arguments not supported" unless
325
- kw.empty?
326
-
327
- args_str = args.reverse.join(',')
321
+ if al.text_value.empty?
322
+ args_str, arg_count = "", 0
323
+ else
324
+ args_str, arg_count = al.rewrite(context), al.arg_count
325
+ end
328
326
 
329
327
  if vcode.is_a?(ClassText)
330
328
  # ruby class call
331
329
  class_name = vcode.text
332
- context.parse_check_call_fn(i.text_value, args.count, class_name)
330
+ context.parse_check_call_fn(i.text_value, arg_count, class_name)
333
331
  "#{class_name}.#{i.text_value}(#{args_str})"
334
332
  else
335
333
  "_instance_call(#{vcode}, '#{i.text_value}', [#{args_str}], _e)"
336
334
  end
337
-
338
335
  end
339
336
  end
340
337
 
341
338
  class NodeCall < SNode
342
339
  def check(context, *)
343
- al.check(context) unless al.text_value.empty?
344
- []
340
+ al.text_value.empty? ? [] : al.check(context)
345
341
  end
346
342
 
347
343
  def rewrite(context, node_name)
348
- args, kw = al.text_value.empty? ? [[], {}] : al.rewrite(context)
349
-
350
- kw_str =
351
- (kw.map {|k, v| "'#{k}' => #{v}"} +
352
- args.reverse.each_with_index.map {|v, i| "#{i} => #{v}"}).join(',')
353
-
354
- "_node_call(#{node_name}, _e, {#{kw_str}})"
344
+ var = "_h#{context.hcount}"
345
+ res = al.text_value.empty? ? "" : al.rewrite(context, var)
346
+ "(#{var}={}; #{res}; _node_call(#{node_name}, _e, #{var}))"
355
347
  end
356
348
  end
357
349
 
@@ -410,7 +402,7 @@ eos
410
402
  end
411
403
 
412
404
  def rewrite(context)
413
- "[" + (defined?(args) ? args.rewrite(context) : "") + "].freeze"
405
+ "[" + (defined?(args) ? args.rewrite(context) : "") + "]"
414
406
  end
415
407
  end
416
408
 
@@ -457,7 +449,7 @@ eos
457
449
 
458
450
  res += ".select{|#{args_str}|(#{ifexp.e3.rewrite(context)})}" if
459
451
  defined?(ifexp.e3)
460
- res += ".map{|#{args_str}| (#{e2.rewrite(context)}) }.freeze"
452
+ res += ".map{|#{args_str}| (#{e2.rewrite(context)}) }"
461
453
  unpack_vars.each {|vname| context.parse_undef_var(vname)}
462
454
  res
463
455
  end
@@ -465,13 +457,13 @@ eos
465
457
 
466
458
  class SetExpr < ListExpr
467
459
  def rewrite(context)
468
- "Set#{super}.freeze"
460
+ "Set#{super}"
469
461
  end
470
462
  end
471
463
 
472
464
  class SetComprehension < ListComprehension
473
465
  def rewrite(context)
474
- "Set[*#{super}].freeze"
466
+ "Set[*#{super}]"
475
467
  end
476
468
  end
477
469
 
@@ -514,7 +506,7 @@ eos
514
506
  unpack_str = unpack_vars.count > 1 ? "(#{args_str})" : args_str
515
507
 
516
508
  res += ".each_with_object({}){|#{unpack_str}, _h#{hid}| " +
517
- "_h#{hid}[#{el.rewrite(context)}]=(#{er.rewrite(context)})}.freeze"
509
+ "_h#{hid}[#{el.rewrite(context)}]=(#{er.rewrite(context)})}"
518
510
 
519
511
  unpack_vars.each {|vname| context.parse_undef_var(vname)}
520
512
  res
@@ -527,47 +519,61 @@ eos
527
519
  end
528
520
 
529
521
  def rewrite(context)
530
- "{#{args.rewrite(context) if defined?(args)}}.freeze"
522
+ return "{}" unless defined?(args)
523
+ var = "_h#{context.hcount}"
524
+ "(#{var}={}; " + args.rewrite(context, var) + "; #{var})"
531
525
  end
532
526
  end
533
527
 
534
528
  class KwArgs < SNode
535
529
  def check(context, *)
536
- arg0.check(context) + (
537
- defined?(args_rest.al) && !args_rest.al.empty? ?
538
- args_rest.al.check(context) : [])
530
+ [arg0.check(context),
531
+ (ifexp.e3.check(context) if defined?(ifexp.e3)),
532
+ (args_rest.al.check(context) if
533
+ defined?(args_rest.al) && !args_rest.al.empty?)
534
+ ].compact.sum
539
535
  end
540
536
 
541
- def rewrite(context)
537
+ def rewrite(context, var, i=0)
542
538
  arg0_rw = arg0.rewrite(context)
543
539
 
544
- if defined?(args_rest.al) && !args_rest.al.text_value.empty?
545
- args, kw = args_rest.al.rewrite(context)
540
+ if defined?(splat)
541
+ res = "#{var}.merge!(#{arg0_rw})"
546
542
  else
547
- args, kw = [], {}
543
+ k_rw = defined?(k.i) ? "'#{k.i.text_value}'" : i.to_s
544
+ res = "#{var}[#{k_rw}]=(#{arg0_rw})"
545
+ i += 1 unless defined?(k.i)
548
546
  end
549
547
 
550
- if defined?(k.i)
551
- kw[k.i.text_value] = arg0_rw
552
- else
553
- args << arg0_rw
554
- end
555
-
556
- [args, kw]
548
+ res += " if (#{ifexp.e3.rewrite(context)})" if defined?(ifexp.e3)
549
+ res += ";"
550
+ res += args_rest.al.rewrite(context, var, i) if
551
+ defined?(args_rest.al) && !args_rest.al.text_value.empty?
552
+ res
557
553
  end
558
554
  end
559
555
 
560
556
  class HashArgs < SNode
561
557
  def check(context, *)
562
- e0.check(context) + e1.check(context) +
563
- (defined?(args_rest.al) && !args_rest.al.empty? ?
564
- args_rest.al.check(context) : [])
558
+ [e0.check(context),
559
+ (e1.check(context) unless defined?(splat)),
560
+ (ifexp.e3.check(context) if defined?(ifexp.e3)),
561
+ (args_rest.al.check(context) if
562
+ defined?(args_rest.al) && !args_rest.al.empty?),
563
+ ].compact.sum
565
564
  end
566
565
 
567
- def rewrite(context)
568
- e0.rewrite(context) + " => " + e1.rewrite(context) +
569
- (defined?(args_rest.al) && !args_rest.al.text_value.empty? ?
570
- ", " + args_rest.al.rewrite(context) : "")
566
+ def rewrite(context, var)
567
+ if defined?(splat)
568
+ res = "#{var}.merge!(#{e0.rewrite(context)})"
569
+ else
570
+ res = "#{var}[#{e0.rewrite(context)}]=(#{e1.rewrite(context)})"
571
+ end
572
+ res += " if (#{ifexp.e3.rewrite(context)})" if defined?(ifexp.e3)
573
+ res += ";"
574
+ res += args_rest.al.rewrite(context, var) if
575
+ defined?(args_rest.al) && !args_rest.al.text_value.empty?
576
+ res
571
577
  end
572
578
  end
573
579
  end
@@ -1,3 +1,3 @@
1
1
  module Delorean
2
- VERSION = "0.4.2"
2
+ VERSION = "0.4.4"
3
3
  end
data/spec/eval_spec.rb CHANGED
@@ -587,6 +587,21 @@ eof
587
587
  ]
588
588
  end
589
589
 
590
+ it "handles literal hashes with conditionals" do
591
+ engine.parse defn("A:",
592
+ " a = {'a':1 if 123, 'b':'x' if nil}",
593
+ " b = {'a':a if a, 2: a if true, 'c':nil if 2*2}",
594
+ " c = 1>2",
595
+ " d = {1: {1: 2 if b}, 3: 3 if c, 2: {2: 3 if a}}",
596
+ )
597
+
598
+ engine.evaluate("A", %w{a b d}).should == [
599
+ {"a"=>1},
600
+ {"a"=>{"a"=>1}, 2=>{"a"=>1}, "c"=>nil},
601
+ {1=>{1=>2}, 2=>{2=>3}},
602
+ ]
603
+ end
604
+
590
605
  it "should eval hash comprehension" do
591
606
  engine.parse defn("A:",
592
607
  " b = {i*5 :i for i in [1,2,3]}",
@@ -914,12 +929,17 @@ eof
914
929
  " x = _.a * _.b",
915
930
  " y = a && _",
916
931
  " z = (B() + _).x",
932
+ " w = B(**_).x",
933
+ " v = {**_, 'a': 123}",
917
934
  )
918
935
 
919
936
  engine.evaluate("A", "x", {"a"=>3, "b"=>5}).should == 15
920
937
  h = {"a"=>1, "b"=>2, "c"=>3}
921
938
  engine.evaluate("A", "y", {"a"=>1, "b"=>2, "c"=>3}).should == h
922
939
  engine.evaluate("A", "z", {"a"=>1, "b"=>2, "c"=>3}).should == -1
940
+ engine.evaluate("A", "w", {"a"=>4, "b"=>5, "c"=>3}).should == -1
941
+ engine.evaluate("A", "v", {"a"=>4, "b"=>5, "c"=>3}).should == {
942
+ "a"=>123, "b"=>5, "c"=>3}
923
943
  end
924
944
 
925
945
  it "implements positional args in node calls" do
@@ -955,6 +975,40 @@ eof
955
975
  expect(r).to eq 3
956
976
  end
957
977
 
978
+ it "node calls with double splats" do
979
+ engine.parse defn("A:",
980
+ " a =?",
981
+ " b =?",
982
+ " c = a+b",
983
+ " h = {'a': 123}",
984
+ " k = {'b': 456}",
985
+ " x = A(**h, **k).c"
986
+ )
987
+ r = engine.evaluate("A", "x")
988
+ expect(r).to eq 579
989
+ end
990
+
991
+ it "hash literal with double splats" do
992
+ engine.parse defn("A:",
993
+ " a =?",
994
+ " b =?",
995
+ " h = {'a': 123, **a}",
996
+ " k = {'b': 456, **h, **a, **b}",
997
+ " l = {**k}",
998
+ " m = {**k, 1:1, 2:2, 3:33}",
999
+ " n = {**k if false, 1:1, 2:2, 3:33}",
1000
+ )
1001
+ r = engine.evaluate("A", ["h", "k", "l", "m", "n"],
1002
+ {"a" => {3=>3, 4=>4}, "b" => {5=>5, "a" => "aa"}})
1003
+ expect(r).to eq [
1004
+ {"a"=>123, 3=>3, 4=>4},
1005
+ {"b"=>456, "a"=>"aa", 3=>3, 4=>4, 5=>5},
1006
+ {"b"=>456, "a"=>"aa", 3=>3, 4=>4, 5=>5},
1007
+ {"b"=>456, "a"=>"aa", 3=>33, 4=>4, 5=>5, 1=>1, 2=>2},
1008
+ {1=>1, 2=>2, 3=>33},
1009
+ ]
1010
+ end
1011
+
958
1012
  it "understands openstructs" do
959
1013
  engine.parse defn("A:",
960
1014
  " os = Dummy.returns_openstruct",
data/spec/parse_spec.rb CHANGED
@@ -614,6 +614,13 @@ describe "Delorean" do
614
614
  }.should raise_error(Delorean::ParseError)
615
615
  end
616
616
 
617
+ it "should be able to parse conditional hash literals" do
618
+ engine.parse defn("A:",
619
+ " a = {}",
620
+ " c = {'a':a if a, 'b': 2, 'c':-3 if 123}",
621
+ )
622
+ end
623
+
617
624
  it "should handle trailing ',' with hashes" do
618
625
  engine.parse defn("A:",
619
626
  " b = {-1:1,}",
@@ -746,6 +753,26 @@ describe "Delorean" do
746
753
  )
747
754
  end
748
755
 
756
+ it "allow conditional args to node calls" do
757
+ engine.parse defn("A:",
758
+ " d = A(a=1, b=4 if true, c=4 if false)",
759
+ )
760
+ end
761
+
762
+ it "allow double splats in node calls" do
763
+ engine.parse defn("A:",
764
+ " a =?",
765
+ " d = A(**a, **(a+a), a=123, b=456)",
766
+ )
767
+ end
768
+
769
+ it "allow double splats in literal hashes" do
770
+ engine.parse defn("A:",
771
+ " a =?",
772
+ " d = {'a':1, 2:2, **a, **(a+a)}",
773
+ )
774
+ end
775
+
749
776
  it "should parse instance calls" do
750
777
  engine.parse defn("A:",
751
778
  " a = [1,2,[4]].flatten(1)",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delorean_lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arman Bostani
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-28 00:00:00.000000000 Z
11
+ date: 2018-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: treetop