ExpressionInterpreter 0.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/README +0 -0
- data/lib/expression_interpreter.rb +229 -0
- data/tests/test_parser.rb +51 -0
- metadata +48 -0
data/README
ADDED
File without changes
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module ExpressionInterpreter
|
2
|
+
|
3
|
+
class Expression
|
4
|
+
def initialize(expression)
|
5
|
+
parser = Parser.new(expression)
|
6
|
+
@operation = parser.operation
|
7
|
+
@variables = parser.variables
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate(&var)
|
11
|
+
context = Context.new
|
12
|
+
@variables.each do |variable|
|
13
|
+
context.assign(variable, yield(variable.name))
|
14
|
+
end
|
15
|
+
@operation.evaluate(context)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@operation.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Operation
|
24
|
+
def initialize(left_operand, operation, right_operand)
|
25
|
+
@left_operand = left_operand
|
26
|
+
@operation = operation
|
27
|
+
@right_operand = right_operand
|
28
|
+
end
|
29
|
+
|
30
|
+
def evaluate(conext)
|
31
|
+
left_eval = @left_operand.evaluate(conext)
|
32
|
+
right_eval = @right_operand.evaluate(conext)
|
33
|
+
left_eval.send(@operation, right_eval)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"(#{@left_operand}#{@operation}#{@right_operand})"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class CompositeOperation
|
42
|
+
attr_reader :replaced
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@operands = []
|
46
|
+
@operations = []
|
47
|
+
@replaced = ""
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_operation(operand, operation)
|
51
|
+
@operands << operand.operation
|
52
|
+
@operations << operation.name
|
53
|
+
@replaced += operand.replaced
|
54
|
+
@replaced += operation.replaced
|
55
|
+
end
|
56
|
+
|
57
|
+
def last_operation(operand)
|
58
|
+
@operands << operand.operation
|
59
|
+
@replaced += operand.replaced
|
60
|
+
end
|
61
|
+
|
62
|
+
def operation
|
63
|
+
%w(* / + -).each do |op_match|
|
64
|
+
i = 0
|
65
|
+
retry if @operations.detect do |operation|
|
66
|
+
if op_match == operation
|
67
|
+
@operands[i] = Operation.new(@operands[i], @operations[i], @operands[i+1])
|
68
|
+
@operands.delete_at(i+1)
|
69
|
+
@operations.delete_at(i)
|
70
|
+
true
|
71
|
+
else
|
72
|
+
i += 1
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
@operands[0]
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Variable
|
85
|
+
attr :name
|
86
|
+
def initialize(name)
|
87
|
+
@name = name.to_sym
|
88
|
+
end
|
89
|
+
def evaluate(conext)
|
90
|
+
conext.look_up(@name)
|
91
|
+
end
|
92
|
+
def to_s
|
93
|
+
@name.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Constant
|
98
|
+
def initialize(value)
|
99
|
+
@value = value.to_f
|
100
|
+
end
|
101
|
+
def evaluate(context)
|
102
|
+
@value
|
103
|
+
end
|
104
|
+
def to_s
|
105
|
+
@value.to_s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Context
|
110
|
+
def initialize
|
111
|
+
@var_map = {}
|
112
|
+
end
|
113
|
+
def look_up(name)
|
114
|
+
@var_map[name]
|
115
|
+
end
|
116
|
+
def assign(variable, value)
|
117
|
+
@var_map[variable.name] = value.to_f
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Parser
|
122
|
+
attr_reader :operation, :variables
|
123
|
+
|
124
|
+
def initialize(expression)
|
125
|
+
@variables = []
|
126
|
+
expression.gsub!(/\s/, "")
|
127
|
+
@operation = parse(expression).operation
|
128
|
+
end
|
129
|
+
|
130
|
+
def parse(expression)
|
131
|
+
expression = strip_expression(expression)
|
132
|
+
|
133
|
+
cp = CompositeOperation.new
|
134
|
+
1.times do
|
135
|
+
|
136
|
+
# Determine left operand
|
137
|
+
operand = next_operand(expression)
|
138
|
+
expression = expression[operand.replaced.size..-1]
|
139
|
+
|
140
|
+
# Determine operator
|
141
|
+
case expression
|
142
|
+
when /^([\+|\-|\*|\/])/
|
143
|
+
operator = Operator.new($1, $&)
|
144
|
+
expression.sub!($&, "")
|
145
|
+
cp.add_operation(operand, operator)
|
146
|
+
retry
|
147
|
+
else
|
148
|
+
cp.last_operation(operand)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
Operand.new(cp.operation, cp.replaced)
|
154
|
+
end
|
155
|
+
|
156
|
+
def next_operand(expression)
|
157
|
+
start_with_open_bracket = /^\(/
|
158
|
+
start_with_number = /^([0-9]+|[0-9]*\.[0-9]+)/
|
159
|
+
start_with_operand = /^([^(\+|\-|\*|\/)]*)/
|
160
|
+
|
161
|
+
case expression
|
162
|
+
when start_with_open_bracket
|
163
|
+
# The whole expression within the open bracket and
|
164
|
+
# close bracket are the operand
|
165
|
+
replacant = find_expression(expression)
|
166
|
+
operand = parse(replacant).operation
|
167
|
+
when start_with_number
|
168
|
+
replacant = $&;
|
169
|
+
operand = Constant.new($1)
|
170
|
+
when start_with_operand
|
171
|
+
replacant = $&;
|
172
|
+
operand = Variable.new($1)
|
173
|
+
add_variable(operand)
|
174
|
+
end
|
175
|
+
Operand.new(operand, replacant)
|
176
|
+
end
|
177
|
+
|
178
|
+
def find_expression(exp)
|
179
|
+
pos, bc = 0, 0
|
180
|
+
exp.each_byte do |b|
|
181
|
+
pos += 1
|
182
|
+
bc += 1 if b == 40
|
183
|
+
bc -= 1 if b == 41
|
184
|
+
break if bc == 0
|
185
|
+
end
|
186
|
+
exp[0..pos-1]
|
187
|
+
end
|
188
|
+
|
189
|
+
# Strip open and close brackets belonging to each other
|
190
|
+
# at the begining and end. i.e.: (x+y) -> x+y
|
191
|
+
def strip_expression(expression)
|
192
|
+
1.times do
|
193
|
+
if expression =~ /^\(/ && expression == find_expression(expression)
|
194
|
+
expression = expression.sub(/^\(/, "").sub(/\)$/, "")
|
195
|
+
retry
|
196
|
+
end
|
197
|
+
end
|
198
|
+
expression
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_variable(variable)
|
202
|
+
unless @variables.detect {|var| var == variable }
|
203
|
+
@variables << variable
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
class Operand
|
210
|
+
attr_accessor :operation, :replaced
|
211
|
+
def initialize(operation, replaced)
|
212
|
+
@operation = operation
|
213
|
+
@replaced = replaced
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class Operator
|
218
|
+
attr_accessor :name, :replaced
|
219
|
+
def initialize(name, replaced)
|
220
|
+
@name = name
|
221
|
+
@replaced = replaced
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
|
3
|
+
require 'expression_interpreter'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
include ExpressionInterpreter
|
7
|
+
|
8
|
+
class TestExpressionInterpreter < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@context = {
|
12
|
+
:x1 => 12,
|
13
|
+
:x2 => 3,
|
14
|
+
:x3 => 90.0,
|
15
|
+
:x4 => 3,
|
16
|
+
:x5 => 10.0}
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_add
|
20
|
+
exp = Expression.new(" x1 + x2")
|
21
|
+
assert_equal "(x1+x2)", exp.to_s
|
22
|
+
assert_equal 15, exp.evaluate {|v| @context[v] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_sub
|
26
|
+
exp = Expression.new("x2 -x3")
|
27
|
+
assert_equal "(x2-x3)", exp.to_s
|
28
|
+
assert_equal -87, exp.evaluate {|v| @context[v] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_operator_precedence
|
32
|
+
exp = Expression.new("x1 + x2 * x3")
|
33
|
+
assert_equal "(x1+(x2*x3))", exp.to_s
|
34
|
+
assert_equal 282, exp.evaluate {|v| @context[v] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_respect_bracket
|
38
|
+
exp = Expression.new("(x1 + x2) * x3")
|
39
|
+
assert_equal "((x1+x2)*x3)", exp.to_s
|
40
|
+
assert_equal 1350, exp.evaluate {|v| @context[v] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_formula1
|
44
|
+
exp = Expression.new("x1 * x2 + x3 * x4 / 10.0")
|
45
|
+
assert_equal "((x1*x2)+((x3*x4)/10.0))", exp.to_s
|
46
|
+
assert_equal 63, exp.evaluate {|v| @context[v] }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: ExpressionInterpreter
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-09-28 00:00:00 +02:00
|
8
|
+
summary: A simple expression interpreter
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: s.grijpink@gmail.com
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: expression_parser
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Sjors Grijpink
|
31
|
+
files:
|
32
|
+
- tests/test_parser.rb
|
33
|
+
- lib/expression_interpreter.rb
|
34
|
+
- README
|
35
|
+
test_files: []
|
36
|
+
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
dependencies: []
|
48
|
+
|