evaluator 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/evaluator.rb
2
+ test/test_evaluator.rb
3
+ README.markdown
4
+ Rakefile
5
+ Manifest.txt
@@ -0,0 +1,35 @@
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
+ 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
+
32
+ Authors
33
+ -------
34
+
35
+ Daniel Mendler
@@ -0,0 +1,9 @@
1
+ require 'hoe'
2
+
3
+ $:.unshift 'lib'
4
+ require 'evaluator'
5
+
6
+ Hoe.new 'evaluator', Evaluator::VERSION do |evaluator|
7
+ evaluator.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
8
+ end
9
+
@@ -0,0 +1,192 @@
1
+ require 'complex'
2
+ begin
3
+ require 'units'
4
+ rescue LoadError
5
+ end
6
+
7
+ module Evaluator
8
+ def self.infix(priority, unary = nil, &block) [false, priority, lambda(&block), unary] end
9
+ def self.prefix(&block) [true, 1e5, lambda(&block)] end
10
+
11
+ VERSION = '0.1.5'
12
+ OPERATOR = {
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 },
37
+ 'gcd' => prefix {|x,y| x.gcd(y) },
38
+ 'lcm' => prefix {|x,y| x.lcm(y) },
39
+ 'sin' => prefix {|x| Math.sin(x) },
40
+ 'cos' => prefix {|x| Math.cos(x) },
41
+ 'tan' => prefix {|x| Math.tan(x) },
42
+ 'sinh' => prefix {|x| Math.sinh(x) },
43
+ 'cosh' => prefix {|x| Math.cosh(x) },
44
+ 'tanh' => prefix {|x| Math.tanh(x) },
45
+ 'asin' => prefix {|x| Math.asin(x) },
46
+ 'acos' => prefix {|x| Math.acos(x) },
47
+ 'atan' => prefix {|x| Math.atan(x) },
48
+ 'asinh' => prefix {|x| Math.asinh(x) },
49
+ 'acosh' => prefix {|x| Math.acosh(x) },
50
+ 'atanh' => prefix {|x| Math.atanh(x) },
51
+ 'sqrt' => prefix {|x| Math.sqrt(x) },
52
+ 'log' => prefix {|x| Math.log(x) },
53
+ 'log10' => prefix {|x| Math.log10(x) },
54
+ 'log2' => prefix {|x| Math.log(x)/Math.log(2) },
55
+ 'exp' => prefix {|x| Math.exp(x) },
56
+ 'erf' => prefix {|x| Math.erf(x) },
57
+ 'erfc' => prefix {|x| Math.erfc(x) },
58
+ 'floor' => prefix {|x| x.floor },
59
+ 'ceil' => prefix {|x| x.ceil },
60
+ 'string' => prefix {|x| x.to_s },
61
+ 'int' => prefix {|x| x.to_i },
62
+ 'float' => prefix {|x| x.to_f },
63
+ 'rand' => prefix {|| rand },
64
+ 'conj' => prefix {|x| x.conj },
65
+ 'im' => prefix {|x| x.imag },
66
+ 're' => prefix {|x| x.real },
67
+ 'round' => prefix {|x| x.round },
68
+ 'abs' => prefix {|x| x.abs },
69
+ 'minus' => prefix {|x| -x },
70
+ 'plus' => prefix {|x| x },
71
+ '!' => prefix {|x| !x },
72
+ '~' => prefix {|x| ~x },
73
+ 'substr' => prefix {|x,a,b| x.slice(a,b) },
74
+ 'len' => prefix {|x| x.length },
75
+ 'tolower' => prefix {|x| x.downcase },
76
+ 'toupper' => prefix {|x| x.upcase },
77
+ 'strip' => prefix {|x| x.strip },
78
+ 'reverse' => prefix {|x| x.reverse },
79
+ 'index' => prefix {|x,y| x.index(y) },
80
+ 'rindex' => prefix {|x,y| x.rindex(y) },
81
+ '=' => '==',
82
+ 'or' => '||',
83
+ 'and' => '&&',
84
+ 'mod' => '%',
85
+ 'ln' => 'log',
86
+ 'imag' => 'im',
87
+ 'real' => 're',
88
+ 'count' => 'len',
89
+ 'size' => 'len',
90
+ 'length' => 'len',
91
+ 'trim' => 'strip',
92
+ 'downcase' => 'tolower',
93
+ 'upcase' => 'toupper',
94
+ 'slice' => 'substr',
95
+ 'arcsin' => 'asin',
96
+ 'arccos' => 'acos',
97
+ 'arctan' => 'atan',
98
+ 'arcsinh' => 'asinh',
99
+ 'arccosh' => 'asinh',
100
+ 'arctanh' => 'atanh',
101
+ }
102
+ CONSTANTS = {
103
+ 'true' => true,
104
+ 'false' => false,
105
+ 'nil' => nil,
106
+ 'e' => Math::E,
107
+ 'pi' => Math::PI,
108
+ 'i' => Complex::I,
109
+ }
110
+ STRING = /^(?:'(?:\\'|[^'])*'|"(?:\\"|[^"])*")$/
111
+ REAL = /^(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
112
+ HEX = /^0[xX][\dA-Fa-f]+$/
113
+ OCT = /^0[0-7]+$/
114
+ DEC = /^\d+$/
115
+ SYMBOL = /^[a-zA-Z_][\w_]*$/
116
+ UNIT = /^\[[^\]]+\]$/
117
+ VALUE_TOKENS = [UNIT, STRING, REAL, HEX, OCT, DEC, SYMBOL].map {|x| x.source[1..-2] }
118
+ OPERATOR_TOKENS = OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |x| Regexp.quote(x) }
119
+ TOKENIZER = Regexp.new((VALUE_TOKENS + OPERATOR_TOKENS + ['\\(', '\\)', ',']).join('|'))
120
+
121
+ def self.eval(expr, vars = {})
122
+ vars = Hash[*vars.merge(CONSTANTS).map {|k,v| [k.to_s.downcase, v] }.flatten]
123
+ stack, result, unary = [], [], true
124
+ expr.to_s.scan(TOKENIZER).each do |tok|
125
+ if tok == '('
126
+ stack << '('
127
+ unary = true
128
+ elsif tok == ')'
129
+ exec(result, stack.pop) while !stack.empty? && stack.last != '('
130
+ raise(SyntaxError, 'Unexpected token )') if stack.empty?
131
+ stack.pop
132
+ unary = false
133
+ elsif tok == ','
134
+ exec(result, stack.pop) while !stack.empty? && stack.last != '('
135
+ unary = true
136
+ elsif operator = OPERATOR[tok.downcase]
137
+ # Check for alias
138
+ tok = String === operator ? operator : tok.downcase
139
+ operator = OPERATOR[tok]
140
+ if operator[0]
141
+ stack << '*' if !unary
142
+ # Prefix operator
143
+ stack << tok
144
+ elsif unary && operator[3]
145
+ # Alternative prefix operator
146
+ stack << operator[3]
147
+ else
148
+ # Infix operator
149
+ exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= operator[1]
150
+ stack << tok
151
+ end
152
+ unary = true
153
+ else
154
+ val = case tok
155
+ when UNIT
156
+ if tok.respond_to? :to_unit
157
+ tok[1..-2].to_unit
158
+ else
159
+ raise(RuntimeError, 'Unit support not available')
160
+ end
161
+ when STRING then tok[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'")
162
+ when REAL then tok.to_f
163
+ when HEX then tok.to_i(16)
164
+ when OCT then tok.to_i(8)
165
+ when DEC then tok.to_i(10)
166
+ when SYMBOL
167
+ tok.downcase!
168
+ raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
169
+ vars[tok]
170
+ end
171
+ stack << '*' if !unary
172
+ result << val
173
+ unary = false
174
+ end
175
+ end
176
+ exec(result, stack.pop) while !stack.empty?
177
+ result.last
178
+ end
179
+
180
+ def self.exec(result, op)
181
+ raise(SyntaxError, "Unexpected token #{op}") if !OPERATOR.include?(op)
182
+ fn = OPERATOR[op][2]
183
+ raise(SyntaxError, "Not enough operands for #{op}") if result.size < fn.arity
184
+ result << fn[*result.slice!(-fn.arity, fn.arity)]
185
+ end
186
+
187
+ private_class_method :infix, :prefix, :exec
188
+ end
189
+
190
+ def Evaluator(expr, vars = {})
191
+ Evaluator.eval(expr, vars)
192
+ end
@@ -0,0 +1,169 @@
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
+ assert_equal 'a1b', Evaluator("'a'+string 1+'b'")
34
+ assert_equal 'a1b', Evaluator("'a'+string(1)+'b'")
35
+
36
+ assert_equal 3, Evaluator('5-2')
37
+ assert_equal(-1, Evaluator('3-4'))
38
+
39
+ assert_equal 12, Evaluator('3*4')
40
+ assert_equal 'ababab', Evaluator('"ab"*3')
41
+
42
+ assert_equal 3, Evaluator('12/4')
43
+
44
+ assert_equal 10, Evaluator('103. div 10.')
45
+
46
+ assert_equal 3, Evaluator('7 mod 4')
47
+ assert_equal 3, Evaluator('7 % 4')
48
+
49
+ assert_equal 1024, Evaluator('2 ** 10')
50
+
51
+ assert_equal 8, Evaluator('2 << 2')
52
+ assert_equal 32, Evaluator('256 >> 3')
53
+
54
+ assert_equal 2, Evaluator('6 & 2')
55
+ assert_equal 7, Evaluator('1 | 2 | 4')
56
+ assert_equal 1, Evaluator('9 ^ 8')
57
+ end
58
+
59
+ def test_unary_operators
60
+ assert_equal(-1, Evaluator('-1'))
61
+ assert_equal(-2, Evaluator('-(1+1)'))
62
+ assert_equal(-42, Evaluator('---42'))
63
+ assert_equal 42, Evaluator('----42')
64
+ assert_equal(-9, Evaluator('3*-3'))
65
+ assert_equal(9, Evaluator('3*+3'))
66
+ end
67
+
68
+ def test_precendence
69
+ assert_equal 16, Evaluator('1+3*5')
70
+ assert_equal 16, Evaluator('3*5+1')
71
+ assert_equal 23, Evaluator('3*5+2**3')
72
+ end
73
+
74
+ def test_constants
75
+ assert_equal Math::PI, Evaluator('PI')
76
+ assert_equal Math::E, Evaluator('E')
77
+ assert_equal Complex::I, Evaluator('I')
78
+ assert_equal true, Evaluator('trUe')
79
+ assert_equal false, Evaluator('fAlSe')
80
+ assert_equal nil, Evaluator('niL')
81
+ end
82
+
83
+ def test_numeric_functions
84
+ assert_equal 13, Evaluator('gcd(26, 39)')
85
+ assert_equal 78, Evaluator('lcm(26, 39)')
86
+ assert_equal Math.cos(42), Evaluator('cos 42')
87
+ assert_equal Math.sin(42), Evaluator('sin 42')
88
+ assert_equal Math.cos(42), Evaluator('cos 42')
89
+ assert_equal Math.tan(42), Evaluator('tan 42')
90
+ assert_equal Math.sinh(42), Evaluator('sinh 42')
91
+ assert_equal Math.cosh(42), Evaluator('cosh 42')
92
+ assert_equal Math.tanh(42), Evaluator('tanh 42')
93
+ assert_equal Math.asin(0.5), Evaluator('asin .5')
94
+ assert_equal Math.acos(0.5), Evaluator('acos .5')
95
+ assert_equal Math.atan(0.5), Evaluator('atan .5')
96
+ assert_equal Math.asinh(0.5), Evaluator('asinh .5')
97
+ assert_equal Math.atanh(0.5), Evaluator('atanh .5')
98
+ assert_equal Math.sqrt(42) + 1, Evaluator('sqrt 42 + 1')
99
+ assert_equal Math.log(42) + 3, Evaluator('log 42 + 3')
100
+ assert_equal Math.log(42) + 3, Evaluator('ln 42 + 3')
101
+ assert_equal Math.log10(42) + 3, Evaluator('log10 42 + 3')
102
+ assert_equal Math.log(42)/Math.log(2) + 3, Evaluator('log2 42 + 3')
103
+ assert_equal 3 * Math.exp(42), Evaluator('3 * exp 42')
104
+ assert_equal Math.erf(2), Evaluator('erf 2')
105
+ assert_equal Math.erfc(2), Evaluator('erfc 2')
106
+ assert_equal 42, Evaluator('floor 42.3')
107
+ assert_equal 42, Evaluator('ceil 41.6')
108
+ assert_equal 3.5, Evaluator('float("3.5")')
109
+ assert_equal "3.5", Evaluator('string(3.5)')
110
+ assert_equal 3, Evaluator('int("3.5")')
111
+ assert_equal 3, Evaluator('int(3.6)')
112
+ srand(42); x = rand; srand(42)
113
+ assert_equal x, Evaluator('rand')
114
+ assert_equal Complex(1,-2), Evaluator('conj(1+2*i)')
115
+ assert_equal 2, Evaluator('Im(1+2*i)')
116
+ assert_equal 1, Evaluator('Re(1+2*i)')
117
+ assert_equal 3, Evaluator('round(3.4)')
118
+ assert_equal 4, Evaluator('round(3.5)')
119
+ assert_equal 6, Evaluator('abs -6')
120
+ assert_equal 3, Evaluator('plus 3')
121
+ assert_equal(-3, Evaluator('minus 3'))
122
+ assert_equal false, Evaluator('!3')
123
+ assert_equal ~3, Evaluator('~3')
124
+ end
125
+
126
+ def test_string_functions
127
+ assert_equal 'bcd', Evaluator('substr("abcde", 1, 3)')
128
+ assert_equal 4, Evaluator('len("abcd")')
129
+ assert_equal 'abc', Evaluator('strip " abc "')
130
+ assert_equal 'cba', Evaluator('reverse "abc"')
131
+ assert_equal 2, Evaluator('index("abcdefg", "cde")')
132
+ assert_equal 7, Evaluator('rindex("abcdefgcdef", "cde")')
133
+ end
134
+
135
+ def test_variables
136
+ assert_equal 14, Evaluator('a+b*c', :a => 2, :b => 3, :c => 4)
137
+ assert_equal 14, Evaluator('a+b*C', 'A' => 2, 'b' => 3, :c => 4)
138
+ assert_equal 14, Evaluator('alpha+beta*GAMMA', 'ALPHA' => 2, 'bEtA' => 3, 'gamma' => 4)
139
+ end
140
+
141
+ def test_errors
142
+ assert_raise(SyntaxError) { Evaluator('(((((((((((((3+3))') }
143
+ assert_raise(SyntaxError) { Evaluator('1+2)') }
144
+ assert_raise(SyntaxError) { Evaluator('1+2+3+') }
145
+ assert_raise(SyntaxError) { Evaluator('1 + floor') }
146
+ assert_raise(NameError) { Evaluator('42*a+3') }
147
+ assert_raise(NameError) { Evaluator('abc10') }
148
+ assert_raise(NameError) { Evaluator('abc10d') }
149
+ end
150
+
151
+ def test_numbers
152
+ assert_equal 0xABCDEF123, Evaluator('0xABCDEF123')
153
+ assert_equal 01234, Evaluator('01234')
154
+ assert_equal 234, Evaluator('234 ')
155
+ assert_equal 0.123, Evaluator('.123')
156
+ assert_equal 0.123, Evaluator('0.123')
157
+ assert_equal 123.0, Evaluator('123.')
158
+ assert_equal 123e-42, Evaluator('123e-42')
159
+ assert_equal 0.123e-42, Evaluator('.123e-42')
160
+ assert_equal 2.123e-42, Evaluator('2.123e-42')
161
+ end
162
+
163
+ def test_strings
164
+ assert_equal "abc'a", Evaluator('"abc\'a"')
165
+ assert_equal 'abc"a', Evaluator('"abc\"a"')
166
+ assert_equal 'abc"a', Evaluator("'abc\"a'")
167
+ assert_equal "abc'a", Evaluator("'abc\\'a'")
168
+ end
169
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: evaluator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
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 +02: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.3.1
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: Mathematical expression evaluator
67
+ test_files:
68
+ - test/test_evaluator.rb