dentaku 3.3.0 → 3.3.1
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/.rubocop.yml +2 -2
- data/.travis.yml +4 -6
- data/CHANGELOG.md +7 -1
- data/lib/dentaku/ast/arithmetic.rb +1 -1
- data/lib/dentaku/ast/case.rb +5 -3
- data/lib/dentaku/ast/case/case_conditional.rb +5 -2
- data/lib/dentaku/ast/function.rb +1 -1
- data/lib/dentaku/flat_hash.rb +2 -2
- data/lib/dentaku/parser.rb +17 -11
- data/lib/dentaku/token_matcher.rb +1 -1
- data/lib/dentaku/token_scanner.rb +1 -1
- data/lib/dentaku/tokenizer.rb +6 -1
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/addition_spec.rb +1 -1
- data/spec/ast/and_function_spec.rb +6 -6
- data/spec/ast/and_spec.rb +1 -1
- data/spec/ast/arithmetic_spec.rb +17 -17
- data/spec/ast/avg_spec.rb +5 -5
- data/spec/ast/count_spec.rb +7 -7
- data/spec/ast/division_spec.rb +1 -1
- data/spec/ast/function_spec.rb +8 -8
- data/spec/ast/max_spec.rb +3 -3
- data/spec/ast/min_spec.rb +3 -3
- data/spec/ast/mul_spec.rb +6 -6
- data/spec/ast/node_spec.rb +8 -8
- data/spec/ast/numeric_spec.rb +1 -1
- data/spec/ast/or_spec.rb +6 -6
- data/spec/ast/round_spec.rb +4 -4
- data/spec/ast/rounddown_spec.rb +4 -4
- data/spec/ast/roundup_spec.rb +4 -4
- data/spec/ast/sum_spec.rb +6 -6
- data/spec/ast/switch_spec.rb +5 -5
- data/spec/bulk_expression_solver_spec.rb +1 -1
- data/spec/calculator_spec.rb +30 -26
- data/spec/external_function_spec.rb +3 -3
- data/spec/parser_spec.rb +72 -123
- data/spec/spec_helper.rb +6 -4
- data/spec/token_matcher_spec.rb +8 -8
- data/spec/token_scanner_spec.rb +4 -4
- data/spec/tokenizer_spec.rb +8 -8
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b760fc43a6d5745ea9c95827fb0ce03e8fef8453c2264ee1073e5754c2b8d402
|
|
4
|
+
data.tar.gz: 04e03adf0729e170babe62c8f91cc88ca8aa46932dacb694ccfc564696eed765
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 006ec24c2c61758a1b7f5921e181ed49e2d54bf479091f9600ed31019fedd0a9411d744932e7dbf973b2e8fea098ff7299e58fd97851e5b2382a81e05043f8e4
|
|
7
|
+
data.tar.gz: 7f9d3735fc20e918d808158e8c618e11a0021762f102d9d5b32eec7d24f6a28845c01912fbc63c4cb835438f7f8d6d494fe811a5b01e373a5a6296a087c4edda
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion: 2.
|
|
2
|
+
TargetRubyVersion: 2.6
|
|
3
3
|
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
|
|
4
4
|
# to ignore them, so only the ones explicitly set in this file are enabled.
|
|
5
5
|
DisabledByDefault: true
|
|
@@ -110,7 +110,7 @@ Style/UnneededPercentQ:
|
|
|
110
110
|
|
|
111
111
|
# Align `end` with the matching keyword or starting expression except for
|
|
112
112
|
# assignments, where it should be aligned with the LHS.
|
|
113
|
-
|
|
113
|
+
Layout/EndAlignment:
|
|
114
114
|
Enabled: true
|
|
115
115
|
EnforcedStyleAlignWith: variable
|
|
116
116
|
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## [v3.3.1] 2019-03-26
|
|
4
|
+
- better errors for parse failures and exceptions in internal functions
|
|
5
|
+
- fix Ruby 2.6.0 deprecation warnings
|
|
6
|
+
- fix issue with functions in nested case statements
|
|
7
|
+
|
|
3
8
|
## [v3.3.0] 2018-12-04
|
|
4
9
|
- add array literal syntax
|
|
5
10
|
- return correct type from string function AST nodes
|
|
@@ -166,7 +171,8 @@
|
|
|
166
171
|
## [v0.1.0] 2012-01-20
|
|
167
172
|
- initial release
|
|
168
173
|
|
|
169
|
-
[HEAD]: https://github.com/rubysolo/dentaku/compare/v3.3.
|
|
174
|
+
[HEAD]: https://github.com/rubysolo/dentaku/compare/v3.3.1...HEAD
|
|
175
|
+
[v3.3.1]: https://github.com/rubysolo/dentaku/compare/v3.3.0...v3.3.1
|
|
170
176
|
[v3.3.0]: https://github.com/rubysolo/dentaku/compare/v3.2.1...v3.3.0
|
|
171
177
|
[v3.2.1]: https://github.com/rubysolo/dentaku/compare/v3.2.0...v3.2.1
|
|
172
178
|
[v3.2.0]: https://github.com/rubysolo/dentaku/compare/v3.1.0...v3.2.0
|
data/lib/dentaku/ast/case.rb
CHANGED
|
@@ -3,6 +3,7 @@ require_relative './case/case_when'
|
|
|
3
3
|
require_relative './case/case_then'
|
|
4
4
|
require_relative './case/case_switch_variable'
|
|
5
5
|
require_relative './case/case_else'
|
|
6
|
+
require 'dentaku/exceptions'
|
|
6
7
|
|
|
7
8
|
module Dentaku
|
|
8
9
|
module AST
|
|
@@ -11,16 +12,17 @@ module Dentaku
|
|
|
11
12
|
@switch = nodes.shift
|
|
12
13
|
|
|
13
14
|
unless @switch.is_a?(AST::CaseSwitchVariable)
|
|
14
|
-
raise 'Case missing switch variable'
|
|
15
|
+
raise ParseError.for(:node_invalid), 'Case missing switch variable'
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
@conditions = nodes
|
|
18
19
|
|
|
20
|
+
@else = nil
|
|
19
21
|
@else = @conditions.pop if @conditions.last.is_a?(AST::CaseElse)
|
|
20
22
|
|
|
21
23
|
@conditions.each do |condition|
|
|
22
24
|
unless condition.is_a?(AST::CaseConditional)
|
|
23
|
-
raise "#{condition} is not a CaseConditional"
|
|
25
|
+
raise ParseError.for(:node_invalid), "#{condition} is not a CaseConditional"
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
end
|
|
@@ -36,7 +38,7 @@ module Dentaku
|
|
|
36
38
|
if @else
|
|
37
39
|
return @else.value(context)
|
|
38
40
|
else
|
|
39
|
-
raise "No block matched the switch value '#{switch_value}'"
|
|
41
|
+
raise ArgumentError.for(:invalid_value), "No block matched the switch value '#{switch_value}'"
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'dentaku/exceptions'
|
|
2
|
+
|
|
1
3
|
module Dentaku
|
|
2
4
|
module AST
|
|
3
5
|
class CaseConditional < Node
|
|
@@ -7,11 +9,12 @@ module Dentaku
|
|
|
7
9
|
def initialize(when_statement, then_statement)
|
|
8
10
|
@when = when_statement
|
|
9
11
|
unless @when.is_a?(AST::CaseWhen)
|
|
10
|
-
raise 'Expected first argument to be a CaseWhen'
|
|
12
|
+
raise ParseError.for(:node_invalid), 'Expected first argument to be a CaseWhen'
|
|
11
13
|
end
|
|
14
|
+
|
|
12
15
|
@then = then_statement
|
|
13
16
|
unless @then.is_a?(AST::CaseThen)
|
|
14
|
-
raise 'Expected second argument to be a CaseThen'
|
|
17
|
+
raise ParseError.for(:node_invalid), 'Expected second argument to be a CaseThen'
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
|
data/lib/dentaku/ast/function.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Dentaku
|
|
|
38
38
|
|
|
39
39
|
if value.is_a?(::String)
|
|
40
40
|
number = value[/\A-?\d*\.?\d+\z/]
|
|
41
|
-
return number.include?('.') ?
|
|
41
|
+
return number.include?('.') ? BigDecimal(number, DIG) : number.to_i if number
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
raise Dentaku::ArgumentError.for(:incompatible_type, value: value, for: Numeric),
|
data/lib/dentaku/flat_hash.rb
CHANGED
|
@@ -19,8 +19,8 @@ module Dentaku
|
|
|
19
19
|
key
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def self.expand(
|
|
23
|
-
|
|
22
|
+
def self.expand(hash)
|
|
23
|
+
hash.each_with_object({}) do |(k, v), r|
|
|
24
24
|
hash_levels = k.to_s.split('.')
|
|
25
25
|
hash_levels = hash_levels.map(&:to_sym) if k.is_a?(Symbol)
|
|
26
26
|
child_hash = hash_levels[0...-1].reduce(r) { |h, n| h[n] ||= {} }
|
data/lib/dentaku/parser.rb
CHANGED
|
@@ -46,6 +46,8 @@ module Dentaku
|
|
|
46
46
|
args = Array.new(args_size) { output.pop }.reverse
|
|
47
47
|
|
|
48
48
|
output.push operator.new(*args)
|
|
49
|
+
rescue ::ArgumentError => e
|
|
50
|
+
raise Dentaku::ArgumentError, e.message
|
|
49
51
|
rescue NodeError => e
|
|
50
52
|
fail! :node_invalid, operator: operator, child: e.child, expect: e.expect, actual: e.actual
|
|
51
53
|
end
|
|
@@ -111,17 +113,19 @@ module Dentaku
|
|
|
111
113
|
open_cases = 0
|
|
112
114
|
case_end_index = nil
|
|
113
115
|
|
|
114
|
-
input.each_with_index do |
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
input.each_with_index do |input_token, index|
|
|
117
|
+
if input_token.category == :case
|
|
118
|
+
if input_token.value == :open
|
|
119
|
+
open_cases += 1
|
|
120
|
+
end
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
if input_token.value == :close
|
|
123
|
+
if open_cases > 0
|
|
124
|
+
open_cases -= 1
|
|
125
|
+
else
|
|
126
|
+
case_end_index = index
|
|
127
|
+
break
|
|
128
|
+
end
|
|
125
129
|
end
|
|
126
130
|
end
|
|
127
131
|
end
|
|
@@ -129,7 +133,8 @@ module Dentaku
|
|
|
129
133
|
subparser = Parser.new(
|
|
130
134
|
inner_case_inputs,
|
|
131
135
|
operations: [AST::Case],
|
|
132
|
-
arities: [0]
|
|
136
|
+
arities: [0],
|
|
137
|
+
function_registry: @function_registry
|
|
133
138
|
)
|
|
134
139
|
subparser.parse
|
|
135
140
|
output.concat(subparser.output)
|
|
@@ -253,6 +258,7 @@ module Dentaku
|
|
|
253
258
|
end
|
|
254
259
|
|
|
255
260
|
when :comma
|
|
261
|
+
fail! :invalid_statement if arities.empty?
|
|
256
262
|
arities[-1] += 1
|
|
257
263
|
while operations.any? && operations.last != AST::Grouping && operations.last != AST::Array
|
|
258
264
|
consume
|
data/lib/dentaku/tokenizer.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Dentaku
|
|
|
12
12
|
def tokenize(string, options = {})
|
|
13
13
|
@nesting = 0
|
|
14
14
|
@tokens = []
|
|
15
|
-
@aliases = options.fetch(:aliases,
|
|
15
|
+
@aliases = options.fetch(:aliases, global_aliases)
|
|
16
16
|
input = strip_comments(string.to_s.dup)
|
|
17
17
|
input = replace_aliases(input)
|
|
18
18
|
@case_sensitive = options.fetch(:case_sensitive, false)
|
|
@@ -84,6 +84,11 @@ module Dentaku
|
|
|
84
84
|
|
|
85
85
|
private
|
|
86
86
|
|
|
87
|
+
def global_aliases
|
|
88
|
+
return {} unless Dentaku.respond_to?(:aliases)
|
|
89
|
+
Dentaku.aliases
|
|
90
|
+
end
|
|
91
|
+
|
|
87
92
|
def fail!(reason, **meta)
|
|
88
93
|
message =
|
|
89
94
|
case reason
|
data/lib/dentaku/version.rb
CHANGED
data/spec/ast/addition_spec.rb
CHANGED
|
@@ -7,29 +7,29 @@ describe 'Dentaku::AST::And' do
|
|
|
7
7
|
|
|
8
8
|
it 'returns false if any of the arguments is false' do
|
|
9
9
|
result = Dentaku('AND(1 = 1, 0 = 1)')
|
|
10
|
-
expect(result).to eq
|
|
10
|
+
expect(result).to eq(false)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
it 'supports nested expressions' do
|
|
14
14
|
result = Dentaku('AND(y = 1, x = 1)', x: 1, y: 2)
|
|
15
|
-
expect(result).to eq
|
|
15
|
+
expect(result).to eq(false)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
it 'returns true if all of the arguments are true' do
|
|
19
19
|
result = Dentaku('AND(1 = 1, "2" = "2", true = true, true)')
|
|
20
|
-
expect(result).to eq
|
|
20
|
+
expect(result).to eq(true)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it 'returns true if all nested AND functions return true' do
|
|
24
24
|
result = Dentaku('AND(AND(1 = 1), AND(true != false, AND(true)))')
|
|
25
|
-
expect(result).to eq
|
|
25
|
+
expect(result).to eq(true)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
it 'raises an error if no arguments are passed' do
|
|
29
|
-
expect { calculator.evaluate!('AND()') }.to raise_error(ArgumentError)
|
|
29
|
+
expect { calculator.evaluate!('AND()') }.to raise_error(Dentaku::ArgumentError)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
it 'raises an error if a non logical argument is passed' do
|
|
33
|
-
expect { calculator.evaluate!('AND("r")') }.to raise_error(ArgumentError)
|
|
33
|
+
expect { calculator.evaluate!('AND("r")') }.to raise_error(Dentaku::ArgumentError)
|
|
34
34
|
end
|
|
35
35
|
end
|
data/spec/ast/and_spec.rb
CHANGED
data/spec/ast/arithmetic_spec.rb
CHANGED
|
@@ -11,29 +11,29 @@ describe Dentaku::AST::Arithmetic do
|
|
|
11
11
|
let(:ctx) { {'x' => 1, 'y' => 2} }
|
|
12
12
|
|
|
13
13
|
it 'performs an arithmetic operation with numeric operands' do
|
|
14
|
-
expect(add(one, two)).to eq
|
|
15
|
-
expect(sub(one, two)).to eq
|
|
16
|
-
expect(mul(one, two)).to eq
|
|
17
|
-
expect(div(one, two)).to eq
|
|
14
|
+
expect(add(one, two)).to eq(3)
|
|
15
|
+
expect(sub(one, two)).to eq(-1)
|
|
16
|
+
expect(mul(one, two)).to eq(2)
|
|
17
|
+
expect(div(one, two)).to eq(0.5)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it 'performs an arithmetic operation with one numeric operand and one string operand' do
|
|
21
|
-
expect(add(one, x)).to eq
|
|
22
|
-
expect(sub(one, x)).to eq
|
|
23
|
-
expect(mul(one, x)).to eq
|
|
24
|
-
expect(div(one, x)).to eq
|
|
25
|
-
|
|
26
|
-
expect(add(y, two)).to eq
|
|
27
|
-
expect(sub(y, two)).to eq
|
|
28
|
-
expect(mul(y, two)).to eq
|
|
29
|
-
expect(div(y, two)).to eq
|
|
21
|
+
expect(add(one, x)).to eq(2)
|
|
22
|
+
expect(sub(one, x)).to eq(0)
|
|
23
|
+
expect(mul(one, x)).to eq(1)
|
|
24
|
+
expect(div(one, x)).to eq(1)
|
|
25
|
+
|
|
26
|
+
expect(add(y, two)).to eq(4)
|
|
27
|
+
expect(sub(y, two)).to eq(0)
|
|
28
|
+
expect(mul(y, two)).to eq(4)
|
|
29
|
+
expect(div(y, two)).to eq(1)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
it 'performs an arithmetic operation with string operands' do
|
|
33
|
-
expect(add(x, y)).to eq
|
|
34
|
-
expect(sub(x, y)).to eq
|
|
35
|
-
expect(mul(x, y)).to eq
|
|
36
|
-
expect(div(x, y)).to eq
|
|
33
|
+
expect(add(x, y)).to eq(3)
|
|
34
|
+
expect(sub(x, y)).to eq(-1)
|
|
35
|
+
expect(mul(x, y)).to eq(2)
|
|
36
|
+
expect(div(x, y)).to eq(0.5)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
private
|
data/spec/ast/avg_spec.rb
CHANGED
|
@@ -5,29 +5,29 @@ require 'dentaku'
|
|
|
5
5
|
describe 'Dentaku::AST::Function::Avg' do
|
|
6
6
|
it 'returns the average of an array of Numeric values' do
|
|
7
7
|
result = Dentaku('AVG(1, x, 1.8)', x: 2.3)
|
|
8
|
-
expect(result).to eq
|
|
8
|
+
expect(result).to eq(1.7)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'returns the average of a single entry array of a Numeric value' do
|
|
12
12
|
result = Dentaku('AVG(x)', x: 2.3)
|
|
13
|
-
expect(result).to eq
|
|
13
|
+
expect(result).to eq(2.3)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
it 'returns the average even if a String is passed' do
|
|
17
17
|
result = Dentaku('AVG(1, x, 1.8)', x: '2.3')
|
|
18
|
-
expect(result).to eq
|
|
18
|
+
expect(result).to eq(1.7)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'returns the average even if an array is passed' do
|
|
22
22
|
result = Dentaku('AVG(1, x, 2.3)', x: [4, 5])
|
|
23
|
-
expect(result).to eq
|
|
23
|
+
expect(result).to eq(3.075)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
context 'checking errors' do
|
|
27
27
|
let(:calculator) { Dentaku::Calculator.new }
|
|
28
28
|
|
|
29
29
|
it 'raises an error if no arguments are passed' do
|
|
30
|
-
expect { calculator.evaluate!('AVG()') }.to raise_error(ArgumentError)
|
|
30
|
+
expect { calculator.evaluate!('AVG()') }.to raise_error(Dentaku::ArgumentError)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
data/spec/ast/count_spec.rb
CHANGED
|
@@ -5,36 +5,36 @@ require 'dentaku'
|
|
|
5
5
|
describe 'Dentaku::AST::Count' do
|
|
6
6
|
it 'returns the length of an array' do
|
|
7
7
|
result = Dentaku('COUNT(1, x, 1.8)', x: 2.3)
|
|
8
|
-
expect(result).to eq
|
|
8
|
+
expect(result).to eq(3)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'returns the length of a single number object' do
|
|
12
12
|
result = Dentaku('COUNT(x)', x: 2.3)
|
|
13
|
-
expect(result).to eq
|
|
13
|
+
expect(result).to eq(1)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
it 'returns the length if a single String is passed' do
|
|
17
17
|
result = Dentaku('COUNT(x)', x: 'dentaku')
|
|
18
|
-
expect(result).to eq
|
|
18
|
+
expect(result).to eq(7)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'returns the length if an array is passed' do
|
|
22
22
|
result = Dentaku('COUNT(x)', x: [4, 5])
|
|
23
|
-
expect(result).to eq
|
|
23
|
+
expect(result).to eq(2)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it 'returns the length if an array with one element is passed' do
|
|
27
27
|
result = Dentaku('COUNT(x)', x: [4])
|
|
28
|
-
expect(result).to eq
|
|
28
|
+
expect(result).to eq(1)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
it 'returns the length if an array even if it has nested array' do
|
|
32
32
|
result = Dentaku('COUNT(1, x, 3)', x: [4, 5])
|
|
33
|
-
expect(result).to eq
|
|
33
|
+
expect(result).to eq(3)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
it 'returns the length if an array is passed' do
|
|
37
37
|
result = Dentaku('COUNT()')
|
|
38
|
-
expect(result).to eq
|
|
38
|
+
expect(result).to eq(0)
|
|
39
39
|
end
|
|
40
40
|
end
|
data/spec/ast/division_spec.rb
CHANGED