dentaku 3.4.2 → 3.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +4 -5
  4. data/lib/dentaku/ast/access.rb +6 -0
  5. data/lib/dentaku/ast/arithmetic.rb +23 -18
  6. data/lib/dentaku/ast/array.rb +4 -0
  7. data/lib/dentaku/ast/bitwise.rb +30 -5
  8. data/lib/dentaku/ast/case/case_conditional.rb +4 -0
  9. data/lib/dentaku/ast/case/case_else.rb +6 -0
  10. data/lib/dentaku/ast/case/case_switch_variable.rb +6 -0
  11. data/lib/dentaku/ast/case/case_then.rb +6 -0
  12. data/lib/dentaku/ast/case/case_when.rb +10 -0
  13. data/lib/dentaku/ast/case.rb +6 -0
  14. data/lib/dentaku/ast/comparators.rb +35 -35
  15. data/lib/dentaku/ast/function.rb +6 -8
  16. data/lib/dentaku/ast/functions/all.rb +4 -17
  17. data/lib/dentaku/ast/functions/any.rb +4 -17
  18. data/lib/dentaku/ast/functions/duration.rb +2 -2
  19. data/lib/dentaku/ast/functions/enum.rb +37 -0
  20. data/lib/dentaku/ast/functions/filter.rb +4 -17
  21. data/lib/dentaku/ast/functions/if.rb +4 -0
  22. data/lib/dentaku/ast/functions/map.rb +3 -16
  23. data/lib/dentaku/ast/functions/pluck.rb +8 -7
  24. data/lib/dentaku/ast/functions/ruby_math.rb +3 -2
  25. data/lib/dentaku/ast/functions/xor.rb +44 -0
  26. data/lib/dentaku/ast/identifier.rb +8 -0
  27. data/lib/dentaku/ast/literal.rb +10 -0
  28. data/lib/dentaku/ast/negation.rb +4 -0
  29. data/lib/dentaku/ast/nil.rb +4 -0
  30. data/lib/dentaku/ast/node.rb +4 -0
  31. data/lib/dentaku/ast/operation.rb +9 -0
  32. data/lib/dentaku/ast/string.rb +7 -0
  33. data/lib/dentaku/ast.rb +2 -0
  34. data/lib/dentaku/bulk_expression_solver.rb +1 -5
  35. data/lib/dentaku/exceptions.rb +2 -2
  36. data/lib/dentaku/parser.rb +10 -3
  37. data/lib/dentaku/print_visitor.rb +101 -0
  38. data/lib/dentaku/token_scanner.rb +3 -3
  39. data/lib/dentaku/version.rb +1 -1
  40. data/lib/dentaku/visitor/infix.rb +82 -0
  41. data/spec/ast/all_spec.rb +25 -0
  42. data/spec/ast/any_spec.rb +23 -0
  43. data/spec/ast/arithmetic_spec.rb +7 -0
  44. data/spec/ast/comparator_spec.rb +14 -9
  45. data/spec/ast/filter_spec.rb +7 -0
  46. data/spec/ast/function_spec.rb +5 -0
  47. data/spec/ast/map_spec.rb +12 -0
  48. data/spec/ast/or_spec.rb +1 -1
  49. data/spec/ast/pluck_spec.rb +32 -0
  50. data/spec/ast/xor_spec.rb +35 -0
  51. data/spec/bulk_expression_solver_spec.rb +9 -0
  52. data/spec/calculator_spec.rb +71 -2
  53. data/spec/parser_spec.rb +18 -3
  54. data/spec/print_visitor_spec.rb +66 -0
  55. data/spec/tokenizer_spec.rb +18 -0
  56. data/spec/visitor/infix_spec.rb +31 -0
  57. data/spec/visitor_spec.rb +138 -0
  58. metadata +24 -6
@@ -1,23 +1,10 @@
1
- require_relative '../function'
2
- require_relative '../../exceptions'
1
+ require_relative './enum'
3
2
 
4
3
  module Dentaku
5
4
  module AST
6
- class Map < Function
7
- def self.min_param_count
8
- 3
9
- end
10
-
11
- def self.max_param_count
12
- 3
13
- end
14
-
15
- def deferred_args
16
- [1, 2]
17
- end
18
-
5
+ class Map < Enum
19
6
  def value(context = {})
20
- collection = @args[0].value(context)
7
+ collection = Array(@args[0].value(context))
21
8
  item_identifier = @args[1].identifier
22
9
  expression = @args[2]
23
10
 
@@ -1,9 +1,9 @@
1
- require_relative '../function'
1
+ require_relative './enum'
2
2
  require_relative '../../exceptions'
3
3
 
4
4
  module Dentaku
5
5
  module AST
6
- class Pluck < Function
6
+ class Pluck < Enum
7
7
  def self.min_param_count
8
8
  2
9
9
  end
@@ -12,12 +12,13 @@ module Dentaku
12
12
  2
13
13
  end
14
14
 
15
- def deferred_args
16
- [1]
17
- end
18
-
19
15
  def value(context = {})
20
- collection = @args[0].value(context)
16
+ collection = Array(@args[0].value(context))
17
+ unless collection.all? { |elem| elem.is_a?(Hash) }
18
+ raise ArgumentError.for(:incompatible_type, value: collection),
19
+ 'PLUCK() requires first argument to be an array of hashes'
20
+ end
21
+
21
22
  pluck_path = @args[1].identifier
22
23
 
23
24
  collection.map { |h| h.transform_keys(&:to_s)[pluck_path] }
@@ -5,9 +5,10 @@ module Dentaku
5
5
  module AST
6
6
  class RubyMath < Function
7
7
  def self.[](method)
8
- klass = Class.new(self)
8
+ klass_name = method.to_s.capitalize
9
+ klass = const_set(klass_name , Class.new(self))
9
10
  klass.implement(method)
10
- klass
11
+ const_get(klass_name)
11
12
  end
12
13
 
13
14
  def self.implement(method)
@@ -0,0 +1,44 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Xor < Function
7
+ def self.min_param_count
8
+ 1
9
+ end
10
+
11
+ def self.max_param_count
12
+ Float::INFINITY
13
+ end
14
+
15
+ def value(context = {})
16
+ if @args.empty?
17
+ raise Dentaku::ArgumentError.for(
18
+ :too_few_arguments,
19
+ function_name: 'XOR()', at_least: 1, given: 0
20
+ ), 'XOR() requires at least one argument'
21
+ end
22
+
23
+ true_arg_count = 0
24
+ @args.each do |arg|
25
+ case arg.value(context)
26
+ when TrueClass
27
+ true_arg_count += 1
28
+ break if true_arg_count > 1
29
+ when FalseClass, nil
30
+ next
31
+ else
32
+ raise Dentaku::ArgumentError.for(
33
+ :incompatible_type,
34
+ function_name: 'XOR()', expect: :logical, actual: arg.class
35
+ ), 'XOR() requires arguments to be logical expressions'
36
+ end
37
+ end
38
+ true_arg_count == 1
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Dentaku::AST::Function.register_class(:xor, Dentaku::AST::Xor)
@@ -34,6 +34,14 @@ module Dentaku
34
34
  context.key?(identifier) ? dependencies_of(context[identifier], context) : [identifier]
35
35
  end
36
36
 
37
+ def accept(visitor)
38
+ visitor.visit_identifier(self)
39
+ end
40
+
41
+ def to_s
42
+ identifier.to_s
43
+ end
44
+
37
45
  private
38
46
 
39
47
  def dependencies_of(node, context)
@@ -4,6 +4,7 @@ module Dentaku
4
4
  attr_reader :type
5
5
 
6
6
  def initialize(token)
7
+ @token = token
7
8
  @value = token.value
8
9
  @type = token.category
9
10
  end
@@ -15,6 +16,15 @@ module Dentaku
15
16
  def dependencies(*)
16
17
  []
17
18
  end
19
+
20
+ def accept(visitor)
21
+ visitor.visit_literal(self)
22
+ end
23
+
24
+ def quoted
25
+ @token.raw_value || value.to_s
26
+ end
27
+ alias_method :to_s, :quoted
18
28
  end
19
29
  end
20
30
  end
@@ -40,6 +40,10 @@ module Dentaku
40
40
  @node.dependencies(context)
41
41
  end
42
42
 
43
+ def accept(visitor)
44
+ visitor.visit_negation(self)
45
+ end
46
+
43
47
  private
44
48
 
45
49
  def valid_node?(node)
@@ -4,6 +4,10 @@ module Dentaku
4
4
  def value(*)
5
5
  nil
6
6
  end
7
+
8
+ def accept(visitor)
9
+ visitor.visit_nil(self)
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -19,6 +19,10 @@ module Dentaku
19
19
  def type
20
20
  nil
21
21
  end
22
+
23
+ def name
24
+ self.class.name.to_s.split("::").last.upcase
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -25,6 +25,15 @@ module Dentaku
25
25
  def self.right_associative?
26
26
  false
27
27
  end
28
+
29
+ def accept(visitor)
30
+ visitor.visit_operation(self)
31
+ end
32
+
33
+ def display_operator
34
+ operator.to_s
35
+ end
36
+ alias_method :to_s, :display_operator
28
37
  end
29
38
  end
30
39
  end
@@ -3,6 +3,13 @@ require_relative "./literal"
3
3
  module Dentaku
4
4
  module AST
5
5
  class String < Literal
6
+ def quoted
7
+ %Q{"#{ escaped }"}
8
+ end
9
+
10
+ def escaped
11
+ @value.gsub('"', '\"')
12
+ end
6
13
  end
7
14
  end
8
15
  end
data/lib/dentaku/ast.rb CHANGED
@@ -21,6 +21,7 @@ require_relative './ast/functions/any'
21
21
  require_relative './ast/functions/avg'
22
22
  require_relative './ast/functions/count'
23
23
  require_relative './ast/functions/duration'
24
+ require_relative './ast/functions/filter'
24
25
  require_relative './ast/functions/if'
25
26
  require_relative './ast/functions/map'
26
27
  require_relative './ast/functions/max'
@@ -35,3 +36,4 @@ require_relative './ast/functions/ruby_math'
35
36
  require_relative './ast/functions/string_functions'
36
37
  require_relative './ast/functions/sum'
37
38
  require_relative './ast/functions/switch'
39
+ require_relative './ast/functions/xor'
@@ -89,13 +89,9 @@ module Dentaku
89
89
  def with_rescues(var_name, results, block)
90
90
  yield
91
91
 
92
- rescue UnboundVariableError, Dentaku::ZeroDivisionError => ex
92
+ rescue Dentaku::UnboundVariableError, Dentaku::ZeroDivisionError, Dentaku::ArgumentError => ex
93
93
  ex.recipient_variable = var_name
94
94
  results[var_name] = block.call(ex)
95
-
96
- rescue Dentaku::ArgumentError => ex
97
- results[var_name] = block.call(ex)
98
-
99
95
  ensure
100
96
  if results[var_name] == :undefined && calculator.memory.has_key?(var_name.downcase)
101
97
  results[var_name] = calculator.memory[var_name.downcase]
@@ -1,10 +1,9 @@
1
1
  module Dentaku
2
2
  class Error < StandardError
3
+ attr_accessor :recipient_variable
3
4
  end
4
5
 
5
6
  class UnboundVariableError < Error
6
- attr_accessor :recipient_variable
7
-
8
7
  attr_reader :unbound_variables
9
8
 
10
9
  def initialize(unbound_variables)
@@ -74,6 +73,7 @@ module Dentaku
74
73
 
75
74
  class ArgumentError < ::ArgumentError
76
75
  attr_reader :reason, :meta
76
+ attr_accessor :recipient_variable
77
77
 
78
78
  def initialize(reason, **meta)
79
79
  @reason = reason
@@ -10,8 +10,11 @@ module Dentaku
10
10
  pow: AST::Exponentiation,
11
11
  negate: AST::Negation,
12
12
  mod: AST::Modulo,
13
+
13
14
  bitor: AST::BitwiseOr,
14
15
  bitand: AST::BitwiseAnd,
16
+ bitshiftleft: AST::BitwiseShiftLeft,
17
+ bitshiftright: AST::BitwiseShiftRight,
15
18
 
16
19
  lt: AST::LessThan,
17
20
  gt: AST::GreaterThan,
@@ -22,6 +25,7 @@ module Dentaku
22
25
 
23
26
  and: AST::And,
24
27
  or: AST::Or,
28
+ xor: AST::Xor,
25
29
  }.freeze
26
30
 
27
31
  attr_reader :input, :output, :operations, :arities, :case_sensitive
@@ -37,17 +41,19 @@ module Dentaku
37
41
 
38
42
  def consume(count = 2)
39
43
  operator = operations.pop
44
+ fail! :invalid_statement if operator.nil?
45
+
40
46
  operator.peek(output)
41
47
 
42
48
  args_size = operator.arity || count
43
49
  min_size = operator.arity || operator.min_param_count || count
44
50
  max_size = operator.arity || operator.max_param_count || count
45
51
 
46
- if output.length < min_size
52
+ if output.length < min_size || args_size < min_size
47
53
  fail! :too_few_operands, operator: operator, expect: min_size, actual: output.length
48
54
  end
49
55
 
50
- if output.length > max_size && operations.empty?
56
+ if output.length > max_size && operations.empty? || args_size > max_size
51
57
  fail! :too_many_operands, operator: operator, expect: max_size, actual: output.length
52
58
  end
53
59
 
@@ -143,7 +149,8 @@ module Dentaku
143
149
  inner_case_inputs,
144
150
  operations: [AST::Case],
145
151
  arities: [0],
146
- function_registry: @function_registry
152
+ function_registry: @function_registry,
153
+ case_sensitive: case_sensitive
147
154
  )
148
155
  subparser.parse
149
156
  output.concat(subparser.output)
@@ -0,0 +1,101 @@
1
+ module Dentaku
2
+ class PrintVisitor
3
+ def initialize(node)
4
+ @output = ''
5
+ node.accept(self)
6
+ end
7
+
8
+ def visit_operation(node)
9
+ if node.left
10
+ visit_operand(node.left, node.class.precedence, suffix: " ")
11
+ end
12
+
13
+ @output << node.display_operator
14
+
15
+ if node.right
16
+ visit_operand(node.right, node.class.precedence, prefix: " ")
17
+ end
18
+ end
19
+
20
+ def visit_operand(node, precedence, prefix: "", suffix: "")
21
+ @output << prefix
22
+ @output << "(" if node.is_a?(Dentaku::AST::Operation) && node.class.precedence < precedence
23
+ node.accept(self)
24
+ @output << ")" if node.is_a?(Dentaku::AST::Operation) && node.class.precedence < precedence
25
+ @output << suffix
26
+ end
27
+
28
+ def visit_function(node)
29
+ @output << node.name
30
+ @output << "("
31
+ arg_count = node.args.length
32
+ node.args.each_with_index do |a, index|
33
+ a.accept(self)
34
+ @output << ", " unless index >= arg_count - 1
35
+ end
36
+ @output << ")"
37
+ end
38
+
39
+ def visit_case(node)
40
+ @output << "CASE "
41
+ node.switch.accept(self)
42
+ node.conditions.each { |c| c.accept(self) }
43
+ node.else && node.else.accept(self)
44
+ @output << " END"
45
+ end
46
+
47
+ def visit_switch(node)
48
+ node.node.accept(self)
49
+ end
50
+
51
+ def visit_case_conditional(node)
52
+ node.when.accept(self)
53
+ node.then.accept(self)
54
+ end
55
+
56
+ def visit_when(node)
57
+ @output << " WHEN "
58
+ node.node.accept(self)
59
+ end
60
+
61
+ def visit_then(node)
62
+ @output << " THEN "
63
+ node.node.accept(self)
64
+ end
65
+
66
+ def visit_else(node)
67
+ @output << " ELSE "
68
+ node.node.accept(self)
69
+ end
70
+
71
+ def visit_negation(node)
72
+ @output << "-"
73
+ @output << "(" unless node.node.is_a? Dentaku::AST::Literal
74
+ node.node.accept(self)
75
+ @output << ")" unless node.node.is_a? Dentaku::AST::Literal
76
+ end
77
+
78
+ def visit_access(node)
79
+ node.structure.accept(self)
80
+ @output << "["
81
+ node.index.accept(self)
82
+ @output << "]"
83
+ end
84
+
85
+ def visit_literal(node)
86
+ @output << node.quoted
87
+ end
88
+
89
+ def visit_identifier(node)
90
+ @output << node.identifier
91
+ end
92
+
93
+ def visit_nil(node)
94
+ @output << "NULL"
95
+ end
96
+
97
+ def to_s
98
+ @output
99
+ end
100
+ end
101
+ end
@@ -91,7 +91,7 @@ module Dentaku
91
91
 
92
92
  def numeric
93
93
  new(:numeric, '((?:\d+(\.\d+)?|\.\d+)(?:(e|E)(\+|-)?\d+)?)\b', lambda { |raw|
94
- raw =~ /\./ ? BigDecimal(raw) : raw.to_i
94
+ raw =~ /(\.|e|E)/ ? BigDecimal(raw) : raw.to_i
95
95
  })
96
96
  end
97
97
 
@@ -120,9 +120,9 @@ module Dentaku
120
120
 
121
121
  def operator
122
122
  names = {
123
- pow: '^', add: '+', subtract: '-', multiply: '*', divide: '/', mod: '%', bitor: '|', bitand: '&'
123
+ pow: '^', add: '+', subtract: '-', multiply: '*', divide: '/', mod: '%', bitor: '|', bitand: '&', bitshiftleft: '<<', bitshiftright: '>>'
124
124
  }.invert
125
- new(:operator, '\^|\+|-|\*|\/|%|\||&', lambda { |raw| names[raw] })
125
+ new(:operator, '\^|\+|-|\*|\/|%|\||&|<<|>>', lambda { |raw| names[raw] })
126
126
  end
127
127
 
128
128
  def grouping
@@ -1,3 +1,3 @@
1
1
  module Dentaku
2
- VERSION = "3.4.2"
2
+ VERSION = "3.5.1"
3
3
  end
@@ -0,0 +1,82 @@
1
+ # infix visitor
2
+ #
3
+ # use this visitor in a processor to get infix visiting order
4
+ #
5
+ # visitor node deps
6
+ # accept -> visit left ->
7
+ # process
8
+ # visit right ->
9
+ module Dentaku
10
+ module Visitor
11
+ module Infix
12
+ def visit(ast)
13
+ ast.accept(self)
14
+ end
15
+
16
+ def process(_ast)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def visit_function(node)
21
+ node.args.each do |arg|
22
+ visit(arg)
23
+ end
24
+ process(node)
25
+ end
26
+
27
+ def visit_identifier(node)
28
+ process(node)
29
+ end
30
+
31
+ def visit_operation(node)
32
+ visit(node.left) if node.left
33
+ process(node)
34
+ visit(node.right) if node.right
35
+ end
36
+
37
+ def visit_operand(node)
38
+ process(node)
39
+ end
40
+
41
+ def visit_case(node)
42
+ process(node)
43
+ end
44
+
45
+ def visit_switch(node)
46
+ process(node)
47
+ end
48
+
49
+ def visit_case_conditional(node)
50
+ process(node)
51
+ end
52
+
53
+ def visit_when(node)
54
+ process(node)
55
+ end
56
+
57
+ def visit_then(node)
58
+ process(node)
59
+ end
60
+
61
+ def visit_else(node)
62
+ process(node)
63
+ end
64
+
65
+ def visit_negation(node)
66
+ process(node)
67
+ end
68
+
69
+ def visit_access(node)
70
+ process(node)
71
+ end
72
+
73
+ def visit_literal(node)
74
+ process(node)
75
+ end
76
+
77
+ def visit_nil(node)
78
+ process(node)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,25 @@
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
+ it 'performs ALL operation' do
8
+ result = Dentaku('ALL(vals, val, val > 1)', vals: [1, 2, 3])
9
+ expect(result).to eq(false)
10
+ end
11
+
12
+ it 'works with a single value if needed for some reason' do
13
+ result = Dentaku('ALL(vals, val, val > 1)', vals: 1)
14
+ expect(result).to eq(false)
15
+
16
+ result = Dentaku('ALL(vals, val, val > 1)', vals: 2)
17
+ expect(result).to eq(true)
18
+ end
19
+
20
+ it 'raises argument error if a string is passed as identifier' do
21
+ expect { calculator.evaluate!('ALL({1, 2, 3}, "val", val % 2 == 0)') }.to raise_error(
22
+ Dentaku::ArgumentError, 'ALL() requires second argument to be an identifier'
23
+ )
24
+ end
25
+ end
@@ -0,0 +1,23 @@
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
+ it 'performs ANY operation' do
8
+ result = Dentaku('ANY(vals, val, val > 1)', vals: [1, 2, 3])
9
+ expect(result).to eq(true)
10
+ end
11
+
12
+ it 'works with a single value if needed for some reason' do
13
+ result = Dentaku('ANY(vals, val, val > 1)', vals: 1)
14
+ expect(result).to eq(false)
15
+
16
+ result = Dentaku('ANY(vals, val, val > 1)', vals: 2)
17
+ expect(result).to eq(true)
18
+ end
19
+
20
+ it 'raises argument error if a string is passed as identifier' do
21
+ expect { calculator.evaluate!('ANY({1, 2, 3}, "val", val % 2 == 0)') }.to raise_error(Dentaku::ArgumentError)
22
+ end
23
+ end
@@ -45,6 +45,13 @@ describe Dentaku::AST::Arithmetic do
45
45
  expect(add(x, one, 'x' => '.1')).to eq(1.1)
46
46
  expect { add(x, one, 'x' => 'invalid') }.to raise_error(Dentaku::ArgumentError)
47
47
  expect { add(x, one, 'x' => '') }.to raise_error(Dentaku::ArgumentError)
48
+
49
+ int_one = Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, "1")
50
+ decimal_one = Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, "1.0")
51
+
52
+ expect(add(int_one, int_one).class).to eq(Integer)
53
+ expect(add(int_one, decimal_one).class).to eq(BigDecimal)
54
+ expect(add(decimal_one, decimal_one).class).to eq(BigDecimal)
48
55
  end
49
56
 
50
57
  it 'performs arithmetic on arrays' do
@@ -5,7 +5,9 @@ require 'dentaku/token'
5
5
 
6
6
  describe Dentaku::AST::Comparator do
7
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') }
8
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') }
9
11
  let(:x) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'x') }
10
12
  let(:y) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'y') }
11
13
  let(:nilly) do
@@ -21,20 +23,27 @@ describe Dentaku::AST::Comparator do
21
23
  expect(equal(x, y).value(ctx)).to be_falsey
22
24
  end
23
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
+
24
32
  it 'raises a dentaku argument error when incorrect arguments are passed in' do
25
33
  expect { less_than(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
26
34
  expect { less_than_or_equal(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
27
35
  expect { greater_than(one, nilly).value(ctx) }.to raise_error Dentaku::ArgumentError
28
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
29
38
  expect { not_equal(one, nilly).value(ctx) }.to_not raise_error
30
39
  expect { equal(one, nilly).value(ctx) }.to_not raise_error
31
40
  end
32
41
 
33
- it 'raises a dentaku error when nil is passed in as first argument' do
34
- expect { less_than(nilly, one).value(ctx) }.to raise_error Dentaku::Error
35
- expect { less_than_or_equal(nilly, one).value(ctx) }.to raise_error Dentaku::Error
36
- expect { greater_than(nilly, one).value(ctx) }.to raise_error Dentaku::Error
37
- expect { greater_than_or_equal(nilly, one).value(ctx) }.to raise_error Dentaku::Error
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
38
47
  expect { not_equal(nilly, one).value(ctx) }.to_not raise_error
39
48
  expect { equal(nilly, one).value(ctx) }.to_not raise_error
40
49
  end
@@ -50,10 +59,6 @@ describe Dentaku::AST::Comparator do
50
59
  .to raise_error(NotImplementedError)
51
60
  end
52
61
 
53
- it 'relies on inheriting classes to expose value method' do
54
- expect { described_class.new(one, two).value(ctx) }.to raise_error NoMethodError
55
- end
56
-
57
62
  private
58
63
 
59
64
  def less_than(left, right)
@@ -3,6 +3,7 @@ require 'dentaku/ast/functions/filter'
3
3
  require 'dentaku'
4
4
 
5
5
  describe Dentaku::AST::Filter do
6
+ let(:calculator) { Dentaku::Calculator.new }
6
7
  it 'excludes unmatched values' do
7
8
  result = Dentaku('SUM(FILTER(vals, val, val > 1))', vals: [1, 2, 3])
8
9
  expect(result).to eq(5)
@@ -15,4 +16,10 @@ describe Dentaku::AST::Filter do
15
16
  result = Dentaku('FILTER(vals, val, val > 1)', vals: 2)
16
17
  expect(result).to eq([2])
17
18
  end
19
+
20
+ it 'raises argument error if a string is passed as identifier' do
21
+ expect { calculator.evaluate!('FILTER({1, 2, 3}, "val", val % 2 == 0)') }.to raise_error(
22
+ Dentaku::ArgumentError, 'FILTER() requires second argument to be an identifier'
23
+ )
24
+ end
18
25
  end