exalted_math 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
1
+ module Exalted
2
+ grammar Maths
3
+
4
+ rule additive
5
+ multitive space* '+' space* additive {
6
+ def ast
7
+ Ast.add(multitive.ast, additive.ast )
8
+ end
9
+ }
10
+ / multitive space* '-' space* additive {
11
+ def ast
12
+ Ast.sub( multitive.ast, additive.ast )
13
+ end
14
+ }
15
+ / multitive
16
+ end
17
+
18
+ rule multitive
19
+ primary space* '*' space* multitive {
20
+ def ast
21
+ Ast.mul(primary.ast, multitive.ast )
22
+ end
23
+ }
24
+ /
25
+ primary space* '/' space* multitive {
26
+ def ast
27
+ Ast.div( primary.ast, multitive.ast )
28
+ end
29
+ }
30
+ / primary
31
+ end
32
+
33
+ rule primary
34
+ '(' space* additive space* ')' {
35
+ def ast
36
+ additive.ast
37
+ end
38
+ }
39
+ / number / spec / max / min / stat
40
+ end
41
+
42
+ rule spec
43
+ [Ss] 'pec' 'iality'? ':"' speciality:[^"]+ '"' {
44
+ def value
45
+ speciality.text_value
46
+ end
47
+
48
+ def ast
49
+ Ast.spec(value)
50
+ end
51
+ }
52
+ end
53
+
54
+ rule max
55
+ ( 'max' / 'highest' ) num:( '[' number ']' )? '(' space* list space* ')' {
56
+ def count
57
+ if num.elements
58
+ num.elements.detect { |e| e.respond_to?(:value) }.value
59
+ else
60
+ 1
61
+ end
62
+ end
63
+
64
+ def ast
65
+ Ast.max( count, list.asts )
66
+ end
67
+ }
68
+ end
69
+
70
+ rule min
71
+ ( 'min' / 'lowest' ) num:( '[' number ']' )? '(' space* list space* ')' {
72
+ def count
73
+ if num.elements
74
+ num.elements.detect { |e| e.respond_to?(:value) }.value
75
+ else
76
+ 1
77
+ end
78
+ end
79
+ def ast
80
+ Ast.min( count, list.asts )
81
+ end
82
+ }
83
+ end
84
+
85
+ rule list
86
+ ( ( number / stat ) space* ',' space* )* space* ( number / stat ) {
87
+ def dig(values, elements)
88
+ elements.each do |elem|
89
+ values << elem if elem.respond_to?(:value)
90
+ dig(values, elem.elements) if elem.elements
91
+ end
92
+ values
93
+ end
94
+
95
+ def asts
96
+ elem_list = []
97
+ dig(elem_list, elements)
98
+ elem_list.map! { |e| e.ast }
99
+ end
100
+ }
101
+ end
102
+
103
+ rule space
104
+ ( " " / "\t" )+
105
+ end
106
+
107
+ rule number
108
+ negative:('-')? [1-9] [0-9]* {
109
+ def value
110
+ text_value.to_i*neg
111
+ end
112
+
113
+ def neg
114
+ (negative.elements) ? -1 : 1
115
+ end
116
+ def ast
117
+ Ast.num(value)
118
+ end
119
+ }
120
+ end
121
+
122
+ rule stat
123
+ [A-Za-z]+ {
124
+ def value
125
+ text_value.downcase
126
+ end
127
+ def ast
128
+ Ast.stat(value)
129
+ end
130
+ }
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/ruby
2
+ # Jonathan D. Stott <jonathan.stott@gmail.com>
3
+ require 'treetop'
4
+ require 'exalted_math/math'
5
+ require 'exalted_math/ast'
6
+
7
+ module Exalted
8
+ class MathsParser
9
+ def ast(text)
10
+ result = parse(text)
11
+ if result
12
+ [true, result.ast]
13
+ else
14
+ [false, failure_reason]
15
+ end
16
+ end
17
+ end
18
+ end
data/spec/ast_spec.rb ADDED
@@ -0,0 +1,184 @@
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
+ @context = { 'foo' => 3, 'bar' => 4 }
26
+ end
27
+
28
+ it ".num makes a number" do
29
+ @three.should == Ast.num(3)
30
+ end
31
+
32
+ it ".stat makes a stat" do
33
+ @foo.should == Ast.stat('foo')
34
+ end
35
+
36
+ it ".spec makes a spec" do
37
+ @bar.should == Ast.spec('bar')
38
+ end
39
+
40
+ it ".add makes an add" do
41
+ @add.should == Ast.add(@three, @seven)
42
+ end
43
+
44
+ it ".sub makes an sub" do
45
+ @sub.should == Ast.sub(@three, @seven)
46
+ end
47
+
48
+ it ".mul makes an mul" do
49
+ @mul.should == Ast.mul(@three, @seven)
50
+ end
51
+
52
+ it ".div makes an div" do
53
+ @div.should == Ast.div(@seven, @three)
54
+ end
55
+
56
+ it "a number is constant" do
57
+ @three.should.be.constant
58
+ end
59
+
60
+ it "a stat is not constant" do
61
+ @foo.should.not.be.constant
62
+ end
63
+
64
+ it "an add of two constants is constant" do
65
+ @add.should.be.constant
66
+ end
67
+ it "an sub of two constants is constant" do
68
+ @sub.should.be.constant
69
+ end
70
+ it "an mul of two constants is constant" do
71
+ @mul.should.be.constant
72
+ end
73
+ it "an div of two constants is constant" do
74
+ @div.should.be.constant
75
+ end
76
+
77
+ it "an add of constant and not constant is not constant" do
78
+ @add_foo.should.not.be.constant
79
+ end
80
+ it "an sub of constant and not constant is not constant" do
81
+ @sub_foo.should.not.be.constant
82
+ end
83
+ it "an mul of constant and not constant is not constant" do
84
+ @mul_foo.should.not.be.constant
85
+ end
86
+ it "an div of constant and not constant is not constant" do
87
+ @div_foo.should.not.be.constant
88
+ end
89
+
90
+ it "A number is valid" do
91
+ Exalted::Ast.valid?(@three, @context.keys).should.be.true
92
+ end
93
+
94
+ it "A stat is valid if it is in the array of valid keys" do
95
+ Exalted::Ast.valid?(@foo, @context.keys).should.be.true
96
+ end
97
+
98
+ it "A stat is not valid if it is not in the array of valid keys" do
99
+ Exalted::Ast.valid?(@invalid, @context.keys).should.not.be.true
100
+ end
101
+
102
+ it "An add is valid if both its keys are valid" do
103
+ Exalted::Ast.valid?(@add, @context.keys).should.be.true
104
+ end
105
+ it "An sub is valid if both its keys are valid" do
106
+ Exalted::Ast.valid?(@sub, @context.keys).should.be.true
107
+ end
108
+ it "An mul is valid if both its keys are valid" do
109
+ Exalted::Ast.valid?(@mul, @context.keys).should.be.true
110
+ end
111
+ it "An div is valid if both its keys are valid" do
112
+ Exalted::Ast.valid?(@div, @context.keys).should.be.true
113
+ end
114
+ it "An add is invalid if one key is invalid" do
115
+ @add = Exalted::Ast.new('add', @three, @invalid)
116
+ Exalted::Ast.valid?(@add, @context.keys).should.not.be.true
117
+ end
118
+ it "An sub is invalid if one key is invalid" do
119
+ @sub = Exalted::Ast.new('sub', @three, @invalid)
120
+ Exalted::Ast.valid?(@sub, @context.keys).should.not.be.true
121
+ end
122
+ it "An mul is invalid if one key is invalid" do
123
+ @mul = Exalted::Ast.new('mul', @three, @invalid)
124
+ Exalted::Ast.valid?(@mul, @context.keys).should.not.be.true
125
+ end
126
+ it "An div is invalid if one key is invalid" do
127
+ @div = Exalted::Ast.new('div', @three, @invalid)
128
+ Exalted::Ast.valid?(@div, @context.keys).should.not.be.true
129
+ end
130
+
131
+ it "the value of a number is the number" do
132
+ Exalted::Ast.value(@three, @context).should == 3
133
+ Exalted::Ast.value(@seven, @context).should == 7
134
+ end
135
+
136
+ it "the value of a stat is looked up in the context" do
137
+ Exalted::Ast.value(@foo, @context).should == @context['foo']
138
+ Exalted::Ast.value(@bar, @context).should == @context['bar']
139
+ end
140
+
141
+ it "the value of an add is the sum of the two children" do
142
+ Exalted::Ast.value(@add, @context).should == 10
143
+ end
144
+
145
+ it "the value of an sub is first child minus the second" do
146
+ Exalted::Ast.value(@sub, @context).should == -4
147
+ end
148
+
149
+ it "the value of an mul is the two children multiplied together" do
150
+ Exalted::Ast.value(@mul, @context).should == 21
151
+ end
152
+
153
+ it "the value of a div is the first child divided by the second" do
154
+ Exalted::Ast.value(@div, @context).should == 2
155
+ end
156
+
157
+ it "it walks the whole tree to compute a value" do
158
+ Exalted::Ast.value(@nested, @context).should == 0
159
+ end
160
+
161
+ it "has sane equality behaviour" do
162
+ @three.should == Exalted::Ast.new('num', 3)
163
+ end
164
+
165
+ it "can simplify a constant add" do
166
+ Exalted::Ast.simplify(@add).should == Exalted::Ast.new('num', 10)
167
+ end
168
+
169
+ it "A number is simple" do
170
+ Exalted::Ast.simplify(@three).should == Exalted::Ast.new('num', 3)
171
+ end
172
+
173
+ it "A stat is simple" do
174
+ Exalted::Ast.simplify(@foo).should == Exalted::Ast.new('stat', 'foo')
175
+ end
176
+
177
+ it "A non-constant add isn't simplified" do
178
+ Exalted::Ast.simplify(@add_foo).should == Exalted::Ast.new('add', @three, @foo)
179
+ end
180
+ it "A constant branches are simplified" do
181
+ Exalted::Ast.simplify(@nested).should == Exalted::Ast.new('mul', Exalted::Ast.new('num', 10), @sub_foo)
182
+ end
183
+ end
184
+
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/ruby
2
+ # Jonathan D. Stott <jonathan.stott@gmail.com>
3
+ require 'spec_helper'
4
+ require 'exalted_math'
5
+
6
+ include Exalted
7
+ describe "Exalted::MathParser" do
8
+ before do
9
+ @parser = Exalted::MathsParser.new
10
+ end
11
+
12
+ [
13
+ ['4', Ast.num(4)],
14
+ ['-1', Ast.num(-1)],
15
+ ['3 * 4', Ast.mul(Ast.num(3), Ast.num(4) )],
16
+ ['3 - 4', Ast.sub(Ast.num(3), Ast.num(4) )],
17
+ ['3 + 4', Ast.add(Ast.num(3), Ast.num(4) )],
18
+ ['6 / 3', Ast.div(Ast.num(6), Ast.num(3) )],
19
+ ['Essence * 4', Ast.mul(Ast.stat('essence'), Ast.num(4) )],
20
+ ['(Essence * 4) + Willpower', Ast.add(Ast.mul(Ast.stat('essence'), Ast.num(4) ), Ast.stat('willpower'))]
21
+ ].each do |string, ast|
22
+ it "parses '#{string}'" do
23
+ success, result = @parser.ast(string)
24
+ success.should.be.true
25
+ result.should == ast
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/ruby
2
+ # Jonathan D. Stott <jonathan.stott@gmail.com>
3
+ require 'bacon'
4
+
5
+ Bacon.summary_on_exit
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exalted_math
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Jonathan Stott
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-06 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: treetop
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 1
32
+ - 4
33
+ version: "1.4"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: bacon
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: yard
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ description: |-
65
+ Parsing and evaluation of simple maths expressions for Exalted
66
+
67
+ This intended to aid in evaluating simple calculations which appear on character sheets, especially for Exalted.
68
+ email: jonathan.stott@gmail.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - README.rdoc
75
+ files:
76
+ - .gitignore
77
+ - README.rdoc
78
+ - Rakefile
79
+ - VERSION
80
+ - exalted_math.gemspec
81
+ - lib/exalted_math.rb
82
+ - lib/exalted_math/ast.rb
83
+ - lib/exalted_math/math.rb
84
+ - lib/exalted_math/math.treetop
85
+ - spec/ast_spec.rb
86
+ - spec/parser_spec.rb
87
+ - spec/spec_helper.rb
88
+ has_rdoc: true
89
+ homepage: http://github.com/namelessjon/exalted_math
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options:
94
+ - --charset=UTF-8
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 3
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ requirements: []
116
+
117
+ rubyforge_project:
118
+ rubygems_version: 1.3.7
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Parsing and evaluation of simple maths expressions for Exalted
122
+ test_files:
123
+ - spec/ast_spec.rb
124
+ - spec/spec_helper.rb
125
+ - spec/parser_spec.rb