minad-evaluator 0.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/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
|