evaluator 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.
@@ -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