minad-evaluator 0.1 → 0.1.1

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.
Files changed (3) hide show
  1. data/lib/evaluator.rb +101 -123
  2. data/test/test_evaluator.rb +20 -2
  3. metadata +1 -1
data/lib/evaluator.rb CHANGED
@@ -1,23 +1,67 @@
1
1
  require 'complex'
2
2
 
3
3
  module Evaluator
4
+ def self.infix(priority, unary = nil, &block) [false, priority, lambda(&block), unary] end
5
+ def self.prefix(&block) [true, 1e5, lambda(&block)] end
4
6
 
5
- VERSION = "0.1"
6
- INTEGER = /\d+/
7
- REAL = /(?:\d*\.\d+(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)/
8
- STRING = /'(?:[^']|\\')+'|"(?:[^"]|\\")+"/
9
- SYMBOL = /[\w_]+/
10
- FUNCTIONS = {
11
- 'sin' => 1, 'cos' => 1, 'tan' => 1, 'sinh' => 1, 'cosh' => 1, 'tanh' => 1, 'asin' => 1, 'acos' => 1, 'atan' => 1,
12
- 'asinh' => 1, 'atanh' => 1, 'sqrt' => 1, 'log' => 1, 'ln' => 1, 'log10' => 1, 'log2' => 1, 'exp' => 1,
13
- 'floor' => 1, 'ceil' => 1, 'string' => 1, 'int' => 1, 'float' => 1, 'rand' => 0, 'conj' => 1, 'im' => 1, 're' => 1, 'round' => 1,
14
- 'abs' => 1, 'minus' => 1, 'plus' => 1, 'not' => 1 }
15
- OPERATOR = [ %w(|| or), %w(&& and), %w(== != <= >= < >), %w(+ -),
16
- %w(<< >>), %w(& | ^), %w(* / % div mod), %w(**), %w(!) ]
17
- UNARY = {
18
- '+' => 'plus',
19
- '-' => 'minus',
20
- '!' => 'not'
7
+ VERSION = "0.1.1"
8
+ OPERATOR = {
9
+ '||' => infix(0) {|a,b| a || b },
10
+ 'or' => infix(0) {|a,b| a || b },
11
+ '&&' => infix(1) {|a,b| a && b },
12
+ 'and' => infix(1) {|a,b| a && b },
13
+ '==' => infix(2) {|a,b| a == b },
14
+ '!=' => infix(2) {|a,b| a != b },
15
+ '<=' => infix(2) {|a,b| a <= b },
16
+ '>=' => infix(2) {|a,b| a >= b },
17
+ '<' => infix(2) {|a,b| a < b },
18
+ '>' => infix(2) {|a,b| a > b },
19
+ '+' => infix(3, 'plus') {|a,b| a + b },
20
+ '-' => infix(3, 'minus') {|a,b| a - b },
21
+ '>>' => infix(4) {|a,b| a >> b },
22
+ '<<' => infix(4) {|a,b| a << b },
23
+ '&' => infix(5) {|a,b| a & b },
24
+ '|' => infix(5) {|a,b| a | b },
25
+ '^' => infix(5) {|a,b| a ^ b },
26
+ '*' => infix(6) {|a,b| a * b },
27
+ '/' => infix(6) {|a,b| a / b },
28
+ '%' => infix(6) {|a,b| a % b },
29
+ 'div' => infix(6) {|a,b| a.div b },
30
+ 'mod' => infix(6) {|a,b| a % b },
31
+ '**' => infix(7) {|a,b| a ** b },
32
+ 'sin' => prefix {|x| Math.sin(x) },
33
+ 'cos' => prefix {|x| Math.cos(x) },
34
+ 'tan' => prefix {|x| Math.tan(x) },
35
+ 'sinh' => prefix {|x| Math.sinh(x) },
36
+ 'cosh' => prefix {|x| Math.cosh(x) },
37
+ 'tanh' => prefix {|x| Math.tanh(x) },
38
+ 'asin' => prefix {|x| Math.asin(x) },
39
+ 'acos' => prefix {|x| Math.acos(x) },
40
+ 'atan' => prefix {|x| Math.atan(x) },
41
+ 'asinh' => prefix {|x| Math.asinh(x) },
42
+ 'atanh' => prefix {|x| Math.atanh(x) },
43
+ 'sqrt' => prefix {|x| Math.sqrt(x) },
44
+ 'log' => prefix {|x| Math.log(x) },
45
+ 'ln' => prefix {|x| Math.log(x) },
46
+ 'log10' => prefix {|x| Math.log10(x) },
47
+ 'log2' => prefix {|x| Math.log(x)/Math.log(2) },
48
+ 'exp' => prefix {|x| Math.exp(x) },
49
+ 'floor' => prefix {|x| x.floor },
50
+ 'ceil' => prefix {|x| x.ceil },
51
+ 'string' => prefix {|x| x.to_s },
52
+ 'int' => prefix {|x| x.to_i },
53
+ 'float' => prefix {|x| x.to_f },
54
+ 'rand' => prefix {|| rand },
55
+ 'conj' => prefix {|x| x.conj },
56
+ 'im' => prefix {|x| x.imag },
57
+ 're' => prefix {|x| x.real },
58
+ 'round' => prefix {|x| x.round },
59
+ 'abs' => prefix {|x| x.abs },
60
+ 'minus' => prefix {|x| -x },
61
+ 'plus' => prefix {|x| x },
62
+ '!' => prefix {|x| !x },
63
+ 'substr' => prefix {|x,a,b| x.slice(a,b) },
64
+ 'len' => prefix {|x| x.length }
21
65
  }
22
66
  CONSTANTS = {
23
67
  'true' => true,
@@ -27,131 +71,65 @@ module Evaluator
27
71
  'pi' => Math::PI,
28
72
  'i' => Complex::I
29
73
  }
30
-
31
- OP = {}
32
- (OPERATOR + [FUNCTIONS.keys]).each_with_index do |ops,i|
33
- ops.each { |op| OP[op] = i }
34
- end
35
-
36
- TOKENIZER = Regexp.new("#{REAL.source}|#{INTEGER.source}|#{STRING.source}|#{SYMBOL.source}|\\(|\\)|,|" +
37
- (OPERATOR + FUNCTIONS.keys).flatten.sort { |a,b| b.length <=> a.length}.map { |op| Regexp.quote(op) }.join('|'))
74
+ TOKENS = [
75
+ [ /^'(?:[^']|\\')+'|"(?:[^"]|\\")+"$/, lambda {|tok, vars| tok[1..-2] } ],
76
+ [ /^(?:\d*\.\d+(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/, lambda {|tok, vars| tok.to_f } ],
77
+ [ /^0[xX][\dA-Fa-f]+$/, lambda {|tok, vars| tok.to_i(16) } ],
78
+ [ /^0[0-7]+$/, lambda {|tok, vars| tok.to_i(8) } ],
79
+ [ /^\d+$/, lambda {|tok, vars| tok.to_i(10) } ],
80
+ [ /^[a-zA-Z_][\w_]*$/, lambda {|tok, vars|
81
+ tok.downcase!
82
+ raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
83
+ vars[tok]
84
+ }
85
+ ]
86
+ ]
87
+ TOKENIZER = Regexp.new((TOKENS.map {|x| x[0].source[1..-2] } +
88
+ OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |op| Regexp.quote(op) }).
89
+ join('|') + '|\\(|\\)|,')
38
90
 
39
91
  def self.eval(expr, vars = {})
40
- table = CONSTANTS.dup
41
- vars.each_pair {|k,v| table[k.to_s.downcase] = v }
42
- tokens = expr.scan(TOKENIZER)
43
- stack, post = [], []
44
- prev = nil
45
- tokens.each do |tok|
92
+ vars = Hash[*vars.map {|k,v| [k.to_s.downcase, v] }.flatten].merge(CONSTANTS)
93
+ stack, result, unary = [], [], true
94
+ expr.scan(TOKENIZER).each do |tok|
46
95
  if tok == '('
47
96
  stack << '('
48
97
  elsif tok == ')'
49
- op(post, stack.pop) while !stack.empty? && stack.last != '('
98
+ exec(result, stack.pop) while !stack.empty? && stack.last != '('
50
99
  raise(SyntaxError, "Unexpected token )") if stack.empty?
51
100
  stack.pop
52
101
  elsif tok == ','
53
- op(post, stack.pop) while !stack.empty? && stack.last != '('
54
- elsif FUNCTIONS.include?(tok.downcase)
55
- stack << tok.downcase
56
- elsif OP.include?(tok)
57
- if (prev == nil || OP.include?(prev)) && UNARY.include?(tok)
58
- stack << UNARY[tok]
102
+ exec(result, stack.pop) while !stack.empty? && stack.last != '('
103
+ elsif OPERATOR.include?(tok.downcase)
104
+ tok.downcase!
105
+ if OPERATOR[tok][0]
106
+ stack << tok
107
+ elsif unary && OPERATOR[tok][3]
108
+ stack << OPERATOR[tok][3]
59
109
  else
60
- op(post, stack.pop) while !stack.empty? && stack.last != '(' && OP[stack.last] >= OP[tok]
110
+ exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= OPERATOR[tok][1]
61
111
  stack << tok
62
112
  end
63
- elsif tok =~ STRING
64
- post << tok[1..-2]
65
- elsif tok =~ REAL
66
- post << tok.to_f
67
- elsif tok =~ INTEGER
68
- post << tok.to_i
69
113
  else
70
- tok.downcase!
71
- raise(NameError, "Symbol #{tok} is undefined") if !table.include?(tok)
72
- post << table[tok]
114
+ result << TOKENS.find {|x| tok =~ x[0] }[1][tok, vars]
115
+ unary = false
116
+ next
73
117
  end
74
- prev = tok
118
+ unary = true
75
119
  end
76
- op(post, stack.pop) while !stack.empty?
77
- post[0]
120
+ exec(result, stack.pop) while !stack.empty?
121
+ raise(SyntaxError, "Unexpected operands") if result.size != 1
122
+ result[0]
78
123
  end
79
124
 
80
- def self.op(stack, op)
81
- stack << \
82
- if FUNCTIONS.include?(op)
83
- args = FUNCTIONS[op]
84
- raise(SyntaxError, "Not enough operands on the stack") if stack.size < args
85
- a = stack.slice!(-args, args)
86
- case op
87
- when 'sin' then Math.sin(a[0])
88
- when 'cos' then Math.cos(a[0])
89
- when 'tan' then Math.tan(a[0])
90
- when 'sinh' then Math.sinh(a[0])
91
- when 'cosh' then Math.cosh(a[0])
92
- when 'tanh' then Math.tanh(a[0])
93
- when 'asin' then Math.asin(a[0])
94
- when 'acos' then Math.acos(a[0])
95
- when 'atan' then Math.atan(a[0])
96
- when 'asinh' then Math.asinh(a[0])
97
- when 'atanh' then Math.atanh(a[0])
98
- when 'sqrt' then Math.sqrt(a[0])
99
- when 'log' then Math.log(a[0])
100
- when 'ln' then Math.log(a[0])
101
- when 'log10' then Math.log10(a[0])
102
- when 'log2' then Math.log(a[0])/Math.log(2)
103
- when 'exp' then Math.exp(a[0])
104
- when 'floor' then a[0].floor
105
- when 'ceil' then a[0].ceil
106
- when 'string' then a[0].to_s
107
- when 'float' then a[0].to_f
108
- when 'int' then a[0].to_i
109
- when 'rand' then rand
110
- when 'conj' then a[0].conj
111
- when 'im' then a[0].imag
112
- when 're' then a[0].real
113
- when 'round' then a[0].round
114
- when 'abs' then a[0].abs
115
- when 'plus' then a[0]
116
- when 'minus' then -a[0]
117
- when 'not' then !a[0]
118
- end
119
- else
120
- raise(SyntaxError, "Not enough operands on the stack") if stack.size < 2
121
- b = stack.pop
122
- a = stack.pop
123
- case op
124
- when '||' then a || b
125
- when 'or' then a || b
126
- when '&&' then a && b
127
- when 'and' then a && b
128
- when '==' then a == b
129
- when '!=' then a != b
130
- when '<=' then a <= b
131
- when '>=' then a >= b
132
- when '<' then a < b
133
- when '>' then a > b
134
- when '+' then a + b
135
- when '-' then a - b
136
- when '*' then a * b
137
- when '/' then a / b
138
- when 'div' then a.div(b)
139
- when '%' then a % b
140
- when 'mod' then a % b
141
- when '**' then a ** b
142
- when '<<' then a << b
143
- when '>>' then a >> b
144
- when '&' then a & b
145
- when '|' then a | b
146
- when '^' then a ^ b
147
- else
148
- raise(SyntaxError, "Unexpected token #{op}")
149
- end
150
- end
125
+ def self.exec(result, op)
126
+ raise(SyntaxError, "Unexpected token #{op}") if !OPERATOR.include?(op)
127
+ op = OPERATOR[op][2]
128
+ raise(SyntaxError, "Not enough operands for #{op}") if result.size < op.arity
129
+ result << op[*result.slice!(-op.arity, op.arity)]
151
130
  end
152
131
 
153
- private_class_method :op
154
-
132
+ private_class_method :infix, :prefix, :exec
155
133
  end
156
134
 
157
135
  def Evaluator(expr, vars = {})
@@ -111,8 +111,10 @@ class TestEvaluator < Test::Unit::TestCase
111
111
  assert_equal 4, Evaluator('round(3.5)')
112
112
  assert_equal 6, Evaluator('abs -6')
113
113
  assert_equal 3, Evaluator('plus 3')
114
- assert_equal -3, Evaluator('minus 3')
115
- assert_equal false, Evaluator('not 3')
114
+ assert_equal(-3, Evaluator('minus 3'))
115
+ assert_equal false, Evaluator('!3')
116
+ assert_equal 'bcd', Evaluator('substr("abcde", 1, 3)')
117
+ assert_equal 4, Evaluator('len("abcd")')
116
118
  end
117
119
 
118
120
  def test_variables
@@ -120,4 +122,20 @@ class TestEvaluator < Test::Unit::TestCase
120
122
  assert_equal 14, Evaluator('a+b*C', 'A' => 2, 'b' => 3, :c => 4)
121
123
  assert_equal 14, Evaluator('alpha+beta*GAMMA', 'ALPHA' => 2, 'bEtA' => 3, 'gamma' => 4)
122
124
  end
125
+
126
+ def test_errors
127
+ assert_raise(SyntaxError) { Evaluator('(((((((((((((3+3))') }
128
+ assert_raise(SyntaxError) { Evaluator('1+2)') }
129
+ assert_raise(SyntaxError) { Evaluator('1+2+3+') }
130
+ assert_raise(SyntaxError) { Evaluator('1 + floor') }
131
+ assert_raise(NameError) { Evaluator('42*a+3') }
132
+ assert_raise(NameError) { Evaluator('abc10') }
133
+ assert_raise(NameError) { Evaluator('abc10d') }
134
+ end
135
+
136
+ def test_numbers
137
+ assert_equal 0xABCDEF123, Evaluator('0xABCDEF123')
138
+ assert_equal 01234, Evaluator('01234')
139
+ assert_equal 234, Evaluator('234 ')
140
+ end
123
141
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minad-evaluator
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.1"
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Mendler