trxl 0.1.5

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.
@@ -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
+