trxl 0.1.5 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -20,11 +20,19 @@
20
20
  6) Built in functions:
21
21
  HELP,ENV,SIZE,SPLIT,ROUND,MIN,MAX
22
22
  SUM,MULT,AVG, PRINT, PRINT_LINE
23
- TO_INT, TO_FLOAT, TO_ARRAY
23
+ TO_INT, TO_FLOAT, TO_ARRAY, AVG_SUM
24
+ MATCHING_IDS, VALUES_OF_TYPE
24
25
  -----------------------------------------
25
26
  7) Standard library functions:
26
- Use to iterate over Arrays or Strings
27
- FOREACH_IN, INJECT
27
+ foreach_in, inject, map, select
28
+ reject, in_groups_of, sum_of_type
29
+ avg_sum_of_type, avg_range_sum_of_type
30
+ total_range_sum_of_type, ratio
31
+ year_from_date, month_from_date,
32
+ hash_values, hash_value_sum,
33
+ avg_hash_value_sum, hash_range_values,
34
+ hash_range_value_sum,
35
+ avg_hash_range_value_sum
28
36
  -----------------------------------------
29
37
  8) Access the current environment:
30
38
  ENV; (your output may differ)
@@ -129,10 +137,12 @@
129
137
  17) case expressions:
130
138
  foo = fun(x) {
131
139
  case x
132
- when 0 then 0
133
- when 1 then 1
134
- when 2 then 2
135
- else 3
140
+ when NULL then NULL
141
+ when 0 then 0
142
+ when 1 then 1
143
+ when 2 then 2
144
+ else
145
+ 3
136
146
  end
137
147
  }
138
148
  foo(1);
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.8
@@ -2,4 +2,4 @@ require "rubygems"
2
2
  require "treetop"
3
3
 
4
4
  require 'trxl/trxl'
5
- require 'trxl/trxl_grammar'
5
+ require 'trxl/trxl_grammar'
@@ -1,65 +1,54 @@
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
1
+ module Trxl
5
2
 
3
+ class TrxlException < Exception; end
4
+ class InternalError < TrxlException; end
5
+ class FatalParseError < TrxlException; end
6
+ class DivisionByZeroError < FatalParseError; end
7
+ class MissingFormulaException < TrxlException; end
8
+ class MissingVariableException < TrxlException; end
9
+ class InvalidOperationException < TrxlException; end
10
+ class InvalidArgumentException < TrxlException; end
11
+ class WrongNumberOfArgumentsException < TrxlException; end
12
+ class MissingLibraryException < TrxlException; end
13
+ class NotImplementedException < TrxlException; end
14
+ class ExitScopeNotAllowedException < TrxlException; end
6
15
 
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
16
  class Assignment < Treetop::Runtime::SyntaxNode; end
26
- class Variable < Treetop::Runtime::SyntaxNode; end
27
-
28
-
29
-
17
+ class Variable < Treetop::Runtime::SyntaxNode; end
18
+
30
19
  class Environment
31
-
20
+
32
21
  # called when parsing starts
33
22
  def initialize(local_env = {})
34
23
  @stack = [ local_env ]
35
24
  end
36
-
25
+
37
26
  def enter_scope
38
27
  push #peek.dup
39
28
  end
40
-
29
+
41
30
  def exit_scope
42
31
  pop
43
32
  end
44
-
33
+
45
34
  def local
46
35
  peek
47
36
  end
48
-
49
-
37
+
38
+
50
39
  def depth
51
40
  @stack.size
52
41
  end
53
-
42
+
54
43
  def merge(other_env)
55
44
  self.class.new(local.merge(other_env))
56
45
  end
57
-
46
+
58
47
  def merge!(other_env)
59
48
  local.merge!(other_env); self
60
49
  end
61
-
62
-
50
+
51
+
63
52
  def [](variable)
64
53
  var = variable.to_sym
65
54
  @stack.each do |env|
@@ -76,52 +65,52 @@ module Trxl
76
65
  # search all scopes
77
66
  @stack.each do |env|
78
67
  if env.has_key?(var)
79
- return env[var] = value
68
+ return env[var] = value
80
69
  end
81
70
  end
82
71
  # not found, assign it in local scope
83
72
  local[var] = value
84
73
  end
85
-
74
+
86
75
  def to_s
87
76
  @stack.inject([]) do |stack, env|
88
77
  stack << env.inspect
89
78
  end.join(',')
90
79
  end
91
-
80
+
92
81
  def empty?
93
82
  @stack.size == 1 ? peek.empty? : @stack.all? { |h| h.empty? }
94
83
  end
95
-
84
+
96
85
  def has_key?(key)
97
86
  @stack.any? { |env| env.has_key?(key.to_sym) }
98
87
  end
99
-
88
+
100
89
  def select(&block)
101
90
  @stack.inject([]) do |memo, env|
102
91
  memo << env.select(&block)
103
92
  end[0]
104
93
  end
105
-
94
+
106
95
  def add_library(name)
107
96
  (@libraries || []) << name.to_sym
108
97
  end
109
-
98
+
110
99
  def library_included?(name)
111
100
  @libraries ? @libraries.include?(name.to_sym) : false
112
101
  end
113
-
102
+
114
103
  def libraries
115
104
  @libraries.dup # don't allow modifications outside
116
105
  end
117
-
106
+
118
107
  protected
119
-
108
+
120
109
  # called when a new scope is entered
121
110
  def push(local_env = {})
122
111
  @stack.insert(0, local_env)
123
112
  end
124
-
113
+
125
114
  # called when a scope is left
126
115
  def pop
127
116
  if depth > 1
@@ -130,16 +119,16 @@ module Trxl
130
119
  raise Trxl::ExitScopeNotAllowedException, "cannot pop toplevel environment"
131
120
  end
132
121
  end
133
-
122
+
134
123
  # always the local environment
135
124
  def peek
136
125
  @stack[0]
137
126
  end
138
-
127
+
139
128
  end
140
-
129
+
141
130
  class Function < Treetop::Runtime::SyntaxNode
142
-
131
+
143
132
  class Closure
144
133
  attr_reader :env, :function
145
134
 
@@ -168,37 +157,37 @@ module Trxl
168
157
  text_value #eval(env).to_s(env)
169
158
  end
170
159
  end
171
-
160
+
172
161
  class BinaryOperation < Treetop::Runtime::SyntaxNode
173
-
162
+
174
163
  def eval(env = Environment.new)
175
164
  apply(operand_1.eval(env), operand_2.eval(env))
176
165
  end
177
-
166
+
178
167
  def apply(a, b)
179
168
  operator.apply(a, b)
180
169
  end
181
-
170
+
182
171
  def to_s(env = Environment.new)
183
172
  "#{operand_1.to_s(env)} #{operator.text_value} #{operand_2.to_s(env)}"
184
173
  end
185
-
174
+
186
175
  end
187
-
176
+
188
177
  module BinaryOperatorSupport
189
178
 
190
179
  def lhs_nil_allowed?
191
180
  raise InternalError, "Implement BinaryOperaterSupport#lhs_nil_allowed?"
192
181
  end
193
-
182
+
194
183
  def rhs_nil_allowed?
195
184
  raise InternalError, "Implement BinaryOperaterSupport#rhs_nil_allowed?"
196
185
  end
197
-
186
+
198
187
  def nils_allowed?
199
188
  lhs_nil_allowed? && rhs_nil_allowed?
200
189
  end
201
-
190
+
202
191
  def apply(a, b)
203
192
  if a.nil?
204
193
  if b.nil?
@@ -224,9 +213,9 @@ module Trxl
224
213
  else
225
214
  perform_apply(a, b)
226
215
  end
227
- end
216
+ end
228
217
  end
229
-
218
+
230
219
  def perform_apply(a, b)
231
220
  if a.respond_to?(ruby_operator)
232
221
  a.send(ruby_operator, b)
@@ -236,52 +225,52 @@ module Trxl
236
225
  eval "#{_a} #{ruby_operator} #{_b}"
237
226
  end
238
227
  end
239
-
228
+
240
229
  def ruby_operator
241
230
  text_value
242
231
  end
243
-
232
+
244
233
  end
245
-
234
+
246
235
  class NilRejectingOperator < Treetop::Runtime::SyntaxNode
247
-
236
+
248
237
  include BinaryOperatorSupport
249
-
238
+
250
239
  def lhs_nil_allowed?
251
240
  false
252
241
  end
253
-
242
+
254
243
  def rhs_nil_allowed?
255
244
  false
256
245
  end
257
-
246
+
258
247
  end
259
-
248
+
260
249
  class NilAcceptingOperator < Treetop::Runtime::SyntaxNode
261
-
250
+
262
251
  include BinaryOperatorSupport
263
-
252
+
264
253
  def lhs_nil_allowed?
265
254
  true
266
255
  end
267
-
256
+
268
257
  def rhs_nil_allowed?
269
258
  true
270
259
  end
271
-
260
+
272
261
  end
273
-
274
-
262
+
263
+
275
264
  class OffsetAccessExpression < Treetop::Runtime::SyntaxNode
276
-
265
+
277
266
  def left_associative_apply(ruby_object, offsets)
278
267
  offsets.inject(ruby_object) { |obj, offset| obj = obj[offset] }
279
268
  end
280
-
269
+
281
270
  end
282
-
271
+
283
272
  class RequireDirective < Treetop::Runtime::SyntaxNode
284
-
273
+
285
274
  def eval(env = Environment.new)
286
275
  library = ((l = load_library(env))[-1..-1]) == ';' ? "#{l} ENV" : "#{l}; ENV"
287
276
  unless env.library_included?(identifier(env))
@@ -290,12 +279,12 @@ module Trxl
290
279
  end
291
280
  env
292
281
  end
293
-
282
+
294
283
  def identifier(env = Environment.new)
295
284
  @identifier ||= string_literal.eval(env)
296
285
  end
297
-
298
- # override this in subclasses
286
+
287
+ # override this in subclasses
299
288
  def load_library(env = Environment.new)
300
289
  path = identifier(env).split('/')
301
290
  if path[0] == ('stdlib')
@@ -317,28 +306,25 @@ module Trxl
317
306
  raise NotImplementedException, "Only require 'stdlib' is supported"
318
307
  end
319
308
  end
320
-
309
+
321
310
  def optimize_stdlib_access?
322
311
  true
323
312
  end
324
-
313
+
325
314
  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
-
315
+
332
316
  module StdLib
333
-
317
+
334
318
  FOREACH_IN = <<-PROGRAM
335
319
  foreach_in = fun(enumerable, body) {
336
- _foreach_in_(enumerable, body, 0);
320
+ if(SIZE(enumerable) > 0)
321
+ __foreach_in__(enumerable, body, 0)
322
+ end
337
323
  };
338
- _foreach_in_ = fun(enumerable, body, index) {
324
+ __foreach_in__ = fun(enumerable, body, index) {
339
325
  if(index < SIZE(enumerable) - 1)
340
326
  body(enumerable[index]);
341
- _foreach_in_(enumerable, body, index + 1)
327
+ __foreach_in__(enumerable, body, index + 1)
342
328
  else
343
329
  body(enumerable[index])
344
330
  end
@@ -347,11 +333,15 @@ module Trxl
347
333
 
348
334
  INJECT = <<-PROGRAM
349
335
  inject = fun(memo, enumerable, body) {
350
- _inject_(memo, enumerable, body, 0);
336
+ if(SIZE(enumerable) > 0)
337
+ __inject__(memo, enumerable, body, 0)
338
+ else
339
+ memo
340
+ end
351
341
  };
352
- _inject_ = fun(memo, enumerable, body, index) {
342
+ __inject__ = fun(memo, enumerable, body, index) {
353
343
  if(index < SIZE(enumerable) - 1)
354
- _inject_(body(memo, enumerable[index]), enumerable, body, index + 1)
344
+ __inject__(body(memo, enumerable[index]), enumerable, body, index + 1)
355
345
  else
356
346
  body(memo, enumerable[index])
357
347
  end
@@ -361,44 +351,44 @@ module Trxl
361
351
  MAP = <<-PROGRAM
362
352
  require 'stdlib/inject';
363
353
  map = fun(enumerable, body) {
364
- b = body; # WORK AROUND a bug in Trxl::Environment
365
- inject([], enumerable, fun(memo, e) { memo << b(e); });
354
+ __body__ = body; # WORK AROUND a bug in Trxl::Environment
355
+ inject([], enumerable, fun(memo, e) { memo << __body__(e); });
366
356
  };
367
357
  PROGRAM
368
358
 
369
359
  SELECT = <<-PROGRAM
370
360
  require 'stdlib/inject';
371
361
  select = fun(enumerable, body) {
372
- b = body; # WORK AROUND a bug in Trxl::Environment
373
- inject([], enumerable, fun(selected, value) {
374
- if(b(value))
362
+ __body__ = body; # WORK AROUND a bug in Trxl::Environment
363
+ inject([], enumerable, fun(selected, value) {
364
+ if(__body__(value))
375
365
  selected << value
376
366
  else
377
367
  selected
378
- end
368
+ end
379
369
  });
380
370
  };
381
371
  PROGRAM
382
-
372
+
383
373
  REJECT = <<-REJECT
384
374
  require 'stdlib/inject';
385
375
  reject = fun(enumerable, filter) {
386
- f = filter; # WORKAROUND for a bug in Trxl::Environment
376
+ __filter__ = filter; # WORKAROUND for a bug in Trxl::Environment
387
377
  inject([], enumerable, fun(rejected, value) {
388
- if(f(value))
378
+ if(__filter__(value))
389
379
  rejected
390
380
  else
391
381
  rejected << value
392
- end
382
+ end
393
383
  })
394
384
  };
395
385
  REJECT
396
-
386
+
397
387
  IN_GROUPS_OF = <<-IN_GROUPS_OF
398
388
  require 'stdlib/foreach_in';
399
389
  require 'stdlib/inject';
400
390
  in_groups_of = fun(size_of_group, enumerable, group_function) {
401
- count = 0; groups = []; cur_group = [];
391
+ count = 0; groups = []; cur_group = [];
402
392
  foreach_in(enumerable, fun(element) {
403
393
  if(count < size_of_group)
404
394
  cur_group << element;
@@ -418,7 +408,54 @@ module Trxl
418
408
  });
419
409
  };
420
410
  IN_GROUPS_OF
421
-
411
+
412
+ HASH_VALUES = <<-HASH_VALUES
413
+ require 'stdlib/map';
414
+ hash_values = fun(hash) {
415
+ map(TO_ARRAY(hash), fun(pair) { pair[1] });
416
+ };
417
+ HASH_VALUES
418
+
419
+ HASH_VALUE_SUM = <<-VALUE_SUM
420
+ require 'stdlib/hash_values';
421
+ hash_value_sum = fun(hash) {
422
+ SUM(hash_values(hash))
423
+ };
424
+ VALUE_SUM
425
+
426
+ AVG_HASH_VALUE_SUM = <<-AVG_HASH_VALUE_SUM
427
+ require 'stdlib/hash_values';
428
+ avg_hash_value_sum = fun(hash) {
429
+ AVG_SUM(hash_values(hash))
430
+ };
431
+ AVG_HASH_VALUE_SUM
432
+
433
+ HASH_RANGE_VALUES = <<-HASH_RANGE_VALUES
434
+ require 'stdlib/foreach_in';
435
+ require 'stdlib/hash_values';
436
+ hash_range_values = fun(hash_range) {
437
+ inject([], hash_range, fun(values, hash_variable) {
438
+ values << hash_values(ENV[hash_variable]);
439
+ });
440
+ };
441
+ HASH_RANGE_VALUES
442
+
443
+ HASH_RANGE_VALUE_SUM = <<-HASH_RANGE_VALUE_SUM
444
+ require 'stdlib/hash_range_values';
445
+ hash_range_value_sum = fun(hash_range) {
446
+ SUM(hash_range_values(hash_range))
447
+ };
448
+ HASH_RANGE_VALUE_SUM
449
+
450
+ AVG_HASH_RANGE_VALUE_SUM = <<-AVG_HASH_RANGE_VALUE_SUM
451
+ require 'stdlib/hash_range_values';
452
+ avg_hash_range_value_sum = fun(hash_range) {
453
+ inject(0, hash_range_values(hash_range), fun(sum, bucket) {
454
+ sum + AVG_SUM(bucket);
455
+ });
456
+ };
457
+ AVG_HASH_RANGE_VALUE_SUM
458
+
422
459
  SUM_OF_TYPE = <<-SUM_OF_TYPE
423
460
  sum_of_type = fun(type, all_types, all_values) {
424
461
  SUM(VALUES_OF_TYPE(type, all_types, all_values));
@@ -435,22 +472,31 @@ module Trxl
435
472
  require 'stdlib/inject';
436
473
  require 'stdlib/avg_sum_of_type';
437
474
  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])
475
+ inject(0, variable_range, fun(sum, variable) {
476
+ sum + avg_sum_of_type(type, all_types, ENV[variable])
440
477
  });
441
478
  };
442
479
  AVG_RANGE_SUM_OF_TYPE
443
-
480
+
444
481
  TOTAL_RANGE_SUM_OF_TYPE = <<-TOTAL_RANGE_SUM_OF_TYPE
445
482
  require 'stdlib/inject';
446
483
  require 'stdlib/sum_of_type';
447
484
  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])
485
+ inject(0, variable_range, fun(sum, variable) {
486
+ sum + sum_of_type(type, all_types, ENV[variable])
450
487
  });
451
488
  };
452
489
  TOTAL_RANGE_SUM_OF_TYPE
453
490
 
491
+ AVG_RANGE_SUM = <<-AVG_RANGE_SUM
492
+ require 'stdlib/inject';
493
+ avg_range_sum = fun(variable_range) {
494
+ inject(0, variable_range, fun(sum, variable) {
495
+ sum + AVG_SUM(ENV[variable])
496
+ });
497
+ };
498
+ AVG_RANGE_SUM
499
+
454
500
  YEAR_FROM_DATE = <<-YEAR_FROM_DATE
455
501
  year_from_date = fun(date) {
456
502
  date = SPLIT(date, '/');
@@ -469,7 +515,7 @@ module Trxl
469
515
  require 'stdlib/month_from_date';
470
516
  require 'stdlib/year_from_date';
471
517
  DATES
472
-
518
+
473
519
  RATIO = <<-RATIO
474
520
  require 'stdlib/foreach_in';
475
521
  ratio = fun(enumerable, true_condition, base_condition) {
@@ -492,14 +538,14 @@ module Trxl
492
538
  RATIO
493
539
 
494
540
  end
495
-
496
-
541
+
542
+
497
543
  class Calculator
498
-
499
- extend StdLib # optimized for performance
500
-
544
+
545
+ extend StdLib
546
+
501
547
  class << self
502
-
548
+
503
549
  def stdlib(function = nil)
504
550
  if function
505
551
  Kernel.eval("Trxl::StdLib::#{function.to_s.upcase}").strip
@@ -509,7 +555,7 @@ module Trxl
509
555
  end.strip
510
556
  end
511
557
  end
512
-
558
+
513
559
  end
514
560
 
515
561
  def initialize
@@ -523,19 +569,19 @@ module Trxl
523
569
  ast
524
570
  else
525
571
  failure_idx = @parser.failure_index
526
-
572
+
527
573
  # extract code snippet where parse error happened
528
574
  start = ((idx = failure_idx - 12) < 0 ? 0 : idx)
529
575
  stop = ((idx = failure_idx + 12) > code.size ? code.size : idx)
530
576
  local_code = code.slice(start..stop).to_s.gsub(/\n|\r/, "")
531
-
577
+
532
578
  msg = "Parse Error at index #{failure_idx} (showing excerpt):\n"
533
579
  msg << "... #{local_code} ...\n"
534
-
580
+
535
581
  # mark the exact offset where the parse error happened
536
582
  offset = (start == 0) ? failure_idx + 4 : 16
537
583
  offset.times { msg << ' '}; msg << "^\n"
538
-
584
+
539
585
  if verbose
540
586
  # show the originial trxl program
541
587
  msg << "Original Code:\n#{code}\n\n"
@@ -559,27 +605,27 @@ module Trxl
559
605
  end
560
606
 
561
607
  end
562
-
608
+
563
609
  class Interpreter
564
-
610
+
565
611
  attr_accessor :parser, :program, :env
566
-
612
+
567
613
  def initialize
568
614
  @parser = Calculator.new
569
615
  @program = []
570
616
  @env = env
571
617
  end
572
-
618
+
573
619
  def stash(loc)
574
620
  @program << loc
575
621
  end
576
-
622
+
577
623
  def eval(env = [])
578
624
  @parser.eval(@program.join(' '), env)
579
625
  end
580
-
626
+
581
627
  end
582
-
628
+
583
629
  end
584
630
 
585
631