hayadentaku 3.5.7

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.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +26 -0
  3. data/.github/workflows/rubocop.yml +14 -0
  4. data/.gitignore +14 -0
  5. data/.pryrc +2 -0
  6. data/.rubocop.yml +114 -0
  7. data/.travis.yml +10 -0
  8. data/CHANGELOG.md +328 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE +21 -0
  11. data/README.md +352 -0
  12. data/Rakefile +31 -0
  13. data/hayadentaku.gemspec +35 -0
  14. data/lib/dentaku/ast/access.rb +44 -0
  15. data/lib/dentaku/ast/arithmetic.rb +292 -0
  16. data/lib/dentaku/ast/array.rb +38 -0
  17. data/lib/dentaku/ast/bitwise.rb +42 -0
  18. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  19. data/lib/dentaku/ast/case/case_else.rb +35 -0
  20. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  21. data/lib/dentaku/ast/case/case_then.rb +35 -0
  22. data/lib/dentaku/ast/case/case_when.rb +39 -0
  23. data/lib/dentaku/ast/case.rb +93 -0
  24. data/lib/dentaku/ast/combinators.rb +50 -0
  25. data/lib/dentaku/ast/comparators.rb +88 -0
  26. data/lib/dentaku/ast/datetime.rb +8 -0
  27. data/lib/dentaku/ast/function.rb +56 -0
  28. data/lib/dentaku/ast/function_registry.rb +107 -0
  29. data/lib/dentaku/ast/functions/abs.rb +5 -0
  30. data/lib/dentaku/ast/functions/all.rb +19 -0
  31. data/lib/dentaku/ast/functions/and.rb +25 -0
  32. data/lib/dentaku/ast/functions/any.rb +19 -0
  33. data/lib/dentaku/ast/functions/avg.rb +13 -0
  34. data/lib/dentaku/ast/functions/count.rb +26 -0
  35. data/lib/dentaku/ast/functions/duration.rb +51 -0
  36. data/lib/dentaku/ast/functions/enum.rb +54 -0
  37. data/lib/dentaku/ast/functions/filter.rb +21 -0
  38. data/lib/dentaku/ast/functions/if.rb +47 -0
  39. data/lib/dentaku/ast/functions/intercept.rb +33 -0
  40. data/lib/dentaku/ast/functions/map.rb +19 -0
  41. data/lib/dentaku/ast/functions/max.rb +5 -0
  42. data/lib/dentaku/ast/functions/min.rb +5 -0
  43. data/lib/dentaku/ast/functions/mul.rb +12 -0
  44. data/lib/dentaku/ast/functions/not.rb +5 -0
  45. data/lib/dentaku/ast/functions/or.rb +25 -0
  46. data/lib/dentaku/ast/functions/pluck.rb +34 -0
  47. data/lib/dentaku/ast/functions/reduce.rb +60 -0
  48. data/lib/dentaku/ast/functions/round.rb +5 -0
  49. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  50. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  51. data/lib/dentaku/ast/functions/ruby_math.rb +57 -0
  52. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  53. data/lib/dentaku/ast/functions/sum.rb +12 -0
  54. data/lib/dentaku/ast/functions/switch.rb +8 -0
  55. data/lib/dentaku/ast/functions/xor.rb +44 -0
  56. data/lib/dentaku/ast/grouping.rb +23 -0
  57. data/lib/dentaku/ast/identifier.rb +52 -0
  58. data/lib/dentaku/ast/literal.rb +30 -0
  59. data/lib/dentaku/ast/logical.rb +8 -0
  60. data/lib/dentaku/ast/negation.rb +54 -0
  61. data/lib/dentaku/ast/nil.rb +13 -0
  62. data/lib/dentaku/ast/node.rb +29 -0
  63. data/lib/dentaku/ast/numeric.rb +8 -0
  64. data/lib/dentaku/ast/operation.rb +44 -0
  65. data/lib/dentaku/ast/string.rb +15 -0
  66. data/lib/dentaku/ast.rb +42 -0
  67. data/lib/dentaku/bulk_expression_solver.rb +158 -0
  68. data/lib/dentaku/calculator.rb +192 -0
  69. data/lib/dentaku/date_arithmetic.rb +60 -0
  70. data/lib/dentaku/dependency_resolver.rb +29 -0
  71. data/lib/dentaku/exceptions.rb +116 -0
  72. data/lib/dentaku/flat_hash.rb +161 -0
  73. data/lib/dentaku/parser.rb +318 -0
  74. data/lib/dentaku/print_visitor.rb +112 -0
  75. data/lib/dentaku/string_casing.rb +7 -0
  76. data/lib/dentaku/token.rb +48 -0
  77. data/lib/dentaku/token_matcher.rb +138 -0
  78. data/lib/dentaku/token_matchers.rb +29 -0
  79. data/lib/dentaku/token_scanner.rb +240 -0
  80. data/lib/dentaku/tokenizer.rb +127 -0
  81. data/lib/dentaku/version.rb +3 -0
  82. data/lib/dentaku/visitor/infix.rb +86 -0
  83. data/lib/dentaku.rb +69 -0
  84. data/spec/ast/abs_spec.rb +26 -0
  85. data/spec/ast/addition_spec.rb +67 -0
  86. data/spec/ast/all_spec.rb +38 -0
  87. data/spec/ast/and_function_spec.rb +35 -0
  88. data/spec/ast/and_spec.rb +32 -0
  89. data/spec/ast/any_spec.rb +36 -0
  90. data/spec/ast/arithmetic_spec.rb +147 -0
  91. data/spec/ast/avg_spec.rb +42 -0
  92. data/spec/ast/case_spec.rb +84 -0
  93. data/spec/ast/comparator_spec.rb +87 -0
  94. data/spec/ast/count_spec.rb +40 -0
  95. data/spec/ast/division_spec.rb +64 -0
  96. data/spec/ast/filter_spec.rb +25 -0
  97. data/spec/ast/function_spec.rb +69 -0
  98. data/spec/ast/intercept_spec.rb +30 -0
  99. data/spec/ast/map_spec.rb +40 -0
  100. data/spec/ast/max_spec.rb +33 -0
  101. data/spec/ast/min_spec.rb +33 -0
  102. data/spec/ast/mul_spec.rb +43 -0
  103. data/spec/ast/negation_spec.rb +48 -0
  104. data/spec/ast/node_spec.rb +43 -0
  105. data/spec/ast/numeric_spec.rb +16 -0
  106. data/spec/ast/or_spec.rb +35 -0
  107. data/spec/ast/pluck_spec.rb +49 -0
  108. data/spec/ast/reduce_spec.rb +22 -0
  109. data/spec/ast/round_spec.rb +35 -0
  110. data/spec/ast/rounddown_spec.rb +35 -0
  111. data/spec/ast/roundup_spec.rb +35 -0
  112. data/spec/ast/string_functions_spec.rb +217 -0
  113. data/spec/ast/sum_spec.rb +43 -0
  114. data/spec/ast/switch_spec.rb +30 -0
  115. data/spec/ast/xor_spec.rb +35 -0
  116. data/spec/benchmark.rb +70 -0
  117. data/spec/bulk_expression_solver_spec.rb +241 -0
  118. data/spec/calculator_spec.rb +1003 -0
  119. data/spec/dentaku_spec.rb +52 -0
  120. data/spec/dependency_resolver_spec.rb +18 -0
  121. data/spec/exceptions_spec.rb +9 -0
  122. data/spec/external_function_spec.rb +177 -0
  123. data/spec/parser_spec.rb +183 -0
  124. data/spec/print_visitor_spec.rb +77 -0
  125. data/spec/spec_helper.rb +69 -0
  126. data/spec/token_matcher_spec.rb +134 -0
  127. data/spec/token_scanner_spec.rb +49 -0
  128. data/spec/token_spec.rb +16 -0
  129. data/spec/tokenizer_spec.rb +375 -0
  130. data/spec/visitor/infix_spec.rb +52 -0
  131. data/spec/visitor_spec.rb +139 -0
  132. metadata +353 -0
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/functions/abs'
3
+ require 'dentaku'
4
+
5
+ describe 'Dentaku::AST::Function::Abs' do
6
+ it 'returns the absolute value of number' do
7
+ result = Dentaku('ABS(-4.2)')
8
+ expect(result).to eq(4.2)
9
+ end
10
+
11
+ it 'returns the correct value for positive number' do
12
+ result = Dentaku('ABS(1.3)')
13
+ expect(result).to eq(1.3)
14
+ end
15
+
16
+ it 'returns the correct value for zero' do
17
+ result = Dentaku('ABS(0)')
18
+ expect(result).to eq(0)
19
+ end
20
+
21
+ context 'checking errors' do
22
+ it 'raises an error if argument is not numeric' do
23
+ expect { Dentaku!("ABS(2020-1-1)") }.to raise_error(Dentaku::ArgumentError)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/arithmetic'
3
+
4
+ require 'dentaku/token'
5
+
6
+ describe Dentaku::AST::Addition do
7
+ let(:five) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 5) }
8
+ let(:six) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 6) }
9
+
10
+ let(:t) { Dentaku::AST::Numeric.new Dentaku::Token.new(:logical, true) }
11
+
12
+ it 'allows access to its sub-trees' do
13
+ node = described_class.new(five, six)
14
+ expect(node.left).to eq(five)
15
+ expect(node.right).to eq(six)
16
+ end
17
+
18
+ it 'performs addition' do
19
+ node = described_class.new(five, six)
20
+ expect(node.value).to eq(11)
21
+ end
22
+
23
+ it 'requires operands that respond to +' do
24
+ expect {
25
+ described_class.new(five, t)
26
+ }.to raise_error(Dentaku::NodeError, /requires operands/)
27
+
28
+ expression = Dentaku::AST::Multiplication.new(five, five)
29
+ group = Dentaku::AST::Grouping.new(expression)
30
+
31
+ expect {
32
+ described_class.new(group, five)
33
+ }.not_to raise_error
34
+ end
35
+
36
+ it 'allows operands that respond to addition' do
37
+ # Sample struct that has a custom definition for addition
38
+ Addable = Struct.new(:value) do
39
+ def +(other)
40
+ case other
41
+ when Addable
42
+ value + other.value
43
+ when Numeric
44
+ value + other
45
+ end
46
+ end
47
+ end
48
+
49
+ operand_five = Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, Addable.new(5))
50
+ operand_six = Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, Addable.new(6))
51
+
52
+ expect {
53
+ described_class.new(operand_five, operand_six).value
54
+ }.not_to raise_error
55
+
56
+ expect {
57
+ described_class.new(operand_five, six).value
58
+ }.not_to raise_error
59
+ end
60
+
61
+ it 'does not try to parse nested string as date' do
62
+ a = ['2017-01-01', '2017-01-02']
63
+ b = ['2017-01-01']
64
+
65
+ expect(Dentaku('a + b', a: a, b: b)).to eq(['2017-01-01', '2017-01-02', '2017-01-01'])
66
+ end
67
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/functions/all'
3
+ require 'dentaku'
4
+
5
+ describe Dentaku::AST::All do
6
+ let(:calculator) { Dentaku::Calculator.new }
7
+
8
+ it 'performs ALL operation' do
9
+ result = Dentaku('ALL(vals, val, val > 1)', vals: [1, 2, 3])
10
+ expect(result).to eq(false)
11
+ end
12
+
13
+ it 'works with a single value if needed for some reason' do
14
+ result = Dentaku('ALL(vals, val, val > 1)', vals: 1)
15
+ expect(result).to eq(false)
16
+
17
+ result = Dentaku('ALL(vals, val, val > 1)', vals: 2)
18
+ expect(result).to eq(true)
19
+ end
20
+
21
+ it 'raises argument error if a string is passed as identifier' do
22
+ expect { calculator.evaluate!('ALL({1, 2, 3}, "val", val % 2 == 0)') }.to raise_error(
23
+ Dentaku::ParseError, 'ALL() requires second argument to be an identifier'
24
+ )
25
+ end
26
+
27
+ it 'treats missing keys in hashes as NULL in permissive mode' do
28
+ expect(
29
+ calculator.evaluate('ALL(items, item, item.value)', items: [{value: 1}, {}])
30
+ ).to be_falsy
31
+ end
32
+
33
+ it 'raises an error if accessing a missing key in a hash in strict mode' do
34
+ expect {
35
+ calculator.evaluate!('ALL(items, item, item.value)', items: [{value: 1}, {}])
36
+ }.to raise_error(Dentaku::UnboundVariableError)
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'dentaku'
3
+ require 'dentaku/ast/functions/and'
4
+
5
+ describe 'Dentaku::AST::And' do
6
+ let(:calculator) { Dentaku::Calculator.new }
7
+
8
+ it 'returns false if any of the arguments is false' do
9
+ result = Dentaku('AND(1 = 1, 0 = 1)')
10
+ expect(result).to eq(false)
11
+ end
12
+
13
+ it 'supports nested expressions' do
14
+ result = Dentaku('AND(y = 1, x = 1)', x: 1, y: 2)
15
+ expect(result).to eq(false)
16
+ end
17
+
18
+ it 'returns true if all of the arguments are true' do
19
+ result = Dentaku('AND(1 = 1, "2" = "2", true = true, true)')
20
+ expect(result).to eq(true)
21
+ end
22
+
23
+ it 'returns true if all nested AND functions return true' do
24
+ result = Dentaku('AND(AND(1 = 1), AND(true != false, AND(true)))')
25
+ expect(result).to eq(true)
26
+ end
27
+
28
+ it 'raises an error if no arguments are passed' do
29
+ expect { calculator.evaluate!('AND()') }.to raise_error(Dentaku::ArgumentError)
30
+ end
31
+
32
+ it 'raises an error if a non logical argument is passed' do
33
+ expect { calculator.evaluate!('AND("r")') }.to raise_error(Dentaku::ArgumentError)
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/combinators'
3
+
4
+ require 'dentaku/token'
5
+
6
+ describe Dentaku::AST::And do
7
+ let(:t) { Dentaku::AST::Logical.new Dentaku::Token.new(:logical, true) }
8
+ let(:f) { Dentaku::AST::Logical.new Dentaku::Token.new(:logical, false) }
9
+
10
+ let(:five) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 5) }
11
+
12
+ it 'performs logical AND' do
13
+ node = described_class.new(t, f)
14
+ expect(node.value).to eq(false)
15
+ end
16
+
17
+ it 'requires logical operands' do
18
+ expect {
19
+ described_class.new(t, five)
20
+ }.to raise_error(Dentaku::NodeError, /requires logical operands/)
21
+
22
+ expression = Dentaku::AST::LessThanOrEqual.new(five, five)
23
+ expect {
24
+ described_class.new(t, expression)
25
+ }.not_to raise_error
26
+
27
+ expression = Dentaku::AST::Or.new(t, f)
28
+ expect {
29
+ described_class.new(t, expression)
30
+ }.not_to raise_error
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/functions/any'
3
+ require 'dentaku'
4
+
5
+ describe Dentaku::AST::Any do
6
+ let(:calculator) { Dentaku::Calculator.new }
7
+
8
+ it 'performs ANY operation' do
9
+ result = Dentaku('ANY(vals, val, val > 1)', vals: [1, 2, 3])
10
+ expect(result).to eq(true)
11
+ end
12
+
13
+ it 'works with a single value if needed for some reason' do
14
+ result = Dentaku('ANY(vals, val, val > 1)', vals: 1)
15
+ expect(result).to eq(false)
16
+
17
+ result = Dentaku('ANY(vals, val, val > 1)', vals: 2)
18
+ expect(result).to eq(true)
19
+ end
20
+
21
+ it 'raises argument error if a string is passed as identifier' do
22
+ expect { calculator.evaluate!('ANY({1, 2, 3}, "val", val % 2 == 0)') }.to raise_error(Dentaku::ParseError)
23
+ end
24
+
25
+ it 'treats missing keys in hashes as NULL in permissive mode' do
26
+ expect(
27
+ calculator.evaluate('ANY(items, item, item.value)', items: [{value: 1}, {}])
28
+ ).to be_truthy
29
+ end
30
+
31
+ it 'raises an error if accessing a missing key in a hash in strict mode' do
32
+ expect {
33
+ calculator.evaluate!('ANY(items, item, item.value)', items: [{}, {value: 1}])
34
+ }.to raise_error(Dentaku::UnboundVariableError)
35
+ end
36
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/arithmetic'
3
+ require 'dentaku'
4
+
5
+ describe Dentaku::AST::Arithmetic do
6
+ let(:one) { Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, 1)) }
7
+ let(:two) { Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, 2)) }
8
+ let(:x) { Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, 'x')) }
9
+ let(:y) { Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, 'y')) }
10
+ let(:ctx) { {'x' => 1, 'y' => 2} }
11
+ let(:date) { Dentaku::AST::DateTime.new(Dentaku::Token.new(:datetime, DateTime.new(2020, 4, 16))) }
12
+
13
+ it 'performs an arithmetic operation with numeric operands' do
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
+ expect(neg(one)).to eq(-1)
19
+ end
20
+
21
+ it 'performs an arithmetic operation with one numeric operand and one string operand' do
22
+ expect(add(one, x)).to eq(2)
23
+ expect(sub(one, x)).to eq(0)
24
+ expect(mul(one, x)).to eq(1)
25
+ expect(div(one, x)).to eq(1)
26
+
27
+ expect(add(y, two)).to eq(4)
28
+ expect(sub(y, two)).to eq(0)
29
+ expect(mul(y, two)).to eq(4)
30
+ expect(div(y, two)).to eq(1)
31
+ end
32
+
33
+ it 'performs an arithmetic operation with string operands' do
34
+ expect(add(x, y)).to eq(3)
35
+ expect(sub(x, y)).to eq(-1)
36
+ expect(mul(x, y)).to eq(2)
37
+ expect(div(x, y)).to eq(0.5)
38
+ expect(neg(x)).to eq(-1)
39
+ end
40
+
41
+ it 'correctly parses string operands to numeric values' do
42
+ expect(add(x, one, 'x' => '1')).to eq(2)
43
+ expect(add(x, one, 'x' => '1.1')).to eq(2.1)
44
+ expect(add(x, one, 'x' => '.1')).to eq(1.1)
45
+ expect { add(x, one, 'x' => 'invalid') }.to raise_error(Dentaku::ArgumentError)
46
+ expect { add(x, one, 'x' => '') }.to raise_error(Dentaku::ArgumentError)
47
+
48
+ int_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "1"))
49
+ int_neg_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "-1"))
50
+ decimal_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "1.0"))
51
+ decimal_neg_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "-1.0"))
52
+
53
+ [int_one, int_neg_one].permutation(2).each do |(left, right)|
54
+ expect(add(left, right).class).to eq(Integer)
55
+ end
56
+
57
+ [decimal_one, decimal_neg_one].each do |left|
58
+ [int_one, int_neg_one, decimal_one, decimal_neg_one].each do |right|
59
+ expect(add(left, right).class).to eq(BigDecimal)
60
+ end
61
+ end
62
+ end
63
+
64
+ it 'performs arithmetic on arrays' do
65
+ expect(add(x, y, 'x' => [1], 'y' => [2])).to eq([1, 2])
66
+ expect(sub(x, y, 'x' => [1], 'y' => [2])).to eq([1])
67
+ end
68
+
69
+ it 'performs date arithmetic' do
70
+ expect(add(date, one)).to eq(DateTime.new(2020, 4, 17))
71
+ expect(sub(date, one)).to eq(DateTime.new(2020, 4, 15))
72
+ end
73
+
74
+ it 'performs arithmetic on object which implements arithmetic' do
75
+ CanHazMath = Struct.new(:value) do
76
+ extend Forwardable
77
+
78
+ def_delegators :value, :zero?
79
+
80
+ def coerce(other)
81
+ case other
82
+ when Numeric
83
+ [other, value]
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ [:+, :-, :/, :*].each do |operand|
90
+ define_method(operand) do |other|
91
+ case other
92
+ when CanHazMath
93
+ value.public_send(operand, other.value)
94
+ when Numeric
95
+ value.public_send(operand, other)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ op_one = CanHazMath.new(1)
102
+ op_two = CanHazMath.new(2)
103
+
104
+ [op_two, two].each do |left|
105
+ [op_one, one].each do |right|
106
+ expect(add(x, y, 'x' => left, 'y' => right)).to eq(3)
107
+ expect(sub(x, y, 'x' => left, 'y' => right)).to eq(1)
108
+ expect(mul(x, y, 'x' => left, 'y' => right)).to eq(2)
109
+ expect(div(x, y, 'x' => left, 'y' => right)).to eq(2)
110
+ end
111
+ end
112
+ end
113
+
114
+ it 'does not try to parse nested string as date' do
115
+ a = ['2017-01-01', '2017-01-02']
116
+ b = ['2017-01-01']
117
+
118
+ expect(Dentaku('a - b', a: a, b: b)).to eq(['2017-01-02'])
119
+ end
120
+
121
+ it 'raises ArgumentError if given individually valid but incompatible arguments' do
122
+ expect { add(one, date) }.to raise_error(Dentaku::ArgumentError)
123
+ expect { add(x, one, 'x' => [1]) }.to raise_error(Dentaku::ArgumentError)
124
+ end
125
+
126
+ private
127
+
128
+ def add(left, right, context = ctx)
129
+ Dentaku::AST::Addition.new(left, right).value(context)
130
+ end
131
+
132
+ def sub(left, right, context = ctx)
133
+ Dentaku::AST::Subtraction.new(left, right).value(context)
134
+ end
135
+
136
+ def mul(left, right, context = ctx)
137
+ Dentaku::AST::Multiplication.new(left, right).value(context)
138
+ end
139
+
140
+ def div(left, right, context = ctx)
141
+ Dentaku::AST::Division.new(left, right).value(context)
142
+ end
143
+
144
+ def neg(node, context = ctx)
145
+ Dentaku::AST::Negation.new(node).value(context)
146
+ end
147
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/functions/avg'
3
+ require 'dentaku'
4
+
5
+ describe 'Dentaku::AST::Function::Avg' do
6
+ it 'returns the average of an array of Numeric values as BigDecimal' do
7
+ result = Dentaku('AVG(1, 2)')
8
+ expect(result).to eq(1.5)
9
+ end
10
+
11
+ it 'returns the average of an array of Numeric values' do
12
+ result = Dentaku('AVG(1, x, 1.8)', x: 2.3)
13
+ expect(result).to eq(1.7)
14
+ end
15
+
16
+ it 'returns the average of a single entry array of a Numeric value' do
17
+ result = Dentaku('AVG(x)', x: 2.3)
18
+ expect(result).to eq(2.3)
19
+ end
20
+
21
+ it 'returns the average even if a String is passed' do
22
+ result = Dentaku('AVG(1, x, 1.8)', x: '2.3')
23
+ expect(result).to eq(1.7)
24
+ end
25
+
26
+ it 'returns the average even if an array is passed' do
27
+ result = Dentaku('AVG(1, x, 2.3)', x: [4, 5])
28
+ expect(result).to eq(3.075)
29
+ end
30
+
31
+ context 'checking errors' do
32
+ let(:calculator) { Dentaku::Calculator.new }
33
+
34
+ it 'raises an error if no arguments are passed' do
35
+ expect { calculator.evaluate!('AVG()') }.to raise_error(Dentaku::ArgumentError)
36
+ end
37
+
38
+ it 'raises an error if an empty array is passed' do
39
+ expect { calculator.evaluate!('AVG(x)', x: []) }.to raise_error(Dentaku::ArgumentError)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/operation'
3
+ require 'dentaku/ast/logical'
4
+ require 'dentaku/ast/identifier'
5
+ require 'dentaku/ast/arithmetic'
6
+ require 'dentaku/ast/case'
7
+
8
+ require 'dentaku/token'
9
+
10
+ describe Dentaku::AST::Case do
11
+ let!(:one) { Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 1) }
12
+ let!(:two) { Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 2) }
13
+ let!(:apple) do
14
+ Dentaku::AST::Logical.new Dentaku::Token.new(:string, 'apple')
15
+ end
16
+ let!(:banana) do
17
+ Dentaku::AST::Logical.new Dentaku::Token.new(:string, 'banana')
18
+ end
19
+ let!(:identifier) do
20
+ Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, :fruit))
21
+ end
22
+ let!(:switch) { Dentaku::AST::CaseSwitchVariable.new(identifier) }
23
+
24
+ let!(:when1) { Dentaku::AST::CaseWhen.new(apple) }
25
+ let!(:then1) { Dentaku::AST::CaseThen.new(one) }
26
+ let!(:conditional1) { Dentaku::AST::CaseConditional.new(when1, then1) }
27
+
28
+ let!(:when2) { Dentaku::AST::CaseWhen.new(banana) }
29
+ let!(:then2) { Dentaku::AST::CaseThen.new(two) }
30
+ let!(:conditional2) { Dentaku::AST::CaseConditional.new(when2, then2) }
31
+
32
+ describe '#value' do
33
+ it 'raises an exception if there is no switch variable' do
34
+ expect { described_class.new(conditional1, conditional2) }
35
+ .to raise_error('Case missing switch variable')
36
+ end
37
+
38
+ it 'raises an exception if a non-conditional is passed' do
39
+ expect { described_class.new(switch, conditional1, when2) }
40
+ .to raise_error(/is not a CaseConditional/)
41
+ end
42
+
43
+ it 'tests each conditional against the switch variable' do
44
+ node = described_class.new(switch, conditional1, conditional2)
45
+ expect(node.value(fruit: 'banana')).to eq(2)
46
+ end
47
+
48
+ it 'raises an exception if the conditional is not matched' do
49
+ node = described_class.new(switch, conditional1, conditional2)
50
+ expect { node.value(fruit: 'orange') }
51
+ .to raise_error("No block matched the switch value 'orange'")
52
+ end
53
+
54
+ it 'uses the else value if provided and conditional is not matched' do
55
+ three = Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 3)
56
+ else_statement = Dentaku::AST::CaseElse.new(three)
57
+ node = described_class.new(
58
+ switch,
59
+ conditional1,
60
+ conditional2,
61
+ else_statement)
62
+ expect(node.value(fruit: 'orange')).to eq(3)
63
+ end
64
+ end
65
+
66
+ describe '#dependencies' do
67
+ let!(:tax) do
68
+ Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, :tax))
69
+ end
70
+ let!(:fallback) do
71
+ Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, :fallback))
72
+ end
73
+ let!(:addition) { Dentaku::AST::Addition.new(two, tax) }
74
+ let!(:when2) { Dentaku::AST::CaseWhen.new(banana) }
75
+ let!(:then2) { Dentaku::AST::CaseThen.new(addition) }
76
+ let!(:else2) { Dentaku::AST::CaseElse.new(fallback) }
77
+ let!(:conditional2) { Dentaku::AST::CaseConditional.new(when2, then2) }
78
+
79
+ it 'gathers dependencies from switch and conditionals' do
80
+ node = described_class.new(switch, conditional1, conditional2, else2)
81
+ expect(node.dependencies).to eq([:fruit, :tax, :fallback])
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/comparators'
3
+
4
+ require 'dentaku/token'
5
+
6
+ describe Dentaku::AST::Comparator do
7
+ let(:one) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 1) }
8
+ let(:one_str) { Dentaku::AST::String.new Dentaku::Token.new(:string, '1') }
9
+ let(:two) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 2) }
10
+ let(:two_str) { Dentaku::AST::String.new Dentaku::Token.new(:string, '2') }
11
+ let(:x) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'x') }
12
+ let(:y) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'y') }
13
+ let(:nilly) do
14
+ Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'nilly')
15
+ end
16
+ let(:ctx) { { 'x' => 'hello', 'y' => 'world', 'nilly' => nil } }
17
+
18
+ it 'performs comparison with numeric operands' do
19
+ expect(less_than(one, two).value(ctx)).to be_truthy
20
+ expect(less_than(two, one).value(ctx)).to be_falsey
21
+ expect(greater_than(two, one).value(ctx)).to be_truthy
22
+ expect(not_equal(x, y).value(ctx)).to be_truthy
23
+ expect(equal(x, y).value(ctx)).to be_falsey
24
+ end
25
+
26
+ it 'performs conversion from string to numeric operands' do
27
+ expect(less_than(one, two_str).value(ctx)).to be_truthy
28
+ expect(less_than(one_str, two_str).value(ctx)).to be_truthy
29
+ expect(less_than(one_str, two).value(ctx)).to be_truthy
30
+ end
31
+
32
+ it 'raises a dentaku argument error when incorrect arguments are passed in' do
33
+ expect { less_than(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
34
+ expect { less_than_or_equal(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
35
+ expect { greater_than(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
36
+ expect { greater_than_or_equal(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
37
+ expect { greater_than_or_equal(one, x).value(ctx) }.to raise_error Dentaku::ArgumentError
38
+ expect { not_equal(one, nilly).value(ctx) }.to_not raise_error
39
+ expect { equal(one, nilly).value(ctx) }.to_not raise_error
40
+ end
41
+
42
+ it 'raises a dentaku argument error when nil is passed in as first argument' do
43
+ expect { less_than(nilly, one).value(ctx) }.to raise_error Dentaku::ArgumentError
44
+ expect { less_than_or_equal(nilly, one).value(ctx) }.to raise_error Dentaku::ArgumentError
45
+ expect { greater_than(nilly, one).value(ctx) }.to raise_error Dentaku::ArgumentError
46
+ expect { greater_than_or_equal(nilly, one).value(ctx) }.to raise_error Dentaku::ArgumentError
47
+ expect { not_equal(nilly, one).value(ctx) }.to_not raise_error
48
+ expect { equal(nilly, one).value(ctx) }.to_not raise_error
49
+ end
50
+
51
+ it 'returns correct operator symbols' do
52
+ expect(less_than(one, two).operator).to eq(:<)
53
+ expect(less_than_or_equal(one, two).operator).to eq(:<=)
54
+ expect(greater_than(one, two).operator).to eq(:>)
55
+ expect(greater_than_or_equal(one, two).operator).to eq(:>=)
56
+ expect(not_equal(x, y).operator).to eq(:!=)
57
+ expect(equal(x, y).operator).to eq(:==)
58
+ expect { Dentaku::AST::Comparator.new(one, two).operator }
59
+ .to raise_error(NotImplementedError)
60
+ end
61
+
62
+ private
63
+
64
+ def less_than(left, right)
65
+ Dentaku::AST::LessThan.new(left, right)
66
+ end
67
+
68
+ def less_than_or_equal(left, right)
69
+ Dentaku::AST::LessThanOrEqual.new(left, right)
70
+ end
71
+
72
+ def greater_than(left, right)
73
+ Dentaku::AST::GreaterThan.new(left, right)
74
+ end
75
+
76
+ def greater_than_or_equal(left, right)
77
+ Dentaku::AST::GreaterThanOrEqual.new(left, right)
78
+ end
79
+
80
+ def not_equal(left, right)
81
+ Dentaku::AST::NotEqual.new(left, right)
82
+ end
83
+
84
+ def equal(left, right)
85
+ Dentaku::AST::Equal.new(left, right)
86
+ end
87
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/functions/count'
3
+ require 'dentaku'
4
+
5
+ describe 'Dentaku::AST::Count' do
6
+ it 'returns the length of an array' do
7
+ result = Dentaku('COUNT(1, x, 1.8)', x: 2.3)
8
+ expect(result).to eq(3)
9
+ end
10
+
11
+ it 'returns the length of a single number object' do
12
+ result = Dentaku('COUNT(x)', x: 2.3)
13
+ expect(result).to eq(1)
14
+ end
15
+
16
+ it 'returns the length if a single String is passed' do
17
+ result = Dentaku('COUNT(x)', x: 'dentaku')
18
+ expect(result).to eq(7)
19
+ end
20
+
21
+ it 'returns the length if an array is passed' do
22
+ result = Dentaku('COUNT(x)', x: [4, 5])
23
+ expect(result).to eq(2)
24
+ end
25
+
26
+ it 'returns the length if an array with one element is passed' do
27
+ result = Dentaku('COUNT(x)', x: [4])
28
+ expect(result).to eq(1)
29
+ end
30
+
31
+ it 'returns the length if an array even if it has nested array' do
32
+ result = Dentaku('COUNT(1, x, 3)', x: [4, 5])
33
+ expect(result).to eq(3)
34
+ end
35
+
36
+ it 'returns the length if an array is passed' do
37
+ result = Dentaku('COUNT()')
38
+ expect(result).to eq(0)
39
+ end
40
+ end