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