exalted_math 0.1.3 → 0.2.0
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/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
|
-
|