minad-evaluator 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +5 -0
- data/README.markdown +19 -0
- data/Rakefile +9 -0
- data/lib/evaluator.rb +159 -0
- data/test/test_evaluator.rb +123 -0
- metadata +68 -0
data/Manifest.txt
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
README
|
2
|
+
======
|
3
|
+
|
4
|
+
Evaluator is a mathematical expression evaluator for infix notation. It supports variables and functions.
|
5
|
+
|
6
|
+
Usage
|
7
|
+
-----
|
8
|
+
|
9
|
+
require 'evaluator'
|
10
|
+
puts Evaluator('1+1')
|
11
|
+
puts Evaluator('sin pi')
|
12
|
+
|
13
|
+
See the test cases for more examples.
|
14
|
+
|
15
|
+
Authors
|
16
|
+
-------
|
17
|
+
|
18
|
+
Daniel Mendler
|
19
|
+
|
data/Rakefile
ADDED
data/lib/evaluator.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'complex'
|
2
|
+
|
3
|
+
module Evaluator
|
4
|
+
|
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'
|
21
|
+
}
|
22
|
+
CONSTANTS = {
|
23
|
+
'true' => true,
|
24
|
+
'false' => false,
|
25
|
+
'nil' => nil,
|
26
|
+
'e' => Math::E,
|
27
|
+
'pi' => Math::PI,
|
28
|
+
'i' => Complex::I
|
29
|
+
}
|
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('|'))
|
38
|
+
|
39
|
+
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|
|
46
|
+
if tok == '('
|
47
|
+
stack << '('
|
48
|
+
elsif tok == ')'
|
49
|
+
op(post, stack.pop) while !stack.empty? && stack.last != '('
|
50
|
+
raise(SyntaxError, "Unexpected token )") if stack.empty?
|
51
|
+
stack.pop
|
52
|
+
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]
|
59
|
+
else
|
60
|
+
op(post, stack.pop) while !stack.empty? && stack.last != '(' && OP[stack.last] >= OP[tok]
|
61
|
+
stack << tok
|
62
|
+
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
|
+
else
|
70
|
+
tok.downcase!
|
71
|
+
raise(NameError, "Symbol #{tok} is undefined") if !table.include?(tok)
|
72
|
+
post << table[tok]
|
73
|
+
end
|
74
|
+
prev = tok
|
75
|
+
end
|
76
|
+
op(post, stack.pop) while !stack.empty?
|
77
|
+
post[0]
|
78
|
+
end
|
79
|
+
|
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
|
151
|
+
end
|
152
|
+
|
153
|
+
private_class_method :op
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
def Evaluator(expr, vars = {})
|
158
|
+
Evaluator.eval(expr, vars)
|
159
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'evaluator'
|
3
|
+
|
4
|
+
class TestEvaluator < Test::Unit::TestCase
|
5
|
+
def test_binary_operators
|
6
|
+
assert_equal 42, Evaluator('42 || false')
|
7
|
+
assert_equal true, Evaluator('false || nil || true')
|
8
|
+
assert_equal 42, Evaluator('42 or false')
|
9
|
+
assert_equal true, Evaluator('false or nil or true')
|
10
|
+
|
11
|
+
assert_equal 42, Evaluator('true && 42')
|
12
|
+
assert_equal nil, Evaluator('nil && 1')
|
13
|
+
assert_equal 42, Evaluator('true and 42')
|
14
|
+
assert_equal nil, Evaluator('nil and 1')
|
15
|
+
|
16
|
+
assert Evaluator('1+1==2')
|
17
|
+
assert Evaluator("'abc' == 'a'+'b'+'c'")
|
18
|
+
|
19
|
+
assert Evaluator('1+1 != 3')
|
20
|
+
assert Evaluator("'xxx' != 'a'+'b'+'c'")
|
21
|
+
|
22
|
+
assert Evaluator('1<=1')
|
23
|
+
assert Evaluator('1<=2')
|
24
|
+
assert Evaluator('1>=1')
|
25
|
+
assert Evaluator('2>=1')
|
26
|
+
assert(!Evaluator('1<1'))
|
27
|
+
assert Evaluator('1<2')
|
28
|
+
assert(!Evaluator('1>1'))
|
29
|
+
assert Evaluator('2>1')
|
30
|
+
|
31
|
+
assert_equal 3, Evaluator('1+2')
|
32
|
+
assert_equal 'xyz', Evaluator('"x"+"yz"')
|
33
|
+
|
34
|
+
assert_equal 3, Evaluator('5-2')
|
35
|
+
assert_equal(-1, Evaluator('3-4'))
|
36
|
+
|
37
|
+
assert_equal 12, Evaluator('3*4')
|
38
|
+
assert_equal 'ababab', Evaluator('"ab"*3')
|
39
|
+
|
40
|
+
assert_equal 3, Evaluator('12/4')
|
41
|
+
|
42
|
+
assert_equal 10, Evaluator('103. div 10.')
|
43
|
+
|
44
|
+
assert_equal 3, Evaluator('7 mod 4')
|
45
|
+
assert_equal 3, Evaluator('7 % 4')
|
46
|
+
|
47
|
+
assert_equal 1024, Evaluator('2 ** 10')
|
48
|
+
|
49
|
+
assert_equal 8, Evaluator('2 << 2')
|
50
|
+
assert_equal 32, Evaluator('256 >> 3')
|
51
|
+
|
52
|
+
assert_equal 2, Evaluator('6 & 2')
|
53
|
+
assert_equal 7, Evaluator('1 | 2 | 4')
|
54
|
+
assert_equal 1, Evaluator('9 ^ 8')
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_unary_operators
|
58
|
+
assert_equal(-1, Evaluator('-1'))
|
59
|
+
assert_equal(-2, Evaluator('-(1+1)'))
|
60
|
+
assert_equal(-42, Evaluator('---42'))
|
61
|
+
assert_equal 42, Evaluator('----42')
|
62
|
+
assert_equal(-9, Evaluator('3*-3'))
|
63
|
+
assert_equal(9, Evaluator('3*+3'))
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_precendence
|
67
|
+
assert_equal 16, Evaluator('1+3*5')
|
68
|
+
assert_equal 16, Evaluator('3*5+1')
|
69
|
+
assert_equal 23, Evaluator('3*5+2**3')
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_constants
|
73
|
+
assert_equal Math::PI, Evaluator('PI')
|
74
|
+
assert_equal Math::E, Evaluator('E')
|
75
|
+
assert_equal Complex::I, Evaluator('I')
|
76
|
+
assert_equal true, Evaluator('trUe')
|
77
|
+
assert_equal false, Evaluator('fAlSe')
|
78
|
+
assert_equal nil, Evaluator('niL')
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_functions
|
82
|
+
assert_equal Math.sin(42), Evaluator('sin 42')
|
83
|
+
assert_equal Math.cos(42), Evaluator('cos 42')
|
84
|
+
assert_equal Math.tan(42), Evaluator('tan 42')
|
85
|
+
assert_equal Math.sinh(42), Evaluator('sinh 42')
|
86
|
+
assert_equal Math.cosh(42), Evaluator('cosh 42')
|
87
|
+
assert_equal Math.tanh(42), Evaluator('tanh 42')
|
88
|
+
assert_equal Math.asin(0.5), Evaluator('asin .5')
|
89
|
+
assert_equal Math.acos(0.5), Evaluator('acos .5')
|
90
|
+
assert_equal Math.atan(0.5), Evaluator('atan .5')
|
91
|
+
assert_equal Math.asinh(0.5), Evaluator('asinh .5')
|
92
|
+
assert_equal Math.atanh(0.5), Evaluator('atanh .5')
|
93
|
+
assert_equal Math.sqrt(42) + 1, Evaluator('sqrt 42 + 1')
|
94
|
+
assert_equal Math.log(42) + 3, Evaluator('log 42 + 3')
|
95
|
+
assert_equal Math.log(42) + 3, Evaluator('ln 42 + 3')
|
96
|
+
assert_equal Math.log10(42) + 3, Evaluator('log10 42 + 3')
|
97
|
+
assert_equal Math.log(42)/Math.log(2) + 3, Evaluator('log2 42 + 3')
|
98
|
+
assert_equal 3 * Math.exp(42), Evaluator('3 * exp 42')
|
99
|
+
assert_equal 42, Evaluator('floor 42.3')
|
100
|
+
assert_equal 42, Evaluator('ceil 41.6')
|
101
|
+
assert_equal 3.5, Evaluator('float("3.5")')
|
102
|
+
assert_equal "3.5", Evaluator('string(3.5)')
|
103
|
+
assert_equal 3, Evaluator('int("3.5")')
|
104
|
+
assert_equal 3, Evaluator('int(3.6)')
|
105
|
+
srand(42); x = rand; srand(42)
|
106
|
+
assert_equal x, Evaluator('rand')
|
107
|
+
assert_equal Complex(1,-2), Evaluator('conj(1+2*i)')
|
108
|
+
assert_equal 2, Evaluator('Im(1+2*i)')
|
109
|
+
assert_equal 1, Evaluator('Re(1+2*i)')
|
110
|
+
assert_equal 3, Evaluator('round(3.4)')
|
111
|
+
assert_equal 4, Evaluator('round(3.5)')
|
112
|
+
assert_equal 6, Evaluator('abs -6')
|
113
|
+
assert_equal 3, Evaluator('plus 3')
|
114
|
+
assert_equal -3, Evaluator('minus 3')
|
115
|
+
assert_equal false, Evaluator('not 3')
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_variables
|
119
|
+
assert_equal 14, Evaluator('a+b*c', :a => 2, :b => 3, :c => 4)
|
120
|
+
assert_equal 14, Evaluator('a+b*C', 'A' => 2, 'b' => 3, :c => 4)
|
121
|
+
assert_equal 14, Evaluator('alpha+beta*GAMMA', 'ALPHA' => 2, 'bEtA' => 3, 'gamma' => 4)
|
122
|
+
end
|
123
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: minad-evaluator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Mendler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-13 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.3
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email:
|
27
|
+
- mail@daniel-mendler.de
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- Manifest.txt
|
34
|
+
files:
|
35
|
+
- lib/evaluator.rb
|
36
|
+
- test/test_evaluator.rb
|
37
|
+
- README.markdown
|
38
|
+
- Rakefile
|
39
|
+
- Manifest.txt
|
40
|
+
has_rdoc: true
|
41
|
+
homepage:
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --main
|
45
|
+
- README.markdown
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project: evaluator
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: Mathematical expression evaluator
|
67
|
+
test_files:
|
68
|
+
- test/test_evaluator.rb
|