delorean_lang 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9255b38f00277f412eb2a42699d03646470199d261d6cc66298e9e55aa8bd7c0
4
- data.tar.gz: 3ae838764cd7a0f7355d1586b60112b1e073feb367d4b513a239a495b63940ca
3
+ metadata.gz: e0116cc5a84f3c0e7c2efc68c31862529ce3172f7bc7a8f1ee61c8804de3d6da
4
+ data.tar.gz: d29819d73fd26cd4057b6badd1eb831c2c86c095683f14039b61d1165f13fd8b
5
5
  SHA512:
6
- metadata.gz: 3d05eb327cd542977ff5eb795cb57326bd2daed68b6d599e62365adf6a0d828df2bcc7514095605a9318ce281d7dcf6bfb636b1dbdae6055708b2cb4c32835ff
7
- data.tar.gz: b315ab0405e667e6484b2a109b435f0a53a403fec92baa41d483be8b1978477002a82930d8457c1819c076f37f009494c80c313867cabed1af2f3e307c10f65a
6
+ metadata.gz: c448503aec6a6929311129849be0223d5ab0dbed702c22e67ab72fd5ad6c40cb71f60b169299e57795a61731505d458b474a040995790fc948ee50ef183dd318
7
+ data.tar.gz: 56f0224391bc1d1401033c0301297ccb8fb993180196d827c07b5a5f986d19c5f5cf1d051f42af8206e280a6fb0840ee8c434b5fcfe21d3f0fcf26581837f555
@@ -1,6 +1,7 @@
1
- image: thepry/docker-ruby-ci:2.4.2-latest
1
+ image: ruby:2.6.3-buster
2
2
 
3
3
  before_script:
4
+ - gem install bundler:2.0.2
4
5
  - bundle install --jobs $(nproc) --path vendor "${FLAGS[@]}"
5
6
 
6
7
  .base-test:
@@ -28,7 +29,7 @@ cache:
28
29
 
29
30
  variables:
30
31
  GIT_SSL_NO_VERIFY: "true"
31
- BUNDLER_VERSION: "2.0.1"
32
+ BUNDLER_VERSION: "2.0.2"
32
33
  POSTGRES_USER: "runner"
33
34
  POSTGRES_PASSWORD: ""
34
35
  RAILS_ENV: "test"
@@ -42,7 +42,7 @@ Metrics/AbcSize:
42
42
  # Offense count: 2
43
43
  # Configuration parameters: CountComments.
44
44
  Metrics/ClassLength:
45
- Max: 266
45
+ Max: 300
46
46
 
47
47
  # Offense count: 6
48
48
  Metrics/CyclomaticComplexity:
data/Gemfile CHANGED
@@ -4,3 +4,9 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in delorean.gemspec
6
6
  gemspec
7
+
8
+ group :development, :test do
9
+ gem 'benchmark-ips'
10
+ gem 'pry'
11
+ gem 'rspec-instafail', require: false
12
+ end
@@ -378,7 +378,7 @@ module Delorean
378
378
  raise "bad node '#{node}'" unless node =~ /^[A-Z][a-zA-Z0-9_]*$/
379
379
 
380
380
  begin
381
- klass = @m.module_eval(node)
381
+ klass = @m.const_get(node)
382
382
  rescue NameError
383
383
  err(UndefinedNodeError, "node #{node} is undefined")
384
384
  end
@@ -386,15 +386,17 @@ module Delorean
386
386
 
387
387
  params[:_engine] = self
388
388
 
389
- type_arr = attrs.is_a?(Array)
390
- attrs = [attrs] unless type_arr
389
+ if attrs.is_a?(Array)
390
+ attrs.map do |attr|
391
+ raise "bad attribute '#{attr}'" unless attr =~ /^[a-z][A-Za-z0-9_]*$/
391
392
 
392
- res = attrs.map do |attr|
393
- raise "bad attribute '#{attr}'" unless attr =~ /^[a-z][A-Za-z0-9_]*$/
393
+ klass.send("#{attr}#{POST}".to_sym, params)
394
+ end
395
+ else
396
+ raise "bad attribute '#{attrs}'" unless attrs =~ /^[a-z][A-Za-z0-9_]*$/
394
397
 
395
- klass.send("#{attr}#{POST}".to_sym, params)
398
+ klass.send("#{attrs}#{POST}".to_sym, params)
396
399
  end
397
- type_arr ? res : res[0]
398
400
  end
399
401
 
400
402
  def eval_to_hash(node, attrs, params = {})
@@ -748,9 +748,63 @@ eos
748
748
 
749
749
  def rewrite(context)
750
750
  return '{}' unless defined?(args)
751
+ return rewrite_with_literal(context) if can_be_literal?
752
+ return rewrite_with_splat_optimisation(context) if can_optimise_splat?
751
753
 
752
754
  var = "_h#{context.hcount}"
753
- "(#{var}={}; " + args.rewrite(context, var) + "; #{var})"
755
+ "(#{var}={}; #{args.rewrite(context, var)}; #{var})"
756
+ end
757
+
758
+ private
759
+
760
+ def rewrite_with_literal(context)
761
+ "{#{args.rewrite_with_literal(context)}}"
762
+ end
763
+
764
+ # By default { **hash1, **hash2 } is compiled to something like
765
+ # `h1 = {}; h1.merge!(hash1); h1.merge!(hash2)`.
766
+ # In cases when hash starts with two splats, we can skip empty hash
767
+ # assignment and compile it to `h1 = hash1.merge(hash2)`
768
+ # This would work faster in ruby 2.6+
769
+ def rewrite_with_splat_optimisation(context)
770
+ var = "_h#{context.hcount}"
771
+ args2 = args.args_rest.al
772
+
773
+ arg1_str = args.e0.rewrite(context)
774
+ arg2_str = args2.e0.rewrite(context)
775
+
776
+ args_rest = args.args_rest.al.args_rest
777
+
778
+ unless defined?(args_rest.al) && !args_rest.al.text_value.empty?
779
+ return "(#{var} = #{arg1_str}.merge(#{arg2_str}); #{var})"
780
+ end
781
+
782
+ args_rest_str = args_rest.al.rewrite(context, var)
783
+
784
+ "(#{var} = #{arg1_str}.merge(#{arg2_str}); #{args_rest_str} #{var})"
785
+ end
786
+
787
+ def can_be_literal?
788
+ return false if args.conditions?
789
+ return false if args.splats?
790
+
791
+ true
792
+ end
793
+
794
+ def can_optimise_splat?
795
+ # First argument must be splat without condition
796
+ return false unless args.splat?
797
+ return false if args.condition?
798
+
799
+ # Second argument must be splat without condition
800
+ return false unless defined?(args.args_rest.al)
801
+
802
+ args_rest = args.args_rest.al
803
+ return false unless args_rest.text_value.present?
804
+ return false unless args_rest.splat?
805
+ return false if args_rest.condition?
806
+
807
+ true
754
808
  end
755
809
  end
756
810
 
@@ -795,16 +849,48 @@ eos
795
849
  end
796
850
 
797
851
  def rewrite(context, var)
798
- res = if defined?(splat)
852
+ res = if splat?
799
853
  "#{var}.merge!(#{e0.rewrite(context)})"
800
854
  else
801
855
  "#{var}[#{e0.rewrite(context)}]=(#{e1.rewrite(context)})"
802
856
  end
803
- res += " if (#{ifexp.e3.rewrite(context)})" if defined?(ifexp.e3)
857
+ res += " if (#{ifexp.e3.rewrite(context)})" if condition?
804
858
  res += ';'
805
859
  res += args_rest.al.rewrite(context, var) if
806
860
  defined?(args_rest.al) && !args_rest.al.text_value.empty?
807
861
  res
808
862
  end
863
+
864
+ def rewrite_with_literal(context)
865
+ res = "#{e0.rewrite(context)} => #{e1.rewrite(context)},"
866
+ res += args_rest.al.rewrite_with_literal(context) if
867
+ defined?(args_rest.al) && !args_rest.al.text_value.empty?
868
+
869
+ res
870
+ end
871
+
872
+ def splat?
873
+ defined?(splat)
874
+ end
875
+
876
+ def condition?
877
+ defined?(ifexp.e3)
878
+ end
879
+
880
+ def conditions?
881
+ return true if condition?
882
+ return false unless defined?(args_rest.al)
883
+ return false if args_rest.al.text_value.empty?
884
+
885
+ args_rest.al.conditions?
886
+ end
887
+
888
+ def splats?
889
+ return true if splat?
890
+ return false unless defined?(args_rest.al)
891
+ return false if args_rest.al.text_value.empty?
892
+
893
+ args_rest.al.splats?
894
+ end
809
895
  end
810
896
  end
@@ -296,6 +296,14 @@ module Delorean
296
296
  method.called_on Array
297
297
  end
298
298
 
299
+ add_method :to_h do |method|
300
+ method.called_on Object
301
+ end
302
+
303
+ add_method :abs do |method|
304
+ method.called_on Numeric
305
+ end
306
+
299
307
  add_method :to_time do |method|
300
308
  (DT_TYPES + [String]).each do |type|
301
309
  method.called_on type
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Delorean
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0'
5
5
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+ require 'benchmark/ips'
5
+ require 'pry'
6
+
4
7
  describe 'Delorean' do
5
8
  let(:sset) do
6
9
  TestContainer.new(
@@ -780,6 +780,7 @@ describe 'Delorean' do
780
780
  engine.parse defn('A:',
781
781
  ' a =?',
782
782
  " d = {'a':1, 2:2, **a, **(a+a)}",
783
+ ' c = {**a, **(a+a)}',
783
784
  )
784
785
  end
785
786
 
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+ require 'benchmark/ips'
5
+ require 'pry'
6
+
7
+ describe 'Delorean' do
8
+ let(:sset) do
9
+ TestContainer.new({})
10
+ end
11
+
12
+ let(:engine) do
13
+ Delorean::Engine.new 'XXX', sset
14
+ end
15
+
16
+ # FIXME: perhpas add more optimization to hash compilation
17
+ xit 'hash splat performance as expected' do
18
+ perf_test = <<-DELOREAN
19
+ A:
20
+ x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
21
+ h = [[k, k.to_s] for k in x.product(x)].to_h
22
+ hh = {**h, "a":1, "b":2, **h, **h, **h, "c":3}
23
+ DELOREAN
24
+
25
+ engine.parse perf_test.gsub(/^ /, '')
26
+
27
+ bm = Benchmark.ips do |x|
28
+ x.report('delorean') { engine.evaluate('A', 'hh') }
29
+
30
+ x.report('ruby') do
31
+ il = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
32
+ h = il.product(il).map { |i| [i, i.to_s] }.to_h
33
+ h.merge('a' => 1, 'b' => 2).merge(h).merge(h).merge(h).merge('c' => 3)
34
+ end
35
+
36
+ x.compare!
37
+ end
38
+
39
+ # get iterations/sec for each report
40
+ h = bm.entries.each_with_object({}) do |e, hh|
41
+ hh[e.label] = e.stats.central_tendency
42
+ end
43
+
44
+ factor = h['ruby'] / h['delorean']
45
+
46
+ # p factor
47
+
48
+ expect(factor).to be < 1.10
49
+ end
50
+
51
+ it 'hash splat performance (2) as expected' do
52
+ perf_test = <<-DELOREAN
53
+ A:
54
+ h =?
55
+ hh = {**h, **h, **h, **h}
56
+ DELOREAN
57
+
58
+ engine.parse perf_test.gsub(/^ /, '')
59
+
60
+ il = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
61
+ h = il.product(il).map { |i| [i, i.to_s] }.to_h
62
+
63
+ bm = Benchmark.ips do |x|
64
+ x.report('delorean') { engine.evaluate('A', 'hh', 'h' => h) }
65
+
66
+ x.report('ruby') do
67
+ h.merge(h).merge(h).merge(h)
68
+ end
69
+
70
+ x.report('ruby!') do
71
+ hh = {}
72
+ 4.times { hh.merge!(h) }
73
+ end
74
+
75
+ x.compare!
76
+ end
77
+
78
+ # get iterations/sec for each report
79
+ h = bm.entries.each_with_object({}) do |e, hh|
80
+ hh[e.label] = e.stats.central_tendency
81
+ end
82
+
83
+ factor = h['ruby!'] / h['delorean']
84
+ # p factor
85
+ expect(factor).to be_within(0.25).of(0.9)
86
+
87
+ # perf of mutable vs immutable hash ops are as expected
88
+ factor = h['ruby!'] / h['ruby']
89
+ # p factor
90
+ expect(factor).to be_within(0.3).of(1.0)
91
+
92
+ factor = h['ruby'] / h['delorean']
93
+ # p factor
94
+ expect(factor).to be_within(0.15).of(1.0)
95
+ end
96
+
97
+ it 'hash literal performance as expected' do
98
+ il = (1..10).to_a
99
+
100
+ hdef1 = il.map { |i| "'#{'xo' * i}' : #{i}" }.join(',')
101
+ hdef2 = il.map { |i| "'#{'yo' * i}' : #{i}" }.join(',')
102
+
103
+ perf_test = <<-DELOREAN
104
+ A:
105
+ v =?
106
+ h = { #{hdef1}, #{hdef2} }
107
+ DELOREAN
108
+
109
+ engine.parse perf_test.gsub(/^ /, '')
110
+
111
+ bm = Benchmark.ips do |x|
112
+ x.report('delorean') { engine.evaluate('A', 'h', {}) }
113
+
114
+ x.report('ruby') do
115
+ {
116
+ 'xo' => 1,
117
+ 'xoxo' => 2,
118
+ 'xoxoxo' => 3,
119
+ 'xoxoxoxo' => 4,
120
+ 'xoxoxoxoxo' => 5,
121
+ 'xoxoxoxoxoxo' => 6,
122
+ 'xoxoxoxoxoxoxo' => 7,
123
+ 'xoxoxoxoxoxoxoxo' => 8,
124
+ 'xoxoxoxoxoxoxoxoxo' => 9,
125
+ 'xoxoxoxoxoxoxoxoxoxo' => 10,
126
+ 'yo' => 1,
127
+ 'yoyo' => 2,
128
+ 'yoyoyo' => 3,
129
+ 'yoyoyoyo' => 4,
130
+ 'yoyoyoyoyo' => 5,
131
+ 'yoyoyoyoyoyo' => 6,
132
+ 'yoyoyoyoyoyoyo' => 7,
133
+ 'yoyoyoyoyoyoyoyo' => 8,
134
+ 'yoyoyoyoyoyoyoyoyo' => 9,
135
+ 'yoyoyoyoyoyoyoyoyoyo' => 10,
136
+ }
137
+ end
138
+
139
+ x.compare!
140
+ end
141
+
142
+ # get iterations/sec for each report
143
+ h = bm.entries.each_with_object({}) do |e, hh|
144
+ hh[e.label] = e.stats.central_tendency
145
+ end
146
+
147
+ factor = h['ruby'] / h['delorean']
148
+ # p factor
149
+
150
+ # FIXME: locally the factor is around 4, but in Gitlab CI it's around 7
151
+ # expect(factor).to be_within(2.5).of(4)
152
+ expect(factor).to be < 8
153
+ end
154
+
155
+ it 'array and node call performance as expected' do
156
+ perf_test = <<-DELOREAN
157
+ A:
158
+ i =? 0
159
+ max =?
160
+ range = if i>max then [] else A(i=i+1, max=max).range + [i]
161
+
162
+ res = [x*2 for x in range]
163
+ result = res.sum
164
+ DELOREAN
165
+
166
+ engine.parse perf_test.gsub(/^ /, '')
167
+
168
+ bm = Benchmark.ips do |x|
169
+ lim = 100
170
+
171
+ x.report('delorean') { engine.evaluate('A', 'result', 'max' => lim) }
172
+
173
+ x.report('ruby') do
174
+ def range(max, counter = 0)
175
+ counter > max ? [] : range(max, counter + 1)
176
+ end
177
+
178
+ r = range(lim)
179
+ r.map { |i| i * 2 }.sum
180
+ end
181
+
182
+ x.compare!
183
+ end
184
+
185
+ # get iterations/sec for each report
186
+ h = bm.entries.each_with_object({}) do |e, hh|
187
+ hh[e.label] = e.stats.central_tendency
188
+ end
189
+
190
+ factor = h['ruby'] / h['delorean']
191
+
192
+ # p factor
193
+
194
+ expect(factor).to be < 135
195
+ end
196
+ end
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: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arman Bostani
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-08 00:00:00.000000000 Z
11
+ date: 2019-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -171,6 +171,7 @@ files:
171
171
  - spec/eval_spec.rb
172
172
  - spec/func_spec.rb
173
173
  - spec/parse_spec.rb
174
+ - spec/perf_spec.rb
174
175
  - spec/ruby/whitelist_spec.rb
175
176
  - spec/spec_helper.rb
176
177
  homepage: https://github.com/arman000/delorean_lang
@@ -192,8 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
193
  - !ruby/object:Gem::Version
193
194
  version: '0'
194
195
  requirements: []
195
- rubyforge_project:
196
- rubygems_version: 2.7.8
196
+ rubygems_version: 3.0.6
197
197
  signing_key:
198
198
  specification_version: 4
199
199
  summary: Delorean compiler
@@ -203,5 +203,6 @@ test_files:
203
203
  - spec/eval_spec.rb
204
204
  - spec/func_spec.rb
205
205
  - spec/parse_spec.rb
206
+ - spec/perf_spec.rb
206
207
  - spec/ruby/whitelist_spec.rb
207
208
  - spec/spec_helper.rb