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