minad-evaluator 0.1 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/evaluator.rb +101 -123
- data/test/test_evaluator.rb +20 -2
- 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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
'
|
12
|
-
'
|
13
|
-
'
|
14
|
-
'
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
'
|
19
|
-
'
|
20
|
-
'
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
54
|
-
elsif
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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
|
71
|
-
|
72
|
-
|
114
|
+
result << TOKENS.find {|x| tok =~ x[0] }[1][tok, vars]
|
115
|
+
unary = false
|
116
|
+
next
|
73
117
|
end
|
74
|
-
|
118
|
+
unary = true
|
75
119
|
end
|
76
|
-
|
77
|
-
|
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.
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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 :
|
154
|
-
|
132
|
+
private_class_method :infix, :prefix, :exec
|
155
133
|
end
|
156
134
|
|
157
135
|
def Evaluator(expr, vars = {})
|
data/test/test_evaluator.rb
CHANGED
@@ -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
|
115
|
-
assert_equal false, Evaluator('
|
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
|