minad-evaluator 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt ADDED
@@ -0,0 +1,5 @@
1
+ lib/evaluator.rb
2
+ test/test_evaluator.rb
3
+ README.markdown
4
+ Rakefile
5
+ Manifest.txt
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
@@ -0,0 +1,9 @@
1
+ require 'hoe'
2
+
3
+ $:.unshift 'lib'
4
+ require 'evaluator'
5
+
6
+ Hoe.new 'evaluator', Evaluator::VERSION do |latex|
7
+ latex.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
8
+ end
9
+
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