dentaku 2.0.11 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +0 -1
- data/CHANGELOG.md +19 -0
- data/README.md +3 -2
- data/dentaku.gemspec +1 -0
- data/lib/dentaku/ast.rb +4 -0
- data/lib/dentaku/ast/access.rb +27 -0
- data/lib/dentaku/ast/arithmetic.rb +49 -7
- data/lib/dentaku/ast/case.rb +17 -3
- data/lib/dentaku/ast/combinators.rb +8 -2
- data/lib/dentaku/ast/function.rb +16 -0
- data/lib/dentaku/ast/function_registry.rb +15 -2
- data/lib/dentaku/ast/functions/and.rb +25 -0
- data/lib/dentaku/ast/functions/max.rb +1 -1
- data/lib/dentaku/ast/functions/min.rb +1 -1
- data/lib/dentaku/ast/functions/or.rb +25 -0
- data/lib/dentaku/ast/functions/round.rb +2 -2
- data/lib/dentaku/ast/functions/rounddown.rb +3 -2
- data/lib/dentaku/ast/functions/roundup.rb +3 -2
- data/lib/dentaku/ast/functions/ruby_math.rb +3 -3
- data/lib/dentaku/ast/functions/switch.rb +8 -0
- data/lib/dentaku/ast/identifier.rb +3 -2
- data/lib/dentaku/ast/negation.rb +5 -1
- data/lib/dentaku/ast/node.rb +3 -0
- data/lib/dentaku/bulk_expression_solver.rb +1 -2
- data/lib/dentaku/calculator.rb +7 -6
- data/lib/dentaku/exceptions.rb +75 -1
- data/lib/dentaku/parser.rb +73 -12
- data/lib/dentaku/token.rb +4 -0
- data/lib/dentaku/token_scanner.rb +20 -3
- data/lib/dentaku/tokenizer.rb +31 -4
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/addition_spec.rb +6 -6
- data/spec/ast/and_function_spec.rb +35 -0
- data/spec/ast/and_spec.rb +1 -1
- data/spec/ast/arithmetic_spec.rb +56 -0
- data/spec/ast/division_spec.rb +1 -1
- data/spec/ast/function_spec.rb +43 -6
- data/spec/ast/max_spec.rb +15 -0
- data/spec/ast/min_spec.rb +15 -0
- data/spec/ast/or_spec.rb +35 -0
- data/spec/ast/round_spec.rb +25 -0
- data/spec/ast/rounddown_spec.rb +25 -0
- data/spec/ast/roundup_spec.rb +25 -0
- data/spec/ast/switch_spec.rb +30 -0
- data/spec/calculator_spec.rb +26 -4
- data/spec/exceptions_spec.rb +1 -1
- data/spec/parser_spec.rb +22 -3
- data/spec/spec_helper.rb +12 -2
- data/spec/token_scanner_spec.rb +0 -4
- data/spec/tokenizer_spec.rb +40 -2
- metadata +39 -3
data/spec/exceptions_spec.rb
CHANGED
@@ -4,6 +4,6 @@ require 'dentaku/exceptions'
|
|
4
4
|
describe Dentaku::UnboundVariableError do
|
5
5
|
it 'includes variable name(s) in message' do
|
6
6
|
exception = described_class.new(['length'])
|
7
|
-
expect(exception.
|
7
|
+
expect(exception.unbound_variables).to include('length')
|
8
8
|
end
|
9
9
|
end
|
data/spec/parser_spec.rb
CHANGED
@@ -14,7 +14,7 @@ describe Dentaku::Parser do
|
|
14
14
|
plus = Dentaku::Token.new(:operator, :add)
|
15
15
|
four = Dentaku::Token.new(:numeric, 4)
|
16
16
|
|
17
|
-
node
|
17
|
+
node = described_class.new([five, plus, four]).parse
|
18
18
|
expect(node.value).to eq 9
|
19
19
|
end
|
20
20
|
|
@@ -23,7 +23,7 @@ describe Dentaku::Parser do
|
|
23
23
|
lt = Dentaku::Token.new(:comparator, :lt)
|
24
24
|
four = Dentaku::Token.new(:numeric, 4)
|
25
25
|
|
26
|
-
node
|
26
|
+
node = described_class.new([five, lt, four]).parse
|
27
27
|
expect(node.value).to eq false
|
28
28
|
end
|
29
29
|
|
@@ -31,7 +31,7 @@ describe Dentaku::Parser do
|
|
31
31
|
five = Dentaku::Token.new(:numeric, 5)
|
32
32
|
mod = Dentaku::Token.new(:operator, :mod)
|
33
33
|
|
34
|
-
node
|
34
|
+
node = described_class.new([five, mod]).parse
|
35
35
|
expect(node.value).to eq 0.05
|
36
36
|
end
|
37
37
|
|
@@ -104,6 +104,17 @@ describe Dentaku::Parser do
|
|
104
104
|
expect(node.value(x: 3)).to eq 15
|
105
105
|
end
|
106
106
|
|
107
|
+
it 'evaluates access into data structures' do
|
108
|
+
a = token(:a)
|
109
|
+
lbracket = token(:lbracket)
|
110
|
+
one = token(1)
|
111
|
+
rbracket = token(:rbracket)
|
112
|
+
|
113
|
+
node = described_class.new([a, lbracket, one, rbracket]).parse
|
114
|
+
expect { node.value }.to raise_error(Dentaku::UnboundVariableError)
|
115
|
+
expect(node.value(a: [1, 2, 3])).to eq 2
|
116
|
+
end
|
117
|
+
|
107
118
|
it 'evaluates boolean expressions' do
|
108
119
|
d_true = Dentaku::Token.new(:logical, true)
|
109
120
|
d_and = Dentaku::Token.new(:combinator, :and)
|
@@ -160,6 +171,14 @@ describe Dentaku::Parser do
|
|
160
171
|
described_class.new([this, also]).parse
|
161
172
|
}.to raise_error(Dentaku::ParseError)
|
162
173
|
end
|
174
|
+
|
175
|
+
it 'raises an exception when trying to access an undefined function' do
|
176
|
+
fn = Dentaku::Token.new(:function, 'non_exists_func')
|
177
|
+
|
178
|
+
expect {
|
179
|
+
described_class.new([fn]).parse
|
180
|
+
}.to raise_error(Dentaku::ParseError)
|
181
|
+
end
|
163
182
|
end
|
164
183
|
|
165
184
|
it "evaluates explicit 'NULL' as a Nil" do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'pry'
|
2
|
+
require 'coveralls'
|
3
|
+
|
4
|
+
# Check the amount of testcoverage
|
5
|
+
Coveralls.wear!
|
2
6
|
|
3
7
|
# automatically create a token stream from bare values
|
4
8
|
def token_stream(*args)
|
@@ -21,6 +25,8 @@ def type_for(value)
|
|
21
25
|
:operator
|
22
26
|
when :open, :close, :comma
|
23
27
|
:grouping
|
28
|
+
when :lbracket, :rbracket
|
29
|
+
:access
|
24
30
|
when :le, :ge, :ne, :ne, :lt, :gt, :eq
|
25
31
|
:comparator
|
26
32
|
when :and, :or
|
@@ -33,9 +39,13 @@ def type_for(value)
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def identifier(name)
|
36
|
-
Dentaku::AST::Identifier.new(
|
42
|
+
Dentaku::AST::Identifier.new(token(name))
|
37
43
|
end
|
38
44
|
|
39
45
|
def literal(value)
|
40
|
-
Dentaku::AST::Literal.new(
|
46
|
+
Dentaku::AST::Literal.new(token(value))
|
47
|
+
end
|
48
|
+
|
49
|
+
def token(value)
|
50
|
+
Dentaku::Token.new(type_for(value), value)
|
41
51
|
end
|
data/spec/token_scanner_spec.rb
CHANGED
@@ -27,10 +27,6 @@ describe Dentaku::TokenScanner do
|
|
27
27
|
expect(token.value).to eq(5)
|
28
28
|
end
|
29
29
|
|
30
|
-
it 'returns a list of all configured scanners' do
|
31
|
-
expect(described_class.scanners.length).to eq 15
|
32
|
-
end
|
33
|
-
|
34
30
|
it 'allows customizing available scanners' do
|
35
31
|
described_class.scanners = [:whitespace, :numeric]
|
36
32
|
expect(described_class.scanners.length).to eq 2
|
data/spec/tokenizer_spec.rb
CHANGED
@@ -7,6 +7,26 @@ describe Dentaku::Tokenizer do
|
|
7
7
|
expect(tokenizer.tokenize('')).to be_empty
|
8
8
|
end
|
9
9
|
|
10
|
+
it 'tokenizes numeric literal in decimal' do
|
11
|
+
token = tokenizer.tokenize('80').first
|
12
|
+
expect(token.category).to eq(:numeric)
|
13
|
+
expect(token.value).to eq(80)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'tokenizes numeric literal in hexadecimal' do
|
17
|
+
token = tokenizer.tokenize('0x80').first
|
18
|
+
expect(token.category).to eq(:numeric)
|
19
|
+
expect(token.value).to eq(128)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'tokenizes numeric literal in scientific notation' do
|
23
|
+
%w( 6.02e23 .602E+24 ).each do |s|
|
24
|
+
tokens = tokenizer.tokenize(s)
|
25
|
+
expect(tokens.map(&:category)).to eq([:numeric])
|
26
|
+
expect(tokens.map(&:value)).to eq([6.02e23])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
10
30
|
it 'tokenizes addition' do
|
11
31
|
tokens = tokenizer.tokenize('1+1')
|
12
32
|
expect(tokens.map(&:category)).to eq([:numeric, :operator, :numeric])
|
@@ -135,18 +155,36 @@ describe Dentaku::Tokenizer do
|
|
135
155
|
expect(tokens.map(&:value)).to eq(['perimeter', :le, 7500])
|
136
156
|
end
|
137
157
|
|
138
|
-
it '
|
158
|
+
it 'tokenizes "and" for logical expressions' do
|
139
159
|
tokens = tokenizer.tokenize('octopi <= 7500 AND sharks > 1500')
|
140
160
|
expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
|
141
161
|
expect(tokens.map(&:value)).to eq(['octopi', :le, 7500, :and, 'sharks', :gt, 1500])
|
142
162
|
end
|
143
163
|
|
144
|
-
it '
|
164
|
+
it 'tokenizes "or" for logical expressions' do
|
145
165
|
tokens = tokenizer.tokenize('size < 3 or admin = 1')
|
146
166
|
expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
|
147
167
|
expect(tokens.map(&:value)).to eq(['size', :lt, 3, :or, 'admin', :eq, 1])
|
148
168
|
end
|
149
169
|
|
170
|
+
it 'tokenizes "&&" for logical expressions' do
|
171
|
+
tokens = tokenizer.tokenize('octopi <= 7500 && sharks > 1500')
|
172
|
+
expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
|
173
|
+
expect(tokens.map(&:value)).to eq(['octopi', :le, 7500, :and, 'sharks', :gt, 1500])
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'tokenizes "||" for logical expressions' do
|
177
|
+
tokens = tokenizer.tokenize('size < 3 || admin = 1')
|
178
|
+
expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
|
179
|
+
expect(tokens.map(&:value)).to eq(['size', :lt, 3, :or, 'admin', :eq, 1])
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'tokenizes square brackets for data structure access' do
|
183
|
+
tokens = tokenizer.tokenize('a[1]')
|
184
|
+
expect(tokens.map(&:category)).to eq(%i(identifier access numeric access))
|
185
|
+
expect(tokens.map(&:value)).to eq(['a', :lbracket, 1, :rbracket])
|
186
|
+
end
|
187
|
+
|
150
188
|
it 'detects unbalanced parentheses' do
|
151
189
|
expect { tokenizer.tokenize('(5+3') }.to raise_error(Dentaku::TokenizerError, /too many opening parentheses/)
|
152
190
|
expect { tokenizer.tokenize(')') }.to raise_error(Dentaku::TokenizerError, /too many closing parentheses/)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dentaku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Solomon White
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: coveralls
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: " Dentaku is a parser and evaluator for mathematical formulas\n"
|
56
70
|
email:
|
57
71
|
- rubysolo@gmail.com
|
@@ -69,6 +83,7 @@ files:
|
|
69
83
|
- dentaku.gemspec
|
70
84
|
- lib/dentaku.rb
|
71
85
|
- lib/dentaku/ast.rb
|
86
|
+
- lib/dentaku/ast/access.rb
|
72
87
|
- lib/dentaku/ast/arithmetic.rb
|
73
88
|
- lib/dentaku/ast/bitwise.rb
|
74
89
|
- lib/dentaku/ast/case.rb
|
@@ -82,15 +97,18 @@ files:
|
|
82
97
|
- lib/dentaku/ast/datetime.rb
|
83
98
|
- lib/dentaku/ast/function.rb
|
84
99
|
- lib/dentaku/ast/function_registry.rb
|
100
|
+
- lib/dentaku/ast/functions/and.rb
|
85
101
|
- lib/dentaku/ast/functions/if.rb
|
86
102
|
- lib/dentaku/ast/functions/max.rb
|
87
103
|
- lib/dentaku/ast/functions/min.rb
|
88
104
|
- lib/dentaku/ast/functions/not.rb
|
105
|
+
- lib/dentaku/ast/functions/or.rb
|
89
106
|
- lib/dentaku/ast/functions/round.rb
|
90
107
|
- lib/dentaku/ast/functions/rounddown.rb
|
91
108
|
- lib/dentaku/ast/functions/roundup.rb
|
92
109
|
- lib/dentaku/ast/functions/ruby_math.rb
|
93
110
|
- lib/dentaku/ast/functions/string_functions.rb
|
111
|
+
- lib/dentaku/ast/functions/switch.rb
|
94
112
|
- lib/dentaku/ast/grouping.rb
|
95
113
|
- lib/dentaku/ast/identifier.rb
|
96
114
|
- lib/dentaku/ast/literal.rb
|
@@ -113,13 +131,22 @@ files:
|
|
113
131
|
- lib/dentaku/tokenizer.rb
|
114
132
|
- lib/dentaku/version.rb
|
115
133
|
- spec/ast/addition_spec.rb
|
134
|
+
- spec/ast/and_function_spec.rb
|
116
135
|
- spec/ast/and_spec.rb
|
136
|
+
- spec/ast/arithmetic_spec.rb
|
117
137
|
- spec/ast/case_spec.rb
|
118
138
|
- spec/ast/division_spec.rb
|
119
139
|
- spec/ast/function_spec.rb
|
140
|
+
- spec/ast/max_spec.rb
|
141
|
+
- spec/ast/min_spec.rb
|
120
142
|
- spec/ast/node_spec.rb
|
121
143
|
- spec/ast/numeric_spec.rb
|
144
|
+
- spec/ast/or_spec.rb
|
145
|
+
- spec/ast/round_spec.rb
|
146
|
+
- spec/ast/rounddown_spec.rb
|
147
|
+
- spec/ast/roundup_spec.rb
|
122
148
|
- spec/ast/string_functions_spec.rb
|
149
|
+
- spec/ast/switch_spec.rb
|
123
150
|
- spec/benchmark.rb
|
124
151
|
- spec/bulk_expression_solver_spec.rb
|
125
152
|
- spec/calculator_spec.rb
|
@@ -152,19 +179,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
179
|
version: '0'
|
153
180
|
requirements: []
|
154
181
|
rubyforge_project: dentaku
|
155
|
-
rubygems_version: 2.
|
182
|
+
rubygems_version: 2.6.13
|
156
183
|
signing_key:
|
157
184
|
specification_version: 4
|
158
185
|
summary: A formula language parser and evaluator
|
159
186
|
test_files:
|
160
187
|
- spec/ast/addition_spec.rb
|
188
|
+
- spec/ast/and_function_spec.rb
|
161
189
|
- spec/ast/and_spec.rb
|
190
|
+
- spec/ast/arithmetic_spec.rb
|
162
191
|
- spec/ast/case_spec.rb
|
163
192
|
- spec/ast/division_spec.rb
|
164
193
|
- spec/ast/function_spec.rb
|
194
|
+
- spec/ast/max_spec.rb
|
195
|
+
- spec/ast/min_spec.rb
|
165
196
|
- spec/ast/node_spec.rb
|
166
197
|
- spec/ast/numeric_spec.rb
|
198
|
+
- spec/ast/or_spec.rb
|
199
|
+
- spec/ast/round_spec.rb
|
200
|
+
- spec/ast/rounddown_spec.rb
|
201
|
+
- spec/ast/roundup_spec.rb
|
167
202
|
- spec/ast/string_functions_spec.rb
|
203
|
+
- spec/ast/switch_spec.rb
|
168
204
|
- spec/benchmark.rb
|
169
205
|
- spec/bulk_expression_solver_spec.rb
|
170
206
|
- spec/calculator_spec.rb
|