exalted_math 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/exalted_math.gemspec +32 -30
- data/lib/exalted_math.rb +7 -4
- data/lib/exalted_math/math.rb +10 -10
- data/lib/exalted_math/math.treetop +10 -10
- data/lib/exalted_math/node.rb +12 -0
- data/lib/exalted_math/node/add.rb +12 -0
- data/lib/exalted_math/node/divide.rb +13 -0
- data/lib/exalted_math/node/list.rb +38 -0
- data/lib/exalted_math/node/maximum.rb +25 -0
- data/lib/exalted_math/node/minimum.rb +24 -0
- data/lib/exalted_math/node/multiply.rb +13 -0
- data/lib/exalted_math/node/named.rb +36 -0
- data/lib/exalted_math/node/node.rb +23 -0
- data/lib/exalted_math/node/number.rb +28 -0
- data/lib/exalted_math/node/operator.rb +39 -0
- data/lib/exalted_math/node/subtract.rb +13 -0
- data/script/benchmark.rb +23 -0
- data/spec/node_spec.rb +183 -0
- data/spec/parser_spec.rb +15 -25
- data/spec/spec_helper.rb +13 -0
- metadata +80 -85
- data/.gitignore +0 -2
- data/lib/exalted_math/ast.rb +0 -226
- data/spec/ast_spec.rb +0 -207
data/.gitignore
DELETED
data/lib/exalted_math/ast.rb
DELETED
@@ -1,226 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
# Jonathan D. Stott <jonathan.stott@gmail.com>
|
3
|
-
module Exalted
|
4
|
-
# Provides a simple AST for Exalted Maths
|
5
|
-
class Ast < Array
|
6
|
-
class UnknownNodeError < ArgumentError
|
7
|
-
def initialize(type)
|
8
|
-
@message = "Unknown node type '#{type}'"
|
9
|
-
puts @message
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize(*args)
|
14
|
-
super(args)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Is this AST constant?
|
18
|
-
#
|
19
|
-
# If an AST only has constant child nodes, it is constant.
|
20
|
-
# This allows some simplification.
|
21
|
-
#
|
22
|
-
# @return [Boolean] is the AST constant
|
23
|
-
def constant?
|
24
|
-
case self[0]
|
25
|
-
when 'mul', 'div', 'add', 'sub'
|
26
|
-
self[1].constant? and self[2].constant?
|
27
|
-
when 'num'
|
28
|
-
true
|
29
|
-
when 'stat', 'spec'
|
30
|
-
false
|
31
|
-
when 'max', 'min'
|
32
|
-
self[2].all? { |ast| ast.constant? }
|
33
|
-
else
|
34
|
-
raise UnknownNodeError, self[0]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Is the given AST constant?
|
39
|
-
#
|
40
|
-
# If an AST only has constant child nodes, it is constant.
|
41
|
-
# This allows some simplification.
|
42
|
-
#
|
43
|
-
# @param ast [Ast] An Ast
|
44
|
-
#
|
45
|
-
# @return [Boolean] is the AST constant
|
46
|
-
def self.constant?(ast)
|
47
|
-
ast.constant?
|
48
|
-
end
|
49
|
-
|
50
|
-
# Is the given AST valid?
|
51
|
-
#
|
52
|
-
# Does the context provide all the values the AST is looking for?
|
53
|
-
#
|
54
|
-
# @param ast [Ast] The Ast to test
|
55
|
-
# @param valid_values [Array] Array of valid values, the keys of the context
|
56
|
-
# @param errors [Array] Optional array of errors
|
57
|
-
#
|
58
|
-
# @return [Boolean] True if valid, false if not.
|
59
|
-
def self.valid?(ast, valid_values, errors=[])
|
60
|
-
case ast[0]
|
61
|
-
when 'mul', 'div', 'add', 'sub'
|
62
|
-
valid?(ast[1], valid_values, errors) and valid?(ast[2], valid_values, errors)
|
63
|
-
when 'num'
|
64
|
-
true
|
65
|
-
when 'stat', 'spec'
|
66
|
-
if valid_values.include? ast[1]
|
67
|
-
true
|
68
|
-
else
|
69
|
-
errors << "Unknown #{ast[0]} '#{ast[1]}'"
|
70
|
-
false
|
71
|
-
end
|
72
|
-
when 'max', 'min'
|
73
|
-
ast[2].map { |ast| valid?(ast, valid_values, errors) }.all?
|
74
|
-
else
|
75
|
-
raise UnknownNodeError, ast[0]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# Calculate the value of the AST for the given context
|
80
|
-
#
|
81
|
-
# This method recursively walks the AST, calculating the final value
|
82
|
-
#
|
83
|
-
# @param ast [Ast] The AST to compute
|
84
|
-
# @param context [#[]] Context used to evaluate the AST
|
85
|
-
#
|
86
|
-
# @return [Integer] The value of the AST
|
87
|
-
def self.value(ast, context={})
|
88
|
-
case ast[0]
|
89
|
-
when 'mul'
|
90
|
-
value(ast[1], context) * value(ast[2], context)
|
91
|
-
when 'div'
|
92
|
-
value(ast[1], context) / value(ast[2], context)
|
93
|
-
when 'add'
|
94
|
-
value(ast[1], context) + value(ast[2], context)
|
95
|
-
when 'sub'
|
96
|
-
value(ast[1], context) - value(ast[2], context)
|
97
|
-
when 'num'
|
98
|
-
ast[1]
|
99
|
-
when 'stat', 'spec'
|
100
|
-
context[ast[1]]
|
101
|
-
when 'max'
|
102
|
-
tmp = ast[2].map { |ast| value(ast, context) }.sort[-ast[1], ast[1]]
|
103
|
-
(tmp.size == 1) ? tmp.first : tmp.inject(0) { |fin, val| fin += val }
|
104
|
-
when 'min'
|
105
|
-
tmp = ast[2].map { |ast| value(ast, context) }.sort[0, ast[1]]
|
106
|
-
(tmp.size == 1) ? tmp.first : tmp.inject(0) { |fin, val| fin += val }
|
107
|
-
else
|
108
|
-
raise UnknownNodeError, self[0]
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Simplify the AST
|
113
|
-
#
|
114
|
-
# This recusively walks the AST, replacing any constant subtrees with their
|
115
|
-
# value.
|
116
|
-
#
|
117
|
-
# @param ast [Ast] The AST to simplify
|
118
|
-
# @return [Ast] The simplified AST
|
119
|
-
def self.simplify(ast)
|
120
|
-
if ast.constant?
|
121
|
-
new('num', value(ast))
|
122
|
-
else
|
123
|
-
case ast[0]
|
124
|
-
when 'add', 'sub', 'mul', 'div'
|
125
|
-
new(ast[0], simplify(ast[1]), simplify(ast[2]))
|
126
|
-
else
|
127
|
-
ast
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# Create a new num node
|
133
|
-
#
|
134
|
-
# @param value [Integer] The value of the new node
|
135
|
-
#
|
136
|
-
# @return [Ast] A num Ast Node
|
137
|
-
def self.num(value)
|
138
|
-
new('num', value)
|
139
|
-
end
|
140
|
-
|
141
|
-
# Create a new stat node
|
142
|
-
#
|
143
|
-
# @param value [String] The value of the new node
|
144
|
-
#
|
145
|
-
# @return [Ast] A stat Ast Node
|
146
|
-
def self.stat(value)
|
147
|
-
new('stat', value)
|
148
|
-
end
|
149
|
-
|
150
|
-
# Create a new spec node
|
151
|
-
#
|
152
|
-
# @param value [String] The value of the new node
|
153
|
-
#
|
154
|
-
# @return [Ast] A spec Ast Node
|
155
|
-
def self.spec(value)
|
156
|
-
new('spec', value)
|
157
|
-
end
|
158
|
-
|
159
|
-
# Create a new add node
|
160
|
-
#
|
161
|
-
# @param left [Ast] The left node of the add
|
162
|
-
# @param right [Ast] The right node of the add
|
163
|
-
# @return [Ast] An add Ast Node
|
164
|
-
def self.add(left, right)
|
165
|
-
new('add', left, right)
|
166
|
-
end
|
167
|
-
|
168
|
-
# Create a new sub node
|
169
|
-
#
|
170
|
-
# @param left [Ast] The left node of the sub
|
171
|
-
# @param right [Ast] The right node of the sub
|
172
|
-
# @return [Ast] An sub Ast Node
|
173
|
-
def self.sub(left, right)
|
174
|
-
new('sub', left, right)
|
175
|
-
end
|
176
|
-
|
177
|
-
# Create a new mul node
|
178
|
-
#
|
179
|
-
# @param left [Ast] The left node of the mul
|
180
|
-
# @param right [Ast] The right node of the mul
|
181
|
-
# @return [Ast] An mul Ast Node
|
182
|
-
def self.mul(left, right)
|
183
|
-
new('mul', left, right)
|
184
|
-
end
|
185
|
-
|
186
|
-
# Create a new div node
|
187
|
-
#
|
188
|
-
# @param left [Ast] The left node of the div
|
189
|
-
# @param right [Ast] The right node of the div
|
190
|
-
# @return [Ast] An div Ast Node
|
191
|
-
def self.div(left, right)
|
192
|
-
new('div', left, right)
|
193
|
-
end
|
194
|
-
|
195
|
-
# Create a new min node
|
196
|
-
#
|
197
|
-
# @param count [Integer] Number of values to sum for the min
|
198
|
-
# @param list [Array] Array of ASTs to calculate the min from
|
199
|
-
# @return [Ast] A min Ast Node
|
200
|
-
def self.min(count, list)
|
201
|
-
new('min', count, list)
|
202
|
-
end
|
203
|
-
|
204
|
-
# Create a new max node
|
205
|
-
#
|
206
|
-
# @param count [Integer] Number of values to sum for the max
|
207
|
-
# @param list [Array] Array of ASTs to calculate the max from
|
208
|
-
# @return [Ast] A max Ast Node
|
209
|
-
def self.max(count, list)
|
210
|
-
new('max', count, list)
|
211
|
-
end
|
212
|
-
|
213
|
-
def self.from_array(array)
|
214
|
-
case array[0]
|
215
|
-
when 'mul', 'div', 'add', 'sub'
|
216
|
-
new(array[0], from_array(array[1]), from_array(array[2]) )
|
217
|
-
when 'num', 'stat', 'spec'
|
218
|
-
new(array[0], array[1])
|
219
|
-
when 'max', 'min'
|
220
|
-
new(array[0], array[1], array[2].map! { |ast| new(ast) } )
|
221
|
-
else
|
222
|
-
raise UnknownNodeError, self[0]
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
data/spec/ast_spec.rb
DELETED
@@ -1,207 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
# Jonathan D. Stott <jonathan.stott@gmail.com>
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'exalted_math/ast'
|
5
|
-
|
6
|
-
include Exalted
|
7
|
-
|
8
|
-
|
9
|
-
describe "Exalted::Ast" do
|
10
|
-
before do
|
11
|
-
@three = Exalted::Ast.new('num', 3 )
|
12
|
-
@seven = Exalted::Ast.new('num', 7 )
|
13
|
-
@foo = Exalted::Ast.new('stat', 'foo')
|
14
|
-
@bar = Exalted::Ast.new('spec', 'bar')
|
15
|
-
@invalid = Exalted::Ast.new('stat', 'invalid')
|
16
|
-
@add = Exalted::Ast.new('add', @three, @seven)
|
17
|
-
@sub = Exalted::Ast.new('sub', @three, @seven)
|
18
|
-
@mul = Exalted::Ast.new('mul', @three, @seven)
|
19
|
-
@div = Exalted::Ast.new('div', @seven, @three)
|
20
|
-
@add_foo = Exalted::Ast.new('add', @three, @foo)
|
21
|
-
@sub_foo = Exalted::Ast.new('sub', @three, @foo)
|
22
|
-
@mul_foo = Exalted::Ast.new('mul', @three, @foo)
|
23
|
-
@div_foo = Exalted::Ast.new('div', @three, @foo)
|
24
|
-
@nested = Exalted::Ast.new('mul', @add, @sub_foo)
|
25
|
-
@min = Exalted::Ast.new('min', 1, [@three, @seven] )
|
26
|
-
@max = Exalted::Ast.new('max', 1, [@three, @seven] )
|
27
|
-
@context = { 'foo' => 3, 'bar' => 4 }
|
28
|
-
end
|
29
|
-
|
30
|
-
it ".num makes a number" do
|
31
|
-
@three.should == Ast.num(3)
|
32
|
-
end
|
33
|
-
|
34
|
-
it ".stat makes a stat" do
|
35
|
-
@foo.should == Ast.stat('foo')
|
36
|
-
end
|
37
|
-
|
38
|
-
it ".spec makes a spec" do
|
39
|
-
@bar.should == Ast.spec('bar')
|
40
|
-
end
|
41
|
-
|
42
|
-
it ".add makes an add" do
|
43
|
-
@add.should == Ast.add(@three, @seven)
|
44
|
-
end
|
45
|
-
|
46
|
-
it ".sub makes an sub" do
|
47
|
-
@sub.should == Ast.sub(@three, @seven)
|
48
|
-
end
|
49
|
-
|
50
|
-
it ".mul makes an mul" do
|
51
|
-
@mul.should == Ast.mul(@three, @seven)
|
52
|
-
end
|
53
|
-
|
54
|
-
it ".div makes an div" do
|
55
|
-
@div.should == Ast.div(@seven, @three)
|
56
|
-
end
|
57
|
-
|
58
|
-
it ".min makes a min" do
|
59
|
-
@min.should == Ast.min(1, [@three, @seven])
|
60
|
-
end
|
61
|
-
|
62
|
-
it ".max makes a max" do
|
63
|
-
@max.should == Ast.max(1, [@three, @seven])
|
64
|
-
end
|
65
|
-
|
66
|
-
it "a number is constant" do
|
67
|
-
@three.should.be.constant
|
68
|
-
end
|
69
|
-
|
70
|
-
it "a stat is not constant" do
|
71
|
-
@foo.should.not.be.constant
|
72
|
-
end
|
73
|
-
|
74
|
-
it "an add of two constants is constant" do
|
75
|
-
@add.should.be.constant
|
76
|
-
end
|
77
|
-
it "an sub of two constants is constant" do
|
78
|
-
@sub.should.be.constant
|
79
|
-
end
|
80
|
-
it "an mul of two constants is constant" do
|
81
|
-
@mul.should.be.constant
|
82
|
-
end
|
83
|
-
it "an div of two constants is constant" do
|
84
|
-
@div.should.be.constant
|
85
|
-
end
|
86
|
-
it "a min of constants is constant" do
|
87
|
-
@min.should.be.constant
|
88
|
-
end
|
89
|
-
it "a max of constants is constant" do
|
90
|
-
@max.should.be.constant
|
91
|
-
end
|
92
|
-
|
93
|
-
it "an add of constant and not constant is not constant" do
|
94
|
-
@add_foo.should.not.be.constant
|
95
|
-
end
|
96
|
-
it "an sub of constant and not constant is not constant" do
|
97
|
-
@sub_foo.should.not.be.constant
|
98
|
-
end
|
99
|
-
it "an mul of constant and not constant is not constant" do
|
100
|
-
@mul_foo.should.not.be.constant
|
101
|
-
end
|
102
|
-
it "an div of constant and not constant is not constant" do
|
103
|
-
@div_foo.should.not.be.constant
|
104
|
-
end
|
105
|
-
|
106
|
-
it "A number is valid" do
|
107
|
-
Exalted::Ast.valid?(@three, @context.keys).should.be.true
|
108
|
-
end
|
109
|
-
|
110
|
-
it "A stat is valid if it is in the array of valid keys" do
|
111
|
-
Exalted::Ast.valid?(@foo, @context.keys).should.be.true
|
112
|
-
end
|
113
|
-
|
114
|
-
it "A stat is not valid if it is not in the array of valid keys" do
|
115
|
-
Exalted::Ast.valid?(@invalid, @context.keys).should.not.be.true
|
116
|
-
end
|
117
|
-
|
118
|
-
it "An add is valid if both its keys are valid" do
|
119
|
-
Exalted::Ast.valid?(@add, @context.keys).should.be.true
|
120
|
-
end
|
121
|
-
it "An sub is valid if both its keys are valid" do
|
122
|
-
Exalted::Ast.valid?(@sub, @context.keys).should.be.true
|
123
|
-
end
|
124
|
-
it "An mul is valid if both its keys are valid" do
|
125
|
-
Exalted::Ast.valid?(@mul, @context.keys).should.be.true
|
126
|
-
end
|
127
|
-
it "An div is valid if both its keys are valid" do
|
128
|
-
Exalted::Ast.valid?(@div, @context.keys).should.be.true
|
129
|
-
end
|
130
|
-
it "An add is invalid if one key is invalid" do
|
131
|
-
@add = Exalted::Ast.new('add', @three, @invalid)
|
132
|
-
Exalted::Ast.valid?(@add, @context.keys).should.not.be.true
|
133
|
-
end
|
134
|
-
it "An sub is invalid if one key is invalid" do
|
135
|
-
@sub = Exalted::Ast.new('sub', @three, @invalid)
|
136
|
-
Exalted::Ast.valid?(@sub, @context.keys).should.not.be.true
|
137
|
-
end
|
138
|
-
it "An mul is invalid if one key is invalid" do
|
139
|
-
@mul = Exalted::Ast.new('mul', @three, @invalid)
|
140
|
-
Exalted::Ast.valid?(@mul, @context.keys).should.not.be.true
|
141
|
-
end
|
142
|
-
it "An div is invalid if one key is invalid" do
|
143
|
-
@div = Exalted::Ast.new('div', @three, @invalid)
|
144
|
-
Exalted::Ast.valid?(@div, @context.keys).should.not.be.true
|
145
|
-
end
|
146
|
-
|
147
|
-
it "the value of a number is the number" do
|
148
|
-
Exalted::Ast.value(@three, @context).should == 3
|
149
|
-
Exalted::Ast.value(@seven, @context).should == 7
|
150
|
-
end
|
151
|
-
|
152
|
-
it "the value of a stat is looked up in the context" do
|
153
|
-
Exalted::Ast.value(@foo, @context).should == @context['foo']
|
154
|
-
Exalted::Ast.value(@bar, @context).should == @context['bar']
|
155
|
-
end
|
156
|
-
|
157
|
-
it "the value of an add is the sum of the two children" do
|
158
|
-
Exalted::Ast.value(@add, @context).should == 10
|
159
|
-
end
|
160
|
-
|
161
|
-
it "the value of an sub is first child minus the second" do
|
162
|
-
Exalted::Ast.value(@sub, @context).should == -4
|
163
|
-
end
|
164
|
-
|
165
|
-
it "the value of an mul is the two children multiplied together" do
|
166
|
-
Exalted::Ast.value(@mul, @context).should == 21
|
167
|
-
end
|
168
|
-
|
169
|
-
it "the value of a div is the first child divided by the second" do
|
170
|
-
Exalted::Ast.value(@div, @context).should == 2
|
171
|
-
end
|
172
|
-
|
173
|
-
it "the value of a min is the mininum value" do
|
174
|
-
Exalted::Ast.value(@min, @context).should == 3
|
175
|
-
end
|
176
|
-
it "the value of a max is the maxinum value" do
|
177
|
-
Exalted::Ast.value(@max, @context).should == 7
|
178
|
-
end
|
179
|
-
|
180
|
-
it "it walks the whole tree to compute a value" do
|
181
|
-
Exalted::Ast.value(@nested, @context).should == 0
|
182
|
-
end
|
183
|
-
|
184
|
-
it "has sane equality behaviour" do
|
185
|
-
@three.should == Exalted::Ast.new('num', 3)
|
186
|
-
end
|
187
|
-
|
188
|
-
it "can simplify a constant add" do
|
189
|
-
Exalted::Ast.simplify(@add).should == Exalted::Ast.new('num', 10)
|
190
|
-
end
|
191
|
-
|
192
|
-
it "A number is simple" do
|
193
|
-
Exalted::Ast.simplify(@three).should == Exalted::Ast.new('num', 3)
|
194
|
-
end
|
195
|
-
|
196
|
-
it "A stat is simple" do
|
197
|
-
Exalted::Ast.simplify(@foo).should == Exalted::Ast.new('stat', 'foo')
|
198
|
-
end
|
199
|
-
|
200
|
-
it "A non-constant add isn't simplified" do
|
201
|
-
Exalted::Ast.simplify(@add_foo).should == Exalted::Ast.new('add', @three, @foo)
|
202
|
-
end
|
203
|
-
it "A constant branches are simplified" do
|
204
|
-
Exalted::Ast.simplify(@nested).should == Exalted::Ast.new('mul', Exalted::Ast.new('num', 10), @sub_foo)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|