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/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- pkg
2
- doc
@@ -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
@@ -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
-