minad-evaluator 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/evaluator.rb +103 -80
- data/test/test_evaluator.rb +27 -1
- metadata +1 -1
data/lib/evaluator.rb
CHANGED
@@ -4,64 +4,84 @@ module Evaluator
|
|
4
4
|
def self.infix(priority, unary = nil, &block) [false, priority, lambda(&block), unary] end
|
5
5
|
def self.prefix(&block) [true, 1e5, lambda(&block)] end
|
6
6
|
|
7
|
-
VERSION = "0.1.
|
7
|
+
VERSION = "0.1.2"
|
8
8
|
OPERATOR = {
|
9
|
-
'||'
|
10
|
-
'
|
11
|
-
'
|
12
|
-
'
|
13
|
-
'
|
14
|
-
'
|
15
|
-
'
|
16
|
-
'
|
17
|
-
'
|
18
|
-
'
|
19
|
-
'
|
20
|
-
'
|
21
|
-
'
|
22
|
-
'
|
23
|
-
'
|
24
|
-
'
|
25
|
-
'
|
26
|
-
'
|
27
|
-
'
|
28
|
-
'
|
29
|
-
'
|
30
|
-
'
|
31
|
-
'
|
32
|
-
'
|
33
|
-
'
|
34
|
-
'
|
35
|
-
'
|
36
|
-
'
|
37
|
-
'
|
38
|
-
'
|
39
|
-
'
|
40
|
-
'
|
41
|
-
'
|
42
|
-
'
|
43
|
-
'
|
44
|
-
'
|
45
|
-
'
|
46
|
-
'
|
47
|
-
'
|
48
|
-
'
|
49
|
-
'floor'
|
50
|
-
'ceil'
|
51
|
-
'string'
|
52
|
-
'int'
|
53
|
-
'float'
|
54
|
-
'rand'
|
55
|
-
'conj'
|
56
|
-
'im'
|
57
|
-
're'
|
58
|
-
'round'
|
59
|
-
'abs'
|
60
|
-
'minus'
|
61
|
-
'plus'
|
62
|
-
'!'
|
63
|
-
'
|
64
|
-
'
|
9
|
+
'||' => infix(0) {|a,b| a || b },
|
10
|
+
'&&' => infix(1) {|a,b| a && b },
|
11
|
+
'==' => infix(2) {|a,b| a == b },
|
12
|
+
'!=' => infix(2) {|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(3, 'plus') {|a,b| a + b },
|
18
|
+
'-' => infix(3, 'minus') {|a,b| a - b },
|
19
|
+
'>>' => infix(4) {|a,b| a >> b },
|
20
|
+
'<<' => infix(4) {|a,b| a << b },
|
21
|
+
'&' => infix(5) {|a,b| a & b },
|
22
|
+
'|' => infix(5) {|a,b| a | b },
|
23
|
+
'^' => infix(5) {|a,b| a ^ b },
|
24
|
+
'*' => infix(6) {|a,b| a * b },
|
25
|
+
'/' => infix(6) {|a,b| a / b },
|
26
|
+
'%' => infix(6) {|a,b| a % b },
|
27
|
+
'div' => infix(6) {|a,b| a.div b },
|
28
|
+
'**' => infix(7) {|a,b| a ** b },
|
29
|
+
'gcd' => prefix {|x,y| x.gcd(y) },
|
30
|
+
'lcm' => prefix {|x,y| x.lcm(y) },
|
31
|
+
'sin' => prefix {|x| Math.sin(x) },
|
32
|
+
'cos' => prefix {|x| Math.cos(x) },
|
33
|
+
'tan' => prefix {|x| Math.tan(x) },
|
34
|
+
'sinh' => prefix {|x| Math.sinh(x) },
|
35
|
+
'cosh' => prefix {|x| Math.cosh(x) },
|
36
|
+
'tanh' => prefix {|x| Math.tanh(x) },
|
37
|
+
'asin' => prefix {|x| Math.asin(x) },
|
38
|
+
'acos' => prefix {|x| Math.acos(x) },
|
39
|
+
'atan' => prefix {|x| Math.atan(x) },
|
40
|
+
'asinh' => prefix {|x| Math.asinh(x) },
|
41
|
+
'atanh' => prefix {|x| Math.atanh(x) },
|
42
|
+
'sqrt' => prefix {|x| Math.sqrt(x) },
|
43
|
+
'log' => prefix {|x| Math.log(x) },
|
44
|
+
'log10' => prefix {|x| Math.log10(x) },
|
45
|
+
'log2' => prefix {|x| Math.log(x)/Math.log(2) },
|
46
|
+
'exp' => prefix {|x| Math.exp(x) },
|
47
|
+
'erf' => prefix {|x| Math.erf(x) },
|
48
|
+
'erfc' => prefix {|x| Math.erfc(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
|
+
'~' => prefix {|x| ~x },
|
64
|
+
'substr' => prefix {|x,a,b| x.slice(a,b) },
|
65
|
+
'len' => prefix {|x| x.length },
|
66
|
+
'tolower' => prefix {|x| x.downcase },
|
67
|
+
'toupper' => prefix {|x| x.upcase },
|
68
|
+
'strip' => prefix {|x| x.strip },
|
69
|
+
'reverse' => prefix {|x| x.reverse },
|
70
|
+
'index' => prefix {|x,y| x.index(y) },
|
71
|
+
'rindex' => prefix {|x,y| x.rindex(y) },
|
72
|
+
'or' => '||',
|
73
|
+
'and' => '&&',
|
74
|
+
'mod' => '%',
|
75
|
+
'ln' => 'log',
|
76
|
+
'imag' => 'im',
|
77
|
+
'real' => 're',
|
78
|
+
'count' => 'len',
|
79
|
+
'size' => 'len',
|
80
|
+
'length' => 'len',
|
81
|
+
'trim' => 'strip',
|
82
|
+
'downcase' => 'tolower',
|
83
|
+
'upcase' => 'toupper',
|
84
|
+
'slice' => 'substr',
|
65
85
|
}
|
66
86
|
CONSTANTS = {
|
67
87
|
'true' => true,
|
@@ -71,22 +91,15 @@ module Evaluator
|
|
71
91
|
'pi' => Math::PI,
|
72
92
|
'i' => Complex::I
|
73
93
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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('|') + '|\\(|\\)|,')
|
94
|
+
STRING = /^(?:'(?:\\'|[^'])*'|"(?:\\"|[^"])*")$/
|
95
|
+
REAL = /^(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
|
96
|
+
HEX = /^0[xX][\dA-Fa-f]+$/
|
97
|
+
OCT = /^0[0-7]+$/
|
98
|
+
DEC = /^\d+$/
|
99
|
+
SYMBOL = /^[a-zA-Z_][\w_]*$/
|
100
|
+
VALUE_TOKENS = [STRING, REAL, HEX, OCT, DEC, SYMBOL].map {|x| x.source[1..-2] }
|
101
|
+
OPERATOR_TOKENS = OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |x| Regexp.quote(x) }
|
102
|
+
TOKENIZER = Regexp.new((VALUE_TOKENS + OPERATOR_TOKENS + ['\\(', '\\)', ',']).join('|'))
|
90
103
|
|
91
104
|
def self.eval(expr, vars = {})
|
92
105
|
vars = Hash[*vars.map {|k,v| [k.to_s.downcase, v] }.flatten].merge(CONSTANTS)
|
@@ -100,18 +113,28 @@ module Evaluator
|
|
100
113
|
stack.pop
|
101
114
|
elsif tok == ','
|
102
115
|
exec(result, stack.pop) while !stack.empty? && stack.last != '('
|
103
|
-
elsif OPERATOR
|
104
|
-
tok.downcase
|
105
|
-
if
|
116
|
+
elsif operator = OPERATOR[tok.downcase]
|
117
|
+
tok = String === operator ? operator : tok.downcase
|
118
|
+
if operator[0]
|
106
119
|
stack << tok
|
107
|
-
elsif unary &&
|
108
|
-
stack <<
|
120
|
+
elsif unary && operator[3]
|
121
|
+
stack << operator[3]
|
109
122
|
else
|
110
|
-
exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >=
|
123
|
+
exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= operator[1]
|
111
124
|
stack << tok
|
112
125
|
end
|
113
126
|
else
|
114
|
-
result <<
|
127
|
+
result << case tok
|
128
|
+
when STRING then tok[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'")
|
129
|
+
when REAL then tok.to_f
|
130
|
+
when HEX then tok.to_i(16)
|
131
|
+
when OCT then tok.to_i(8)
|
132
|
+
when DEC then tok.to_i(10)
|
133
|
+
when SYMBOL
|
134
|
+
tok.downcase!
|
135
|
+
raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
|
136
|
+
vars[tok]
|
137
|
+
end
|
115
138
|
unary = false
|
116
139
|
next
|
117
140
|
end
|
data/test/test_evaluator.rb
CHANGED
@@ -78,7 +78,10 @@ class TestEvaluator < Test::Unit::TestCase
|
|
78
78
|
assert_equal nil, Evaluator('niL')
|
79
79
|
end
|
80
80
|
|
81
|
-
def
|
81
|
+
def test_numeric_functions
|
82
|
+
assert_equal 13, Evaluator('gcd(26, 39)')
|
83
|
+
assert_equal 78, Evaluator('lcm(26, 39)')
|
84
|
+
assert_equal Math.cos(42), Evaluator('cos 42')
|
82
85
|
assert_equal Math.sin(42), Evaluator('sin 42')
|
83
86
|
assert_equal Math.cos(42), Evaluator('cos 42')
|
84
87
|
assert_equal Math.tan(42), Evaluator('tan 42')
|
@@ -96,6 +99,8 @@ class TestEvaluator < Test::Unit::TestCase
|
|
96
99
|
assert_equal Math.log10(42) + 3, Evaluator('log10 42 + 3')
|
97
100
|
assert_equal Math.log(42)/Math.log(2) + 3, Evaluator('log2 42 + 3')
|
98
101
|
assert_equal 3 * Math.exp(42), Evaluator('3 * exp 42')
|
102
|
+
assert_equal Math.erf(2), Evaluator('erf 2')
|
103
|
+
assert_equal Math.erfc(2), Evaluator('erfc 2')
|
99
104
|
assert_equal 42, Evaluator('floor 42.3')
|
100
105
|
assert_equal 42, Evaluator('ceil 41.6')
|
101
106
|
assert_equal 3.5, Evaluator('float("3.5")')
|
@@ -113,8 +118,16 @@ class TestEvaluator < Test::Unit::TestCase
|
|
113
118
|
assert_equal 3, Evaluator('plus 3')
|
114
119
|
assert_equal(-3, Evaluator('minus 3'))
|
115
120
|
assert_equal false, Evaluator('!3')
|
121
|
+
assert_equal ~3, Evaluator('~3')
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_string_functions
|
116
125
|
assert_equal 'bcd', Evaluator('substr("abcde", 1, 3)')
|
117
126
|
assert_equal 4, Evaluator('len("abcd")')
|
127
|
+
assert_equal 'abc', Evaluator('strip " abc "')
|
128
|
+
assert_equal 'cba', Evaluator('reverse "abc"')
|
129
|
+
assert_equal 2, Evaluator('index("abcdefg", "cde")')
|
130
|
+
assert_equal 7, Evaluator('rindex("abcdefgcdef", "cde")')
|
118
131
|
end
|
119
132
|
|
120
133
|
def test_variables
|
@@ -137,5 +150,18 @@ class TestEvaluator < Test::Unit::TestCase
|
|
137
150
|
assert_equal 0xABCDEF123, Evaluator('0xABCDEF123')
|
138
151
|
assert_equal 01234, Evaluator('01234')
|
139
152
|
assert_equal 234, Evaluator('234 ')
|
153
|
+
assert_equal 0.123, Evaluator('.123')
|
154
|
+
assert_equal 0.123, Evaluator('0.123')
|
155
|
+
assert_equal 123.0, Evaluator('123.')
|
156
|
+
assert_equal 123e-42, Evaluator('123e-42')
|
157
|
+
assert_equal 0.123e-42, Evaluator('.123e-42')
|
158
|
+
assert_equal 2.123e-42, Evaluator('2.123e-42')
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_strings
|
162
|
+
assert_equal "abc'a", Evaluator('"abc\'a"')
|
163
|
+
assert_equal 'abc"a', Evaluator('"abc\"a"')
|
164
|
+
assert_equal 'abc"a', Evaluator("'abc\"a'")
|
165
|
+
assert_equal "abc'a", Evaluator("'abc\\'a'")
|
140
166
|
end
|
141
167
|
end
|