delorean_lang 1.1.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +3 -2
- data/.rubocop_todo.yml +1 -1
- data/Gemfile +6 -0
- data/lib/delorean/engine.rb +9 -7
- data/lib/delorean/nodes.rb +89 -3
- data/lib/delorean/ruby/whitelists/default.rb +8 -0
- data/lib/delorean/version.rb +1 -1
- data/spec/eval_spec.rb +3 -0
- data/spec/parse_spec.rb +1 -0
- data/spec/perf_spec.rb +196 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0116cc5a84f3c0e7c2efc68c31862529ce3172f7bc7a8f1ee61c8804de3d6da
|
4
|
+
data.tar.gz: d29819d73fd26cd4057b6badd1eb831c2c86c095683f14039b61d1165f13fd8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c448503aec6a6929311129849be0223d5ab0dbed702c22e67ab72fd5ad6c40cb71f60b169299e57795a61731505d458b474a040995790fc948ee50ef183dd318
|
7
|
+
data.tar.gz: 56f0224391bc1d1401033c0301297ccb8fb993180196d827c07b5a5f986d19c5f5cf1d051f42af8206e280a6fb0840ee8c434b5fcfe21d3f0fcf26581837f555
|
data/.gitlab-ci.yml
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
image:
|
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.
|
32
|
+
BUNDLER_VERSION: "2.0.2"
|
32
33
|
POSTGRES_USER: "runner"
|
33
34
|
POSTGRES_PASSWORD: ""
|
34
35
|
RAILS_ENV: "test"
|
data/.rubocop_todo.yml
CHANGED
data/Gemfile
CHANGED
data/lib/delorean/engine.rb
CHANGED
@@ -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.
|
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
|
-
|
390
|
-
|
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
|
-
|
393
|
-
|
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("#{
|
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 = {})
|
data/lib/delorean/nodes.rb
CHANGED
@@ -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}={};
|
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
|
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
|
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
|
data/lib/delorean/version.rb
CHANGED
data/spec/eval_spec.rb
CHANGED
data/spec/parse_spec.rb
CHANGED
data/spec/perf_spec.rb
ADDED
@@ -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:
|
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
|
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
|
-
|
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
|