dentaku 3.0.0 → 3.1.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +119 -0
  3. data/.travis.yml +8 -9
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +0 -5
  6. data/LICENSE +21 -0
  7. data/README.md +44 -3
  8. data/Rakefile +4 -1
  9. data/dentaku.gemspec +8 -4
  10. data/lib/dentaku.rb +15 -2
  11. data/lib/dentaku/ast.rb +1 -0
  12. data/lib/dentaku/ast/access.rb +2 -2
  13. data/lib/dentaku/ast/arithmetic.rb +7 -7
  14. data/lib/dentaku/ast/bitwise.rb +2 -2
  15. data/lib/dentaku/ast/case.rb +5 -5
  16. data/lib/dentaku/ast/case/case_conditional.rb +1 -1
  17. data/lib/dentaku/ast/case/case_else.rb +2 -2
  18. data/lib/dentaku/ast/case/case_switch_variable.rb +2 -2
  19. data/lib/dentaku/ast/case/case_then.rb +2 -2
  20. data/lib/dentaku/ast/case/case_when.rb +2 -2
  21. data/lib/dentaku/ast/combinators.rb +10 -2
  22. data/lib/dentaku/ast/comparators.rb +34 -6
  23. data/lib/dentaku/ast/function.rb +1 -1
  24. data/lib/dentaku/ast/function_registry.rb +1 -1
  25. data/lib/dentaku/ast/functions/if.rb +6 -2
  26. data/lib/dentaku/ast/functions/max.rb +1 -1
  27. data/lib/dentaku/ast/functions/min.rb +1 -1
  28. data/lib/dentaku/ast/functions/ruby_math.rb +1 -1
  29. data/lib/dentaku/ast/functions/string_functions.rb +8 -8
  30. data/lib/dentaku/ast/functions/sum.rb +12 -0
  31. data/lib/dentaku/ast/grouping.rb +2 -2
  32. data/lib/dentaku/ast/identifier.rb +8 -5
  33. data/lib/dentaku/ast/negation.rb +2 -2
  34. data/lib/dentaku/ast/node.rb +1 -1
  35. data/lib/dentaku/ast/operation.rb +1 -1
  36. data/lib/dentaku/bulk_expression_solver.rb +39 -20
  37. data/lib/dentaku/calculator.rb +38 -28
  38. data/lib/dentaku/dependency_resolver.rb +1 -1
  39. data/lib/dentaku/flat_hash.rb +31 -0
  40. data/lib/dentaku/parser.rb +7 -6
  41. data/lib/dentaku/string_casing.rb +7 -0
  42. data/lib/dentaku/token.rb +1 -1
  43. data/lib/dentaku/token_matcher.rb +4 -4
  44. data/lib/dentaku/token_scanner.rb +18 -7
  45. data/lib/dentaku/tokenizer.rb +26 -2
  46. data/lib/dentaku/version.rb +1 -1
  47. data/spec/ast/arithmetic_spec.rb +2 -2
  48. data/spec/ast/comparator_spec.rb +57 -0
  49. data/spec/ast/function_spec.rb +1 -1
  50. data/spec/ast/max_spec.rb +5 -0
  51. data/spec/ast/min_spec.rb +5 -0
  52. data/spec/ast/sum_spec.rb +38 -0
  53. data/spec/benchmark.rb +2 -2
  54. data/spec/bulk_expression_solver_spec.rb +89 -1
  55. data/spec/calculator_spec.rb +40 -7
  56. data/spec/dentaku_spec.rb +11 -0
  57. data/spec/external_function_spec.rb +7 -7
  58. data/spec/parser_spec.rb +11 -11
  59. data/spec/spec_helper.rb +21 -3
  60. data/spec/token_matcher_spec.rb +0 -1
  61. data/spec/token_spec.rb +6 -0
  62. data/spec/tokenizer_spec.rb +37 -0
  63. metadata +70 -5
@@ -3,13 +3,13 @@ require_relative './operation'
3
3
  module Dentaku
4
4
  module AST
5
5
  class BitwiseOr < Operation
6
- def value(context={})
6
+ def value(context = {})
7
7
  left.value(context) | right.value(context)
8
8
  end
9
9
  end
10
10
 
11
11
  class BitwiseAnd < Operation
12
- def value(context={})
12
+ def value(context = {})
13
13
  left.value(context) & right.value(context)
14
14
  end
15
15
  end
@@ -25,7 +25,7 @@ module Dentaku
25
25
  end
26
26
  end
27
27
 
28
- def value(context={})
28
+ def value(context = {})
29
29
  switch_value = @switch.value(context)
30
30
  @conditions.each do |condition|
31
31
  if condition.when.value(context) == switch_value
@@ -40,7 +40,7 @@ module Dentaku
40
40
  end
41
41
  end
42
42
 
43
- def dependencies(context={})
43
+ def dependencies(context = {})
44
44
  # TODO: should short-circuit
45
45
  switch_dependencies(context) +
46
46
  condition_dependencies(context) +
@@ -49,15 +49,15 @@ module Dentaku
49
49
 
50
50
  private
51
51
 
52
- def switch_dependencies(context={})
52
+ def switch_dependencies(context = {})
53
53
  @switch.dependencies(context)
54
54
  end
55
55
 
56
- def condition_dependencies(context={})
56
+ def condition_dependencies(context = {})
57
57
  @conditions.flat_map { |condition| condition.dependencies(context) }
58
58
  end
59
59
 
60
- def else_dependencies(context={})
60
+ def else_dependencies(context = {})
61
61
  @else ? @else.dependencies(context) : []
62
62
  end
63
63
  end
@@ -15,7 +15,7 @@ module Dentaku
15
15
  end
16
16
  end
17
17
 
18
- def dependencies(context={})
18
+ def dependencies(context = {})
19
19
  @when.dependencies(context) + @then.dependencies(context)
20
20
  end
21
21
  end
@@ -9,11 +9,11 @@ module Dentaku
9
9
  @node = node
10
10
  end
11
11
 
12
- def value(context={})
12
+ def value(context = {})
13
13
  @node.value(context)
14
14
  end
15
15
 
16
- def dependencies(context={})
16
+ def dependencies(context = {})
17
17
  @node.dependencies(context)
18
18
  end
19
19
  end
@@ -5,11 +5,11 @@ module Dentaku
5
5
  @node = node
6
6
  end
7
7
 
8
- def value(context={})
8
+ def value(context = {})
9
9
  @node.value(context)
10
10
  end
11
11
 
12
- def dependencies(context={})
12
+ def dependencies(context = {})
13
13
  @node.dependencies(context)
14
14
  end
15
15
 
@@ -9,11 +9,11 @@ module Dentaku
9
9
  @node = node
10
10
  end
11
11
 
12
- def value(context={})
12
+ def value(context = {})
13
13
  @node.value(context)
14
14
  end
15
15
 
16
- def dependencies(context={})
16
+ def dependencies(context = {})
17
17
  @node.dependencies(context)
18
18
  end
19
19
  end
@@ -9,11 +9,11 @@ module Dentaku
9
9
  @node = node
10
10
  end
11
11
 
12
- def value(context={})
12
+ def value(context = {})
13
13
  @node.value(context)
14
14
  end
15
15
 
16
- def dependencies(context={})
16
+ def dependencies(context = {})
17
17
  @node.dependencies(context)
18
18
  end
19
19
  end
@@ -28,13 +28,21 @@ module Dentaku
28
28
  end
29
29
 
30
30
  class And < Combinator
31
- def value(context={})
31
+ def operator
32
+ :and
33
+ end
34
+
35
+ def value(context = {})
32
36
  left.value(context) && right.value(context)
33
37
  end
34
38
  end
35
39
 
36
40
  class Or < Combinator
37
- def value(context={})
41
+ def operator
42
+ :or
43
+ end
44
+
45
+ def value(context = {})
38
46
  left.value(context) || right.value(context)
39
47
  end
40
48
  end
@@ -10,42 +10,70 @@ module Dentaku
10
10
  def type
11
11
  :logical
12
12
  end
13
+
14
+ def operator
15
+ raise NotImplementedError
16
+ end
13
17
  end
14
18
 
15
19
  class LessThan < Comparator
16
- def value(context={})
20
+ def value(context = {})
17
21
  left.value(context) < right.value(context)
18
22
  end
23
+
24
+ def operator
25
+ return :<
26
+ end
19
27
  end
20
28
 
21
29
  class LessThanOrEqual < Comparator
22
- def value(context={})
30
+ def value(context = {})
23
31
  left.value(context) <= right.value(context)
24
32
  end
33
+
34
+ def operator
35
+ return :<=
36
+ end
25
37
  end
26
38
 
27
39
  class GreaterThan < Comparator
28
- def value(context={})
40
+ def value(context = {})
29
41
  left.value(context) > right.value(context)
30
42
  end
43
+
44
+ def operator
45
+ return :>
46
+ end
31
47
  end
32
48
 
33
49
  class GreaterThanOrEqual < Comparator
34
- def value(context={})
50
+ def value(context = {})
35
51
  left.value(context) >= right.value(context)
36
52
  end
53
+
54
+ def operator
55
+ return :>=
56
+ end
37
57
  end
38
58
 
39
59
  class NotEqual < Comparator
40
- def value(context={})
60
+ def value(context = {})
41
61
  left.value(context) != right.value(context)
42
62
  end
63
+
64
+ def operator
65
+ return :!=
66
+ end
43
67
  end
44
68
 
45
69
  class Equal < Comparator
46
- def value(context={})
70
+ def value(context = {})
47
71
  left.value(context) == right.value(context)
48
72
  end
73
+
74
+ def operator
75
+ return :==
76
+ end
49
77
  end
50
78
  end
51
79
  end
@@ -11,7 +11,7 @@ module Dentaku
11
11
  @args = args
12
12
  end
13
13
 
14
- def dependencies(context={})
14
+ def dependencies(context = {})
15
15
  @args.flat_map { |a| a.dependencies(context) }
16
16
  end
17
17
 
@@ -38,7 +38,7 @@ module Dentaku
38
38
  @implementation.arity < 0 ? nil : @implementation.arity
39
39
  end
40
40
 
41
- def value(context={})
41
+ def value(context = {})
42
42
  args = @args.map { |a| a.value(context) }
43
43
  self.class.implementation.call(*args)
44
44
  end
@@ -11,15 +11,19 @@ module Dentaku
11
11
  @right = right
12
12
  end
13
13
 
14
- def value(context={})
14
+ def value(context = {})
15
15
  predicate.value(context) ? left.value(context) : right.value(context)
16
16
  end
17
17
 
18
+ def node_type
19
+ :condition
20
+ end
21
+
18
22
  def type
19
23
  left.type
20
24
  end
21
25
 
22
- def dependencies(context={})
26
+ def dependencies(context = {})
23
27
  # TODO : short-circuit?
24
28
  (predicate.dependencies(context) + left.dependencies(context) + right.dependencies(context)).uniq
25
29
  end
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
3
  Dentaku::AST::Function.register(:max, :numeric, ->(*args) {
4
- args.map { |arg| Dentaku::AST::Function.numeric(arg) }.max
4
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.max
5
5
  })
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
3
  Dentaku::AST::Function.register(:min, :numeric, ->(*args) {
4
- args.map { |arg| Dentaku::AST::Function.numeric(arg) }.min
4
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.min
5
5
  })
@@ -3,6 +3,6 @@ require_relative '../function'
3
3
 
4
4
  Math.methods(false).each do |method|
5
5
  Dentaku::AST::Function.register(method, :numeric, lambda { |*args|
6
- Math.send(method, *args.map { |arg| Dentaku::AST::Function.numeric(arg) })
6
+ Math.send(method, *args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) })
7
7
  })
8
8
  end
@@ -9,7 +9,7 @@ module Dentaku
9
9
  @string, @length = *@args
10
10
  end
11
11
 
12
- def value(context={})
12
+ def value(context = {})
13
13
  string = @string.value(context).to_s
14
14
  length = @length.value(context)
15
15
  string[0, length]
@@ -22,7 +22,7 @@ module Dentaku
22
22
  @string, @length = *@args
23
23
  end
24
24
 
25
- def value(context={})
25
+ def value(context = {})
26
26
  string = @string.value(context).to_s
27
27
  length = @length.value(context)
28
28
  string[length * -1, length] || string
@@ -35,7 +35,7 @@ module Dentaku
35
35
  @string, @offset, @length = *@args
36
36
  end
37
37
 
38
- def value(context={})
38
+ def value(context = {})
39
39
  string = @string.value(context).to_s
40
40
  offset = @offset.value(context)
41
41
  length = @length.value(context)
@@ -49,7 +49,7 @@ module Dentaku
49
49
  @string = @args[0]
50
50
  end
51
51
 
52
- def value(context={})
52
+ def value(context = {})
53
53
  string = @string.value(context).to_s
54
54
  string.length
55
55
  end
@@ -61,7 +61,7 @@ module Dentaku
61
61
  @needle, @haystack = *@args
62
62
  end
63
63
 
64
- def value(context={})
64
+ def value(context = {})
65
65
  needle = @needle.value(context)
66
66
  needle = needle.to_s unless needle.is_a?(Regexp)
67
67
  haystack = @haystack.value(context).to_s
@@ -76,7 +76,7 @@ module Dentaku
76
76
  @original, @search, @replacement = *@args
77
77
  end
78
78
 
79
- def value(context={})
79
+ def value(context = {})
80
80
  original = @original.value(context).to_s
81
81
  search = @search.value(context)
82
82
  search = search.to_s unless search.is_a?(Regexp)
@@ -90,7 +90,7 @@ module Dentaku
90
90
  super
91
91
  end
92
92
 
93
- def value(context={})
93
+ def value(context = {})
94
94
  @args.map { |arg| arg.value(context).to_s }.join
95
95
  end
96
96
  end
@@ -101,7 +101,7 @@ module Dentaku
101
101
  @needle, @haystack = *args
102
102
  end
103
103
 
104
- def value(context={})
104
+ def value(context = {})
105
105
  @haystack.value(context).to_s.include? @needle.value(context).to_s
106
106
  end
107
107
  end
@@ -0,0 +1,12 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:sum, :numeric, ->(*args) {
4
+ if args.empty?
5
+ raise Dentaku::ArgumentError.for(
6
+ :too_few_arguments,
7
+ function_name: 'SUM()', at_least: 1, given: 0
8
+ ), 'SUM() requires at least one argument'
9
+ end
10
+
11
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(0, :+)
12
+ })
@@ -5,7 +5,7 @@ module Dentaku
5
5
  @node = node
6
6
  end
7
7
 
8
- def value(context={})
8
+ def value(context = {})
9
9
  @node.value(context)
10
10
  end
11
11
 
@@ -13,7 +13,7 @@ module Dentaku
13
13
  @node.type
14
14
  end
15
15
 
16
- def dependencies(context={})
16
+ def dependencies(context = {})
17
17
  @node.dependencies(context)
18
18
  end
19
19
  end
@@ -1,15 +1,18 @@
1
1
  require_relative '../exceptions'
2
+ require 'dentaku/string_casing'
2
3
 
3
4
  module Dentaku
4
5
  module AST
5
6
  class Identifier < Node
6
- attr_reader :identifier
7
+ include StringCasing
8
+ attr_reader :identifier, :case_sensitive
7
9
 
8
- def initialize(token)
9
- @identifier = token.value.downcase
10
+ def initialize(token, options = {})
11
+ @case_sensitive = options.fetch(:case_sensitive, false)
12
+ @identifier = standardize_case(token.value)
10
13
  end
11
14
 
12
- def value(context={})
15
+ def value(context = {})
13
16
  v = context.fetch(identifier) do
14
17
  raise UnboundVariableError.new([identifier]),
15
18
  "no value provided for variables: #{identifier}"
@@ -23,7 +26,7 @@ module Dentaku
23
26
  end
24
27
  end
25
28
 
26
- def dependencies(context={})
29
+ def dependencies(context = {})
27
30
  context.key?(identifier) ? dependencies_of(context[identifier]) : [identifier]
28
31
  end
29
32
 
@@ -14,7 +14,7 @@ module Dentaku
14
14
  :*
15
15
  end
16
16
 
17
- def value(context={})
17
+ def value(context = {})
18
18
  cast(@node.value(context)) * -1
19
19
  end
20
20
 
@@ -34,7 +34,7 @@ module Dentaku
34
34
  40
35
35
  end
36
36
 
37
- def dependencies(context={})
37
+ def dependencies(context = {})
38
38
  @node.dependencies(context)
39
39
  end
40
40
 
@@ -12,7 +12,7 @@ module Dentaku
12
12
  def self.peek(*)
13
13
  end
14
14
 
15
- def dependencies(context={})
15
+ def dependencies(context = {})
16
16
  []
17
17
  end
18
18
  end