trxl 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +24 -0
- data/README +143 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/lib/trxl.rb +5 -0
- data/lib/trxl/trxl.rb +585 -0
- data/lib/trxl/trxl_grammar.rb +8583 -0
- data/lib/trxl/trxl_grammar.treetop +1394 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/trxl/arithmetics_spec.rb +391 -0
- data/spec/trxl/arrays_spec.rb +163 -0
- data/spec/trxl/booleans_spec.rb +138 -0
- data/spec/trxl/builtins_spec.rb +268 -0
- data/spec/trxl/closures_spec.rb +244 -0
- data/spec/trxl/comments_spec.rb +35 -0
- data/spec/trxl/common_spec.rb +22 -0
- data/spec/trxl/conditionals_spec.rb +454 -0
- data/spec/trxl/constants_spec.rb +23 -0
- data/spec/trxl/environment_spec.rb +117 -0
- data/spec/trxl/hashes_spec.rb +62 -0
- data/spec/trxl/numbers_spec.rb +27 -0
- data/spec/trxl/ranges_spec.rb +81 -0
- data/spec/trxl/require_spec.rb +50 -0
- data/spec/trxl/stdlib_spec.rb +370 -0
- data/spec/trxl/strings_spec.rb +1 -0
- data/spec/trxl/variables_spec.rb +45 -0
- data/trxl.gemspec +90 -0
- metadata +119 -0
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
test_log
|
2
|
+
pkg
|
3
|
+
pkg/*
|
4
|
+
*/pkg/*
|
5
|
+
bundle
|
6
|
+
bundle/*
|
7
|
+
doc
|
8
|
+
*.log
|
9
|
+
log
|
10
|
+
!log*.rb
|
11
|
+
*/log
|
12
|
+
log/*
|
13
|
+
*/log/*
|
14
|
+
coverage
|
15
|
+
*/coverage
|
16
|
+
lib/dm-more.rb
|
17
|
+
*.db
|
18
|
+
nbproject
|
19
|
+
.DS_Store
|
20
|
+
rspec_report.html
|
21
|
+
*.swp
|
22
|
+
_Yardoc
|
23
|
+
*/ri
|
24
|
+
|
data/README
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
-----------------------------------------
|
2
|
+
1) Built in operators:
|
3
|
+
+,-,*,/,%,==,!=,<=,>=,<,>,;
|
4
|
+
-----------------------------------------
|
5
|
+
2) Integers and floats in arithmetics:
|
6
|
+
1 or 2.33333 or 0.34 or .34
|
7
|
+
-----------------------------------------
|
8
|
+
3) Arbitrary nesting of parentheses:
|
9
|
+
(1+2*(5+((3+4)*3)-6/2)+7*2)\n => 61
|
10
|
+
-----------------------------------------
|
11
|
+
4) Comments:
|
12
|
+
# A comment until the end of the line
|
13
|
+
/* A longer comment that
|
14
|
+
spans multiple lines
|
15
|
+
*/
|
16
|
+
-----------------------------------------
|
17
|
+
5) Built in keywords:
|
18
|
+
TRUE,FALSE,NULL,IF,ELSE,END
|
19
|
+
-----------------------------------------
|
20
|
+
6) Built in functions:
|
21
|
+
HELP,ENV,SIZE,SPLIT,ROUND,MIN,MAX
|
22
|
+
SUM,MULT,AVG, PRINT, PRINT_LINE
|
23
|
+
TO_INT, TO_FLOAT, TO_ARRAY
|
24
|
+
-----------------------------------------
|
25
|
+
7) Standard library functions:
|
26
|
+
Use to iterate over Arrays or Strings
|
27
|
+
FOREACH_IN, INJECT
|
28
|
+
-----------------------------------------
|
29
|
+
8) Access the current environment:
|
30
|
+
ENV; (your output may differ)
|
31
|
+
=> { :a => 3, :foo => 5 }
|
32
|
+
Given the following environment:
|
33
|
+
{ :a => 1, :b => 2, :c => 3 }
|
34
|
+
ENV['a']
|
35
|
+
=> 1
|
36
|
+
ENV['a'..'b']
|
37
|
+
=> { :a => 1, :b => 2 }
|
38
|
+
-----------------------------------------
|
39
|
+
9) Numeric variables and literals
|
40
|
+
3;
|
41
|
+
=> 3
|
42
|
+
a = 3;
|
43
|
+
=> 3
|
44
|
+
a;
|
45
|
+
=> 3
|
46
|
+
-----------------------------------------
|
47
|
+
10) String variables and literals
|
48
|
+
"This is a string";
|
49
|
+
=> "This is a string";
|
50
|
+
'This is a string';
|
51
|
+
=> "This is a string";
|
52
|
+
s1 = "This is a string"; s1;
|
53
|
+
=> "This is a string"
|
54
|
+
s2 = 'This is a string'; s2;
|
55
|
+
=> "This is a string"
|
56
|
+
SIZE(s1);
|
57
|
+
=> 16
|
58
|
+
SIZE("foo");
|
59
|
+
=> 3
|
60
|
+
-----------------------------------------
|
61
|
+
11) Variables and closure applications
|
62
|
+
a = 3; foo = 5;
|
63
|
+
calc = fun(x,y) { (x + y) * a + foo };
|
64
|
+
calc(2,2);
|
65
|
+
=> 17
|
66
|
+
-----------------------------------------
|
67
|
+
12) Array variables and literals
|
68
|
+
arr = [1, [fun(){2}()], fun(x){x}(3)]
|
69
|
+
SIZE(arr);
|
70
|
+
=> 3
|
71
|
+
SIZE([1,2,3]);
|
72
|
+
=> 3
|
73
|
+
[1,2,3] + [4,[5,6]];
|
74
|
+
=> [1,2,3,4,[5,6]]
|
75
|
+
[1,2,3] - [[1],2,3];
|
76
|
+
=> [1]
|
77
|
+
-----------------------------------------
|
78
|
+
13) Hash variables and literals
|
79
|
+
h = { 1 => fun(){2}(), 'a' => 'foo' }
|
80
|
+
SIZE(h);
|
81
|
+
=> 2
|
82
|
+
h[1];
|
83
|
+
=> 'fun(){2}()'
|
84
|
+
h['a'];
|
85
|
+
=> 'foo'
|
86
|
+
SIZE({ 1 => 2});
|
87
|
+
=> 1
|
88
|
+
-----------------------------------------
|
89
|
+
14) Range variables and literals
|
90
|
+
range_including_upper = 1..5
|
91
|
+
=> [ 1, 2, 3, 4, 5 ]
|
92
|
+
SIZE(range_including_upper);
|
93
|
+
=> 5
|
94
|
+
range_excluding_upper = 1...5
|
95
|
+
=> [ 1, 2, 3, 4 ]
|
96
|
+
SIZE(range_excluding_upper);
|
97
|
+
=> 4
|
98
|
+
SIZE([1..5);
|
99
|
+
=> 5
|
100
|
+
-----------------------------------------
|
101
|
+
15) Conditional branching and recursion:
|
102
|
+
factorial = fun(x) {
|
103
|
+
if(x == 0)
|
104
|
+
1
|
105
|
+
else
|
106
|
+
x * factorial(x - 1)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
factorial(5);
|
110
|
+
=> 120
|
111
|
+
-----------------------------------------
|
112
|
+
16) Conditional branching:
|
113
|
+
foo = fun(x) {
|
114
|
+
if(x == 0)
|
115
|
+
0
|
116
|
+
elsif(x == 1)
|
117
|
+
1
|
118
|
+
else
|
119
|
+
2
|
120
|
+
end
|
121
|
+
}
|
122
|
+
foo(0);
|
123
|
+
=> 0
|
124
|
+
foo(1);
|
125
|
+
=> 1
|
126
|
+
foo(2);
|
127
|
+
=> 2
|
128
|
+
-----------------------------------------
|
129
|
+
17) case expressions:
|
130
|
+
foo = fun(x) {
|
131
|
+
case x
|
132
|
+
when 0 then 0
|
133
|
+
when 1 then 1
|
134
|
+
when 2 then 2
|
135
|
+
else 3
|
136
|
+
end
|
137
|
+
}
|
138
|
+
foo(1);
|
139
|
+
=> 1
|
140
|
+
foo(3);
|
141
|
+
=> 3
|
142
|
+
-----------------------------------------
|
143
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
|
6
|
+
gem 'jeweler', '>= 1.4'
|
7
|
+
require 'jeweler'
|
8
|
+
|
9
|
+
Jeweler::Tasks.new do |gem|
|
10
|
+
|
11
|
+
gem.name = "trxl"
|
12
|
+
gem.summary = 'A specced little language written with ruby and treetop.'
|
13
|
+
gem.description = 'A specced little language written with ruby and treetop. It has lambdas, recursion, conditionals, arrays, hashes, ranges, strings, arithmetics and some other stuff. It even has a small code import facility.'
|
14
|
+
gem.email = "gamsnjaga [at] gmail [dot] com"
|
15
|
+
gem.homepage = "http://github.com/snusnu/trxl"
|
16
|
+
gem.authors = ['Martin Gamsjaeger (snusnu)', 'Michael Aufreiter']
|
17
|
+
|
18
|
+
# Runtime dependencies
|
19
|
+
gem.add_dependency 'treetop', '>= 1.4'
|
20
|
+
|
21
|
+
# Development dependencies
|
22
|
+
gem.add_development_dependency 'rspec', '>= 1.2.9'
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
Jeweler::GemcutterTasks.new
|
27
|
+
|
28
|
+
rescue LoadError
|
29
|
+
puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'spec/rake/spectask'
|
33
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
34
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
35
|
+
spec.libs << 'lib' << 'spec'
|
36
|
+
spec.spec_opts << '--options' << 'spec/spec.opts'
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.5
|
data/lib/trxl.rb
ADDED
data/lib/trxl/trxl.rb
ADDED
@@ -0,0 +1,585 @@
|
|
1
|
+
# load treetop grammar
|
2
|
+
# this is done independent of RAILS_ROOT to allow easy
|
3
|
+
# speccing outside of rails context
|
4
|
+
# TODO once stable, replace this with a simple require
|
5
|
+
|
6
|
+
|
7
|
+
# reopen treetop generated module
|
8
|
+
module Trxl
|
9
|
+
|
10
|
+
class TrxlException < Exception; end
|
11
|
+
class InternalError < TrxlException; end
|
12
|
+
class FatalParseError < TrxlException; end
|
13
|
+
class DivisionByZeroError < FatalParseError; end
|
14
|
+
class MissingFormulaException < TrxlException; end
|
15
|
+
class MissingVariableException < TrxlException; end
|
16
|
+
class InvalidOperationException < TrxlException; end
|
17
|
+
class InvalidArgumentException < TrxlException; end
|
18
|
+
class WrongNumberOfArgumentsException < TrxlException; end
|
19
|
+
|
20
|
+
class MissingLibraryException < TrxlException; end
|
21
|
+
class NotImplementedException < TrxlException; end
|
22
|
+
|
23
|
+
class ExitScopeNotAllowedException < TrxlException; end
|
24
|
+
|
25
|
+
class Assignment < Treetop::Runtime::SyntaxNode; end
|
26
|
+
class Variable < Treetop::Runtime::SyntaxNode; end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
class Environment
|
31
|
+
|
32
|
+
# called when parsing starts
|
33
|
+
def initialize(local_env = {})
|
34
|
+
@stack = [ local_env ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def enter_scope
|
38
|
+
push #peek.dup
|
39
|
+
end
|
40
|
+
|
41
|
+
def exit_scope
|
42
|
+
pop
|
43
|
+
end
|
44
|
+
|
45
|
+
def local
|
46
|
+
peek
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def depth
|
51
|
+
@stack.size
|
52
|
+
end
|
53
|
+
|
54
|
+
def merge(other_env)
|
55
|
+
self.class.new(local.merge(other_env))
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge!(other_env)
|
59
|
+
local.merge!(other_env); self
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def [](variable)
|
64
|
+
var = variable.to_sym
|
65
|
+
@stack.each do |env|
|
66
|
+
if env.has_key?(var)
|
67
|
+
return env[var]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# FIXME find out why map definition doesn't work
|
74
|
+
def []=(variable, value)
|
75
|
+
var = variable.to_sym
|
76
|
+
# search all scopes
|
77
|
+
@stack.each do |env|
|
78
|
+
if env.has_key?(var)
|
79
|
+
return env[var] = value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# not found, assign it in local scope
|
83
|
+
local[var] = value
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
@stack.inject([]) do |stack, env|
|
88
|
+
stack << env.inspect
|
89
|
+
end.join(',')
|
90
|
+
end
|
91
|
+
|
92
|
+
def empty?
|
93
|
+
@stack.size == 1 ? peek.empty? : @stack.all? { |h| h.empty? }
|
94
|
+
end
|
95
|
+
|
96
|
+
def has_key?(key)
|
97
|
+
@stack.any? { |env| env.has_key?(key.to_sym) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def select(&block)
|
101
|
+
@stack.inject([]) do |memo, env|
|
102
|
+
memo << env.select(&block)
|
103
|
+
end[0]
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_library(name)
|
107
|
+
(@libraries || []) << name.to_sym
|
108
|
+
end
|
109
|
+
|
110
|
+
def library_included?(name)
|
111
|
+
@libraries ? @libraries.include?(name.to_sym) : false
|
112
|
+
end
|
113
|
+
|
114
|
+
def libraries
|
115
|
+
@libraries.dup # don't allow modifications outside
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
# called when a new scope is entered
|
121
|
+
def push(local_env = {})
|
122
|
+
@stack.insert(0, local_env)
|
123
|
+
end
|
124
|
+
|
125
|
+
# called when a scope is left
|
126
|
+
def pop
|
127
|
+
if depth > 1
|
128
|
+
@stack.shift
|
129
|
+
else
|
130
|
+
raise Trxl::ExitScopeNotAllowedException, "cannot pop toplevel environment"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# always the local environment
|
135
|
+
def peek
|
136
|
+
@stack[0]
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class Function < Treetop::Runtime::SyntaxNode
|
142
|
+
|
143
|
+
class Closure
|
144
|
+
attr_reader :env, :function
|
145
|
+
|
146
|
+
def initialize(function, env = Environment.new)
|
147
|
+
@function = function
|
148
|
+
@env = env
|
149
|
+
end
|
150
|
+
|
151
|
+
def apply(args)
|
152
|
+
env.enter_scope
|
153
|
+
return_value = function.body.eval(function.formal_parameter_list.bind(args, env))
|
154
|
+
env.exit_scope
|
155
|
+
return_value
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_s(other_env = Environment.new)
|
159
|
+
function.text_value #"fun#{function.formal_parameter_list.to_s(env)} {#{function.body.to_s(other_env.merge(env.local))}}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def eval(env = Environment.new)
|
164
|
+
Closure.new(self, env)
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_s(env = Environment.new)
|
168
|
+
text_value #eval(env).to_s(env)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class BinaryOperation < Treetop::Runtime::SyntaxNode
|
173
|
+
|
174
|
+
def eval(env = Environment.new)
|
175
|
+
apply(operand_1.eval(env), operand_2.eval(env))
|
176
|
+
end
|
177
|
+
|
178
|
+
def apply(a, b)
|
179
|
+
operator.apply(a, b)
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_s(env = Environment.new)
|
183
|
+
"#{operand_1.to_s(env)} #{operator.text_value} #{operand_2.to_s(env)}"
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
module BinaryOperatorSupport
|
189
|
+
|
190
|
+
def lhs_nil_allowed?
|
191
|
+
raise InternalError, "Implement BinaryOperaterSupport#lhs_nil_allowed?"
|
192
|
+
end
|
193
|
+
|
194
|
+
def rhs_nil_allowed?
|
195
|
+
raise InternalError, "Implement BinaryOperaterSupport#rhs_nil_allowed?"
|
196
|
+
end
|
197
|
+
|
198
|
+
def nils_allowed?
|
199
|
+
lhs_nil_allowed? && rhs_nil_allowed?
|
200
|
+
end
|
201
|
+
|
202
|
+
def apply(a, b)
|
203
|
+
if a.nil?
|
204
|
+
if b.nil?
|
205
|
+
if nils_allowed?
|
206
|
+
perform_apply(a, b)
|
207
|
+
else
|
208
|
+
raise InvalidArgumentException, "Both operands MUST NOT be NULL"
|
209
|
+
end
|
210
|
+
else
|
211
|
+
if lhs_nil_allowed?
|
212
|
+
perform_apply(a, b)
|
213
|
+
else
|
214
|
+
raise InvalidArgumentException, "LHS operand MUST NOT be NULL"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
else
|
218
|
+
if b.nil?
|
219
|
+
if rhs_nil_allowed?
|
220
|
+
perform_apply(a, b)
|
221
|
+
else
|
222
|
+
raise InvalidArgumentException, "RHS operand MUST NOT be NULL"
|
223
|
+
end
|
224
|
+
else
|
225
|
+
perform_apply(a, b)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def perform_apply(a, b)
|
231
|
+
if a.respond_to?(ruby_operator)
|
232
|
+
a.send(ruby_operator, b)
|
233
|
+
else
|
234
|
+
_a = a ? (a.is_a?(String) ? "'#{a}'" : a) : false
|
235
|
+
_b = b ? (b.is_a?(String) ? "'#{b}'" : b) : false
|
236
|
+
eval "#{_a} #{ruby_operator} #{_b}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def ruby_operator
|
241
|
+
text_value
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
class NilRejectingOperator < Treetop::Runtime::SyntaxNode
|
247
|
+
|
248
|
+
include BinaryOperatorSupport
|
249
|
+
|
250
|
+
def lhs_nil_allowed?
|
251
|
+
false
|
252
|
+
end
|
253
|
+
|
254
|
+
def rhs_nil_allowed?
|
255
|
+
false
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
class NilAcceptingOperator < Treetop::Runtime::SyntaxNode
|
261
|
+
|
262
|
+
include BinaryOperatorSupport
|
263
|
+
|
264
|
+
def lhs_nil_allowed?
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
def rhs_nil_allowed?
|
269
|
+
true
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
class OffsetAccessExpression < Treetop::Runtime::SyntaxNode
|
276
|
+
|
277
|
+
def left_associative_apply(ruby_object, offsets)
|
278
|
+
offsets.inject(ruby_object) { |obj, offset| obj = obj[offset] }
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
class RequireDirective < Treetop::Runtime::SyntaxNode
|
284
|
+
|
285
|
+
def eval(env = Environment.new)
|
286
|
+
library = ((l = load_library(env))[-1..-1]) == ';' ? "#{l} ENV" : "#{l}; ENV"
|
287
|
+
unless env.library_included?(identifier(env))
|
288
|
+
env.merge!(Calculator.new.eval(library, env).local)
|
289
|
+
env.add_library(identifier(env))
|
290
|
+
end
|
291
|
+
env
|
292
|
+
end
|
293
|
+
|
294
|
+
def identifier(env = Environment.new)
|
295
|
+
@identifier ||= string_literal.eval(env)
|
296
|
+
end
|
297
|
+
|
298
|
+
# override this in subclasses
|
299
|
+
def load_library(env = Environment.new)
|
300
|
+
path = identifier(env).split('/')
|
301
|
+
if path[0] == ('stdlib')
|
302
|
+
if optimize_stdlib_access?
|
303
|
+
if path.size == 2
|
304
|
+
const = path[1].upcase
|
305
|
+
if Trxl::StdLib.constants.include?(const)
|
306
|
+
Calculator.stdlib(const)
|
307
|
+
else
|
308
|
+
raise MissingLibraryException, "Failed to load '#{identifier}'"
|
309
|
+
end
|
310
|
+
else
|
311
|
+
Calculator.stdlib
|
312
|
+
end
|
313
|
+
else
|
314
|
+
raise NotImplementedException, "Only optimized access is supported"
|
315
|
+
end
|
316
|
+
else
|
317
|
+
raise NotImplementedException, "Only require 'stdlib' is supported"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def optimize_stdlib_access?
|
322
|
+
true
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# This module exists only for performance reason.
|
329
|
+
# Loading the stdlib directly from a ruby object,
|
330
|
+
# should be much faster than loading it from a file.
|
331
|
+
|
332
|
+
module StdLib
|
333
|
+
|
334
|
+
FOREACH_IN = <<-PROGRAM
|
335
|
+
foreach_in = fun(enumerable, body) {
|
336
|
+
_foreach_in_(enumerable, body, 0);
|
337
|
+
};
|
338
|
+
_foreach_in_ = fun(enumerable, body, index) {
|
339
|
+
if(index < SIZE(enumerable) - 1)
|
340
|
+
body(enumerable[index]);
|
341
|
+
_foreach_in_(enumerable, body, index + 1)
|
342
|
+
else
|
343
|
+
body(enumerable[index])
|
344
|
+
end
|
345
|
+
};
|
346
|
+
PROGRAM
|
347
|
+
|
348
|
+
INJECT = <<-PROGRAM
|
349
|
+
inject = fun(memo, enumerable, body) {
|
350
|
+
_inject_(memo, enumerable, body, 0);
|
351
|
+
};
|
352
|
+
_inject_ = fun(memo, enumerable, body, index) {
|
353
|
+
if(index < SIZE(enumerable) - 1)
|
354
|
+
_inject_(body(memo, enumerable[index]), enumerable, body, index + 1)
|
355
|
+
else
|
356
|
+
body(memo, enumerable[index])
|
357
|
+
end
|
358
|
+
};
|
359
|
+
PROGRAM
|
360
|
+
|
361
|
+
MAP = <<-PROGRAM
|
362
|
+
require 'stdlib/inject';
|
363
|
+
map = fun(enumerable, body) {
|
364
|
+
b = body; # WORK AROUND a bug in Trxl::Environment
|
365
|
+
inject([], enumerable, fun(memo, e) { memo << b(e); });
|
366
|
+
};
|
367
|
+
PROGRAM
|
368
|
+
|
369
|
+
SELECT = <<-PROGRAM
|
370
|
+
require 'stdlib/inject';
|
371
|
+
select = fun(enumerable, body) {
|
372
|
+
b = body; # WORK AROUND a bug in Trxl::Environment
|
373
|
+
inject([], enumerable, fun(selected, value) {
|
374
|
+
if(b(value))
|
375
|
+
selected << value
|
376
|
+
else
|
377
|
+
selected
|
378
|
+
end
|
379
|
+
});
|
380
|
+
};
|
381
|
+
PROGRAM
|
382
|
+
|
383
|
+
REJECT = <<-REJECT
|
384
|
+
require 'stdlib/inject';
|
385
|
+
reject = fun(enumerable, filter) {
|
386
|
+
f = filter; # WORKAROUND for a bug in Trxl::Environment
|
387
|
+
inject([], enumerable, fun(rejected, value) {
|
388
|
+
if(f(value))
|
389
|
+
rejected
|
390
|
+
else
|
391
|
+
rejected << value
|
392
|
+
end
|
393
|
+
})
|
394
|
+
};
|
395
|
+
REJECT
|
396
|
+
|
397
|
+
IN_GROUPS_OF = <<-IN_GROUPS_OF
|
398
|
+
require 'stdlib/foreach_in';
|
399
|
+
require 'stdlib/inject';
|
400
|
+
in_groups_of = fun(size_of_group, enumerable, group_function) {
|
401
|
+
count = 0; groups = []; cur_group = [];
|
402
|
+
foreach_in(enumerable, fun(element) {
|
403
|
+
if(count < size_of_group)
|
404
|
+
cur_group << element;
|
405
|
+
count = count + 1
|
406
|
+
end;
|
407
|
+
if(count == size_of_group)
|
408
|
+
groups << cur_group;
|
409
|
+
cur_group = [];
|
410
|
+
count = 0
|
411
|
+
end
|
412
|
+
});
|
413
|
+
group_count = 0;
|
414
|
+
inject([], groups, fun(memo, group) {
|
415
|
+
group_count = group_count + 1;
|
416
|
+
memo << group_function(group, group_count);
|
417
|
+
memo
|
418
|
+
});
|
419
|
+
};
|
420
|
+
IN_GROUPS_OF
|
421
|
+
|
422
|
+
SUM_OF_TYPE = <<-SUM_OF_TYPE
|
423
|
+
sum_of_type = fun(type, all_types, all_values) {
|
424
|
+
SUM(VALUES_OF_TYPE(type, all_types, all_values));
|
425
|
+
};
|
426
|
+
SUM_OF_TYPE
|
427
|
+
|
428
|
+
AVG_SUM_OF_TYPE = <<-AVG_SUM_OF_TYPE
|
429
|
+
avg_sum_of_type = fun(type, all_types, all_values) {
|
430
|
+
AVG_SUM(VALUES_OF_TYPE(type, all_types, all_values));
|
431
|
+
};
|
432
|
+
AVG_SUM_OF_TYPE
|
433
|
+
|
434
|
+
AVG_RANGE_SUM_OF_TYPE = <<-AVG_RANGE_SUM_OF_TYPE
|
435
|
+
require 'stdlib/inject';
|
436
|
+
require 'stdlib/avg_sum_of_type';
|
437
|
+
avg_range_sum_of_type = fun(type, all_types, variable_range) {
|
438
|
+
inject(0, variable_range, fun(sum, variable) {
|
439
|
+
sum + avg_sum_of_type(type, all_types, ENV[variable])
|
440
|
+
});
|
441
|
+
};
|
442
|
+
AVG_RANGE_SUM_OF_TYPE
|
443
|
+
|
444
|
+
TOTAL_RANGE_SUM_OF_TYPE = <<-TOTAL_RANGE_SUM_OF_TYPE
|
445
|
+
require 'stdlib/inject';
|
446
|
+
require 'stdlib/sum_of_type';
|
447
|
+
total_range_sum_of_type = fun(type, all_types, variable_range) {
|
448
|
+
inject(0, variable_range, fun(sum, variable) {
|
449
|
+
sum + sum_of_type(type, all_types, ENV[variable])
|
450
|
+
});
|
451
|
+
};
|
452
|
+
TOTAL_RANGE_SUM_OF_TYPE
|
453
|
+
|
454
|
+
YEAR_FROM_DATE = <<-YEAR_FROM_DATE
|
455
|
+
year_from_date = fun(date) {
|
456
|
+
date = SPLIT(date, '/');
|
457
|
+
TO_INT(date[1]);
|
458
|
+
};
|
459
|
+
YEAR_FROM_DATE
|
460
|
+
|
461
|
+
MONTH_FROM_DATE = <<-MONTH_FROM_DATE
|
462
|
+
month_from_date = fun(date) {
|
463
|
+
date = SPLIT(date, '/');
|
464
|
+
TO_INT(date[0]);
|
465
|
+
};
|
466
|
+
MONTH_FROM_DATE
|
467
|
+
|
468
|
+
DATES = <<-DATES
|
469
|
+
require 'stdlib/month_from_date';
|
470
|
+
require 'stdlib/year_from_date';
|
471
|
+
DATES
|
472
|
+
|
473
|
+
RATIO = <<-RATIO
|
474
|
+
require 'stdlib/foreach_in';
|
475
|
+
ratio = fun(enumerable, true_condition, base_condition) {
|
476
|
+
base = 0;
|
477
|
+
positives = 0;
|
478
|
+
foreach_in(enumerable, fun(val) {
|
479
|
+
if(ENV[val] != base_condition)
|
480
|
+
base = base + 1
|
481
|
+
end;
|
482
|
+
if(ENV[val] == true_condition)
|
483
|
+
positives = positives + 1
|
484
|
+
end;
|
485
|
+
});
|
486
|
+
if(base > 0)
|
487
|
+
ROUND((ROUND(positives, 1) / base) * 100, 2)
|
488
|
+
else
|
489
|
+
NULL
|
490
|
+
end
|
491
|
+
};
|
492
|
+
RATIO
|
493
|
+
|
494
|
+
end
|
495
|
+
|
496
|
+
|
497
|
+
class Calculator
|
498
|
+
|
499
|
+
extend StdLib # optimized for performance
|
500
|
+
|
501
|
+
class << self
|
502
|
+
|
503
|
+
def stdlib(function = nil)
|
504
|
+
if function
|
505
|
+
Kernel.eval("Trxl::StdLib::#{function.to_s.upcase}").strip
|
506
|
+
else
|
507
|
+
Trxl::StdLib.constants.inject('') do |lib, const|
|
508
|
+
lib << Kernel.eval("Trxl::StdLib::#{const}")
|
509
|
+
end.strip
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
514
|
+
|
515
|
+
def initialize
|
516
|
+
@parser = TrxlParser.new
|
517
|
+
end
|
518
|
+
|
519
|
+
# may raise
|
520
|
+
# overwrite treetop to provide more precise exceptions
|
521
|
+
def parse(code, verbose = true)
|
522
|
+
if ast = @parser.parse(code)
|
523
|
+
ast
|
524
|
+
else
|
525
|
+
failure_idx = @parser.failure_index
|
526
|
+
|
527
|
+
# extract code snippet where parse error happened
|
528
|
+
start = ((idx = failure_idx - 12) < 0 ? 0 : idx)
|
529
|
+
stop = ((idx = failure_idx + 12) > code.size ? code.size : idx)
|
530
|
+
local_code = code.slice(start..stop).to_s.gsub(/\n|\r/, "")
|
531
|
+
|
532
|
+
msg = "Parse Error at index #{failure_idx} (showing excerpt):\n"
|
533
|
+
msg << "... #{local_code} ...\n"
|
534
|
+
|
535
|
+
# mark the exact offset where the parse error happened
|
536
|
+
offset = (start == 0) ? failure_idx + 4 : 16
|
537
|
+
offset.times { msg << ' '}; msg << "^\n"
|
538
|
+
|
539
|
+
if verbose
|
540
|
+
# show the originial trxl program
|
541
|
+
msg << "Original Code:\n#{code}\n\n"
|
542
|
+
# add detailed treetop parser error messages
|
543
|
+
msg << @parser.failure_reason
|
544
|
+
end
|
545
|
+
raise(Trxl::FatalParseError, msg)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
# may raise
|
550
|
+
# eval an expression in calculations.treetop grammar
|
551
|
+
# eval an already parsed Treetop::Runtime::SyntaxNode
|
552
|
+
def eval(expression, env = Environment.new, verbose = true, interpreter_mode = false)
|
553
|
+
if expression.is_a?(Treetop::Runtime::SyntaxNode)
|
554
|
+
interpreter_mode ? [ expression.eval(env), env ] : expression.eval(env)
|
555
|
+
else
|
556
|
+
ast = parse(expression, verbose)
|
557
|
+
interpreter_mode ? [ ast.eval(env), env ] : ast.eval(env)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
end
|
562
|
+
|
563
|
+
class Interpreter
|
564
|
+
|
565
|
+
attr_accessor :parser, :program, :env
|
566
|
+
|
567
|
+
def initialize
|
568
|
+
@parser = Calculator.new
|
569
|
+
@program = []
|
570
|
+
@env = env
|
571
|
+
end
|
572
|
+
|
573
|
+
def stash(loc)
|
574
|
+
@program << loc
|
575
|
+
end
|
576
|
+
|
577
|
+
def eval(env = [])
|
578
|
+
@parser.eval(@program.join(' '), env)
|
579
|
+
end
|
580
|
+
|
581
|
+
end
|
582
|
+
|
583
|
+
end
|
584
|
+
|
585
|
+
|