dentaku 2.0.11 → 3.0.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.
- 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
|