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 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