trxl 0.1.5 → 0.1.8

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