minad-evaluator 0.1.3 → 0.1.5
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.markdown +17 -1
- data/Rakefile +2 -2
- data/lib/evaluator.rb +66 -41
- data/test/test_evaluator.rb +2 -0
- metadata +1 -1
data/README.markdown
CHANGED
@@ -12,8 +12,24 @@ Usage
|
|
12
12
|
|
13
13
|
See the test cases for more examples.
|
14
14
|
|
15
|
+
Calculator
|
16
|
+
----------
|
17
|
+
|
18
|
+
A small calculator program (calc.rb) is provided with this library. You can use
|
19
|
+
it as follows:
|
20
|
+
|
21
|
+
$ ./calc.rb
|
22
|
+
> number := 10
|
23
|
+
10
|
24
|
+
> number * 3
|
25
|
+
30
|
26
|
+
> 1 [joule] in [MeV]
|
27
|
+
6241509647120.42 MeV
|
28
|
+
|
29
|
+
The calculator loads a few natural constants at startup (calc.startup). For unit support
|
30
|
+
my minad-units library is used. Units are denoted in brackets e.g. [meter], [kV] etc
|
31
|
+
|
15
32
|
Authors
|
16
33
|
-------
|
17
34
|
|
18
35
|
Daniel Mendler
|
19
|
-
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require 'hoe'
|
|
3
3
|
$:.unshift 'lib'
|
4
4
|
require 'evaluator'
|
5
5
|
|
6
|
-
Hoe.new 'evaluator', Evaluator::VERSION do |
|
7
|
-
|
6
|
+
Hoe.new 'evaluator', Evaluator::VERSION do |evaluator|
|
7
|
+
evaluator.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
|
8
8
|
end
|
9
9
|
|
data/lib/evaluator.rb
CHANGED
@@ -1,31 +1,39 @@
|
|
1
1
|
require 'complex'
|
2
|
+
begin
|
3
|
+
require 'units'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
2
6
|
|
3
7
|
module Evaluator
|
4
8
|
def self.infix(priority, unary = nil, &block) [false, priority, lambda(&block), unary] end
|
5
9
|
def self.prefix(&block) [true, 1e5, lambda(&block)] end
|
6
10
|
|
7
|
-
VERSION =
|
11
|
+
VERSION = '0.1.5'
|
8
12
|
OPERATOR = {
|
9
|
-
'
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
'
|
14
|
-
'
|
15
|
-
'
|
16
|
-
'
|
17
|
-
'
|
18
|
-
'
|
19
|
-
'
|
20
|
-
'
|
21
|
-
'
|
22
|
-
'
|
23
|
-
'
|
24
|
-
'
|
25
|
-
'
|
26
|
-
'
|
27
|
-
'
|
28
|
-
'
|
13
|
+
'in' => infix(0) do |a,b|
|
14
|
+
raise(RuntimeError, 'Unit support not available') if !a.respond_to? :in
|
15
|
+
a.in(b)
|
16
|
+
end,
|
17
|
+
'||' => infix(1) {|a,b| a || b },
|
18
|
+
'&&' => infix(2) {|a,b| a && b },
|
19
|
+
'==' => infix(3) {|a,b| a == b },
|
20
|
+
'!=' => infix(3) {|a,b| a != b },
|
21
|
+
'<=' => infix(3) {|a,b| a <= b },
|
22
|
+
'>=' => infix(3) {|a,b| a >= b },
|
23
|
+
'<' => infix(3) {|a,b| a < b },
|
24
|
+
'>' => infix(3) {|a,b| a > b },
|
25
|
+
'+' => infix(4, 'plus') {|a,b| a + b },
|
26
|
+
'-' => infix(4, 'minus') {|a,b| a - b },
|
27
|
+
'>>' => infix(5) {|a,b| a >> b },
|
28
|
+
'<<' => infix(5) {|a,b| a << b },
|
29
|
+
'&' => infix(6) {|a,b| a & b },
|
30
|
+
'|' => infix(6) {|a,b| a | b },
|
31
|
+
'^' => infix(6) {|a,b| a ^ b },
|
32
|
+
'*' => infix(7) {|a,b| a * b },
|
33
|
+
'/' => infix(7) {|a,b| a / b },
|
34
|
+
'%' => infix(7) {|a,b| a % b },
|
35
|
+
'div' => infix(7) {|a,b| a.div b },
|
36
|
+
'**' => infix(8) {|a,b| a ** b },
|
29
37
|
'gcd' => prefix {|x,y| x.gcd(y) },
|
30
38
|
'lcm' => prefix {|x,y| x.lcm(y) },
|
31
39
|
'sin' => prefix {|x| Math.sin(x) },
|
@@ -69,6 +77,7 @@ module Evaluator
|
|
69
77
|
'reverse' => prefix {|x| x.reverse },
|
70
78
|
'index' => prefix {|x,y| x.index(y) },
|
71
79
|
'rindex' => prefix {|x,y| x.rindex(y) },
|
80
|
+
'=' => '==',
|
72
81
|
'or' => '||',
|
73
82
|
'and' => '&&',
|
74
83
|
'mod' => '%',
|
@@ -89,7 +98,7 @@ module Evaluator
|
|
89
98
|
'nil' => nil,
|
90
99
|
'e' => Math::E,
|
91
100
|
'pi' => Math::PI,
|
92
|
-
'i' => Complex::I
|
101
|
+
'i' => Complex::I,
|
93
102
|
}
|
94
103
|
STRING = /^(?:'(?:\\'|[^'])*'|"(?:\\"|[^"])*")$/
|
95
104
|
REAL = /^(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
|
@@ -97,48 +106,64 @@ module Evaluator
|
|
97
106
|
OCT = /^0[0-7]+$/
|
98
107
|
DEC = /^\d+$/
|
99
108
|
SYMBOL = /^[a-zA-Z_][\w_]*$/
|
100
|
-
|
109
|
+
UNIT = /^\[[^\]]+\]$/
|
110
|
+
VALUE_TOKENS = [UNIT, STRING, REAL, HEX, OCT, DEC, SYMBOL].map {|x| x.source[1..-2] }
|
101
111
|
OPERATOR_TOKENS = OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |x| Regexp.quote(x) }
|
102
112
|
TOKENIZER = Regexp.new((VALUE_TOKENS + OPERATOR_TOKENS + ['\\(', '\\)', ',']).join('|'))
|
103
113
|
|
104
114
|
def self.eval(expr, vars = {})
|
105
|
-
vars = Hash[*vars.map {|k,v| [k.to_s.downcase, v] }.flatten]
|
115
|
+
vars = Hash[*vars.merge(CONSTANTS).map {|k,v| [k.to_s.downcase, v] }.flatten]
|
106
116
|
stack, result, unary = [], [], true
|
107
117
|
expr.to_s.scan(TOKENIZER).each do |tok|
|
108
118
|
if tok == '('
|
109
119
|
stack << '('
|
120
|
+
unary = true
|
110
121
|
elsif tok == ')'
|
111
122
|
exec(result, stack.pop) while !stack.empty? && stack.last != '('
|
112
|
-
raise(SyntaxError,
|
123
|
+
raise(SyntaxError, 'Unexpected token )') if stack.empty?
|
113
124
|
stack.pop
|
125
|
+
unary = false
|
114
126
|
elsif tok == ','
|
115
127
|
exec(result, stack.pop) while !stack.empty? && stack.last != '('
|
128
|
+
unary = true
|
116
129
|
elsif operator = OPERATOR[tok.downcase]
|
130
|
+
# Check for alias
|
117
131
|
tok = String === operator ? operator : tok.downcase
|
132
|
+
operator = OPERATOR[tok]
|
118
133
|
if operator[0]
|
134
|
+
# Prefix operator
|
119
135
|
stack << tok
|
120
136
|
elsif unary && operator[3]
|
137
|
+
# Alternative prefix operator
|
121
138
|
stack << operator[3]
|
122
139
|
else
|
140
|
+
# Infix operator
|
123
141
|
exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= operator[1]
|
124
142
|
stack << tok
|
125
143
|
end
|
144
|
+
unary = true
|
126
145
|
else
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
146
|
+
val = case tok
|
147
|
+
when UNIT
|
148
|
+
if tok.respond_to? :to_unit
|
149
|
+
tok[1..-2].to_unit
|
150
|
+
else
|
151
|
+
raise(RuntimeError, 'Unit support not available')
|
152
|
+
end
|
153
|
+
when STRING then tok[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'")
|
154
|
+
when REAL then tok.to_f
|
155
|
+
when HEX then tok.to_i(16)
|
156
|
+
when OCT then tok.to_i(8)
|
157
|
+
when DEC then tok.to_i(10)
|
158
|
+
when SYMBOL
|
159
|
+
tok.downcase!
|
160
|
+
raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
|
161
|
+
vars[tok]
|
162
|
+
end
|
163
|
+
stack << '*' if !unary
|
164
|
+
result << val
|
138
165
|
unary = false
|
139
|
-
next
|
140
166
|
end
|
141
|
-
unary = true
|
142
167
|
end
|
143
168
|
exec(result, stack.pop) while !stack.empty?
|
144
169
|
result.last
|
@@ -146,9 +171,9 @@ module Evaluator
|
|
146
171
|
|
147
172
|
def self.exec(result, op)
|
148
173
|
raise(SyntaxError, "Unexpected token #{op}") if !OPERATOR.include?(op)
|
149
|
-
|
150
|
-
raise(SyntaxError, "Not enough operands for #{op}") if result.size <
|
151
|
-
result <<
|
174
|
+
fn = OPERATOR[op][2]
|
175
|
+
raise(SyntaxError, "Not enough operands for #{op}") if result.size < fn.arity
|
176
|
+
result << fn[*result.slice!(-fn.arity, fn.arity)]
|
152
177
|
end
|
153
178
|
|
154
179
|
private_class_method :infix, :prefix, :exec
|
data/test/test_evaluator.rb
CHANGED
@@ -30,6 +30,8 @@ class TestEvaluator < Test::Unit::TestCase
|
|
30
30
|
|
31
31
|
assert_equal 3, Evaluator('1+2')
|
32
32
|
assert_equal 'xyz', Evaluator('"x"+"yz"')
|
33
|
+
assert_equal 'a1b', Evaluator("'a'+string 1+'b'")
|
34
|
+
assert_equal 'a1b', Evaluator("'a'+string(1)+'b'")
|
33
35
|
|
34
36
|
assert_equal 3, Evaluator('5-2')
|
35
37
|
assert_equal(-1, Evaluator('3-4'))
|