trxl 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -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
@@ -0,0 +1,5 @@
1
+ require "rubygems"
2
+ require "treetop"
3
+
4
+ require 'trxl/trxl'
5
+ require 'trxl/trxl_grammar'
@@ -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
+