dentaku_zevo 3.5.2

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 (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.pryrc +2 -0
  4. data/.rubocop.yml +114 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +281 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +21 -0
  9. data/README.md +342 -0
  10. data/Rakefile +31 -0
  11. data/dentaku.gemspec +32 -0
  12. data/lib/dentaku/ast/access.rb +47 -0
  13. data/lib/dentaku/ast/arithmetic.rb +241 -0
  14. data/lib/dentaku/ast/array.rb +41 -0
  15. data/lib/dentaku/ast/bitwise.rb +42 -0
  16. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  17. data/lib/dentaku/ast/case/case_else.rb +35 -0
  18. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  19. data/lib/dentaku/ast/case/case_then.rb +35 -0
  20. data/lib/dentaku/ast/case/case_when.rb +39 -0
  21. data/lib/dentaku/ast/case.rb +81 -0
  22. data/lib/dentaku/ast/combinators.rb +50 -0
  23. data/lib/dentaku/ast/comparators.rb +89 -0
  24. data/lib/dentaku/ast/datetime.rb +8 -0
  25. data/lib/dentaku/ast/function.rb +56 -0
  26. data/lib/dentaku/ast/function_registry.rb +98 -0
  27. data/lib/dentaku/ast/functions/all.rb +23 -0
  28. data/lib/dentaku/ast/functions/and.rb +25 -0
  29. data/lib/dentaku/ast/functions/any.rb +23 -0
  30. data/lib/dentaku/ast/functions/avg.rb +13 -0
  31. data/lib/dentaku/ast/functions/count.rb +26 -0
  32. data/lib/dentaku/ast/functions/duration.rb +51 -0
  33. data/lib/dentaku/ast/functions/enum.rb +37 -0
  34. data/lib/dentaku/ast/functions/filter.rb +23 -0
  35. data/lib/dentaku/ast/functions/if.rb +51 -0
  36. data/lib/dentaku/ast/functions/map.rb +23 -0
  37. data/lib/dentaku/ast/functions/max.rb +5 -0
  38. data/lib/dentaku/ast/functions/min.rb +5 -0
  39. data/lib/dentaku/ast/functions/mul.rb +12 -0
  40. data/lib/dentaku/ast/functions/not.rb +5 -0
  41. data/lib/dentaku/ast/functions/or.rb +25 -0
  42. data/lib/dentaku/ast/functions/pluck.rb +30 -0
  43. data/lib/dentaku/ast/functions/round.rb +5 -0
  44. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  45. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  46. data/lib/dentaku/ast/functions/ruby_math.rb +55 -0
  47. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  48. data/lib/dentaku/ast/functions/sum.rb +12 -0
  49. data/lib/dentaku/ast/functions/switch.rb +8 -0
  50. data/lib/dentaku/ast/functions/xor.rb +44 -0
  51. data/lib/dentaku/ast/grouping.rb +23 -0
  52. data/lib/dentaku/ast/identifier.rb +52 -0
  53. data/lib/dentaku/ast/literal.rb +30 -0
  54. data/lib/dentaku/ast/logical.rb +8 -0
  55. data/lib/dentaku/ast/negation.rb +54 -0
  56. data/lib/dentaku/ast/nil.rb +13 -0
  57. data/lib/dentaku/ast/node.rb +28 -0
  58. data/lib/dentaku/ast/numeric.rb +8 -0
  59. data/lib/dentaku/ast/operation.rb +39 -0
  60. data/lib/dentaku/ast/string.rb +15 -0
  61. data/lib/dentaku/ast.rb +39 -0
  62. data/lib/dentaku/bulk_expression_solver.rb +128 -0
  63. data/lib/dentaku/calculator.rb +169 -0
  64. data/lib/dentaku/date_arithmetic.rb +45 -0
  65. data/lib/dentaku/dependency_resolver.rb +24 -0
  66. data/lib/dentaku/exceptions.rb +102 -0
  67. data/lib/dentaku/flat_hash.rb +38 -0
  68. data/lib/dentaku/parser.rb +349 -0
  69. data/lib/dentaku/print_visitor.rb +101 -0
  70. data/lib/dentaku/string_casing.rb +7 -0
  71. data/lib/dentaku/token.rb +36 -0
  72. data/lib/dentaku/token_matcher.rb +138 -0
  73. data/lib/dentaku/token_matchers.rb +29 -0
  74. data/lib/dentaku/token_scanner.rb +183 -0
  75. data/lib/dentaku/tokenizer.rb +110 -0
  76. data/lib/dentaku/version.rb +3 -0
  77. data/lib/dentaku/visitor/infix.rb +82 -0
  78. data/lib/dentaku.rb +69 -0
  79. data/spec/ast/addition_spec.rb +62 -0
  80. data/spec/ast/all_spec.rb +25 -0
  81. data/spec/ast/and_function_spec.rb +35 -0
  82. data/spec/ast/and_spec.rb +32 -0
  83. data/spec/ast/any_spec.rb +23 -0
  84. data/spec/ast/arithmetic_spec.rb +91 -0
  85. data/spec/ast/avg_spec.rb +37 -0
  86. data/spec/ast/case_spec.rb +84 -0
  87. data/spec/ast/comparator_spec.rb +87 -0
  88. data/spec/ast/count_spec.rb +40 -0
  89. data/spec/ast/division_spec.rb +35 -0
  90. data/spec/ast/filter_spec.rb +25 -0
  91. data/spec/ast/function_spec.rb +69 -0
  92. data/spec/ast/map_spec.rb +27 -0
  93. data/spec/ast/max_spec.rb +33 -0
  94. data/spec/ast/min_spec.rb +33 -0
  95. data/spec/ast/mul_spec.rb +43 -0
  96. data/spec/ast/negation_spec.rb +48 -0
  97. data/spec/ast/node_spec.rb +43 -0
  98. data/spec/ast/numeric_spec.rb +16 -0
  99. data/spec/ast/or_spec.rb +35 -0
  100. data/spec/ast/pluck_spec.rb +32 -0
  101. data/spec/ast/round_spec.rb +35 -0
  102. data/spec/ast/rounddown_spec.rb +35 -0
  103. data/spec/ast/roundup_spec.rb +35 -0
  104. data/spec/ast/string_functions_spec.rb +217 -0
  105. data/spec/ast/sum_spec.rb +43 -0
  106. data/spec/ast/switch_spec.rb +30 -0
  107. data/spec/ast/xor_spec.rb +35 -0
  108. data/spec/benchmark.rb +70 -0
  109. data/spec/bulk_expression_solver_spec.rb +201 -0
  110. data/spec/calculator_spec.rb +898 -0
  111. data/spec/dentaku_spec.rb +52 -0
  112. data/spec/exceptions_spec.rb +9 -0
  113. data/spec/external_function_spec.rb +106 -0
  114. data/spec/parser_spec.rb +166 -0
  115. data/spec/print_visitor_spec.rb +66 -0
  116. data/spec/spec_helper.rb +71 -0
  117. data/spec/token_matcher_spec.rb +134 -0
  118. data/spec/token_scanner_spec.rb +49 -0
  119. data/spec/token_spec.rb +16 -0
  120. data/spec/tokenizer_spec.rb +359 -0
  121. data/spec/visitor/infix_spec.rb +31 -0
  122. data/spec/visitor_spec.rb +138 -0
  123. metadata +335 -0
@@ -0,0 +1,23 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Any < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.any? do |item_value|
12
+ expression.value(
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
15
+ )
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Dentaku::AST::Function.register_class(:any, Dentaku::AST::Any)
@@ -0,0 +1,13 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:avg, :numeric, ->(*args) {
4
+ flatten_args = args.flatten
5
+ if flatten_args.empty?
6
+ raise Dentaku::ArgumentError.for(
7
+ :too_few_arguments,
8
+ function_name: 'AVG()', at_least: 1, given: 0
9
+ ), 'AVG() requires at least one argument'
10
+ end
11
+
12
+ flatten_args.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(0, :+) / flatten_args.length
13
+ })
@@ -0,0 +1,26 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Count < Function
6
+ def self.min_param_count
7
+ 0
8
+ end
9
+
10
+ def self.max_param_count
11
+ Float::INFINITY
12
+ end
13
+
14
+ def value(context = {})
15
+ if @args.length == 1
16
+ first_arg = @args[0].value(context)
17
+ return first_arg.length if first_arg.respond_to?(:length)
18
+ end
19
+
20
+ @args.length
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Dentaku::AST::Function.register_class(:count, Dentaku::AST::Count)
@@ -0,0 +1,51 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Duration < Function
6
+ def self.min_param_count
7
+ 2
8
+ end
9
+
10
+ def self.max_param_count
11
+ 2
12
+ end
13
+
14
+ class Value
15
+ attr_reader :value, :unit
16
+
17
+ def initialize(value, unit)
18
+ @value = value
19
+ @unit = validate_unit(unit)
20
+ end
21
+
22
+ def validate_unit(unit)
23
+ case unit.downcase
24
+ when /years?/ then :year
25
+ when /months?/ then :month
26
+ when /days?/ then :day
27
+ else
28
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: unit, for: Duration),
29
+ "'#{unit || unit.class}' is not a valid duration unit"
30
+ end
31
+ end
32
+ end
33
+
34
+ def type
35
+ :duration
36
+ end
37
+
38
+ def value(context = {})
39
+ value_node, unit_node = *@args
40
+ Value.new(value_node.value(context), unit_node.identifier)
41
+ end
42
+
43
+ def dependencies(context = {})
44
+ value_node = @args.first
45
+ value_node.dependencies(context)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ Dentaku::AST::Function.register_class(:duration, Dentaku::AST::Duration)
@@ -0,0 +1,37 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Enum < 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 dependencies(context = {})
16
+ validate_identifier(@args[1])
17
+
18
+ collection = @args[0]
19
+ item_identifier = @args[1].identifier
20
+ expression = @args[2]
21
+
22
+ collection_deps = collection.dependencies(context)
23
+ expression_deps = (expression&.dependencies(context) || []).reject do |i|
24
+ i == item_identifier || i.start_with?("#{item_identifier}.")
25
+ end
26
+
27
+ collection_deps + expression_deps
28
+ end
29
+
30
+ def validate_identifier(arg, message = "#{name}() requires second argument to be an identifier")
31
+ unless arg.is_a?(Identifier)
32
+ raise ArgumentError.for(:incompatible_type, value: arg, for: Identifier), message
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Filter < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.select do |item_value|
12
+ expression.value(
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
15
+ )
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Dentaku::AST::Function.register_class(:filter, Dentaku::AST::Filter)
@@ -0,0 +1,51 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class If < Function
6
+ attr_reader :predicate, :left, :right
7
+
8
+ def self.min_param_count
9
+ 3
10
+ end
11
+
12
+ def self.max_param_count
13
+ 3
14
+ end
15
+
16
+ def initialize(predicate, left, right)
17
+ @predicate = predicate
18
+ @left = left
19
+ @right = right
20
+ end
21
+
22
+ def args
23
+ [predicate, left, right]
24
+ end
25
+
26
+ def value(context = {})
27
+ predicate.value(context) ? left.value(context) : right.value(context)
28
+ end
29
+
30
+ def node_type
31
+ :condition
32
+ end
33
+
34
+ def type
35
+ left.type
36
+ end
37
+
38
+ def dependencies(context = {})
39
+ deps = predicate.dependencies(context)
40
+
41
+ if deps.empty?
42
+ predicate.value(context) ? left.dependencies(context) : right.dependencies(context)
43
+ else
44
+ (deps + left.dependencies(context) + right.dependencies(context)).uniq
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ Dentaku::AST::Function.register_class(:if, Dentaku::AST::If)
@@ -0,0 +1,23 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Map < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.map do |item_value|
12
+ expression.value(
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
15
+ )
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Dentaku::AST::Function.register_class(:map, Dentaku::AST::Map)
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:max, :numeric, ->(*args) {
4
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.max
5
+ })
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:min, :numeric, ->(*args) {
4
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.min
5
+ })
@@ -0,0 +1,12 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:mul, :numeric, ->(*args) {
4
+ if args.empty?
5
+ raise Dentaku::ArgumentError.for(
6
+ :too_few_arguments,
7
+ function_name: 'MUL()', at_least: 1, given: 0
8
+ ), 'MUL() requires at least one argument'
9
+ end
10
+
11
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(1, :*)
12
+ })
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:not, :logical, ->(logical) {
4
+ ! logical
5
+ })
@@ -0,0 +1,25 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ Dentaku::AST::Function.register(:or, :logical, lambda { |*args|
5
+ if args.empty?
6
+ raise Dentaku::ArgumentError.for(
7
+ :too_few_arguments,
8
+ function_name: 'OR()', at_least: 1, given: 0
9
+ ), 'OR() requires at least one argument'
10
+ end
11
+
12
+ args.any? do |arg|
13
+ case arg
14
+ when TrueClass
15
+ true
16
+ when FalseClass, nil
17
+ false
18
+ else
19
+ raise Dentaku::ArgumentError.for(
20
+ :incompatible_type,
21
+ function_name: 'OR()', expect: :logical, actual: arg.class
22
+ ), 'OR() requires arguments to be logical expressions'
23
+ end
24
+ end
25
+ })
@@ -0,0 +1,30 @@
1
+ require_relative './enum'
2
+ require_relative '../../exceptions'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Pluck < Enum
7
+ def self.min_param_count
8
+ 2
9
+ end
10
+
11
+ def self.max_param_count
12
+ 2
13
+ end
14
+
15
+ def 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
+
22
+ pluck_path = @args[1].identifier
23
+
24
+ collection.map { |h| h.transform_keys(&:to_s)[pluck_path] }
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ Dentaku::AST::Function.register_class(:pluck, Dentaku::AST::Pluck)
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:round, :numeric, lambda { |numeric, places = 0|
4
+ Dentaku::AST::Function.numeric(numeric).round(Dentaku::AST::Function.numeric(places || 0).to_i)
5
+ })
@@ -0,0 +1,8 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:rounddown, :numeric, lambda { |numeric, precision = 0|
4
+ precision = Dentaku::AST::Function.numeric(precision || 0).to_i
5
+ tens = 10.0**precision
6
+ result = (Dentaku::AST::Function.numeric(numeric) * tens).floor / tens
7
+ precision <= 0 ? result.to_i : result
8
+ })
@@ -0,0 +1,8 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:roundup, :numeric, lambda { |numeric, precision = 0|
4
+ precision = Dentaku::AST::Function.numeric(precision || 0).to_i
5
+ tens = 10.0**precision
6
+ result = (Dentaku::AST::Function.numeric(numeric) * tens).ceil / tens
7
+ precision <= 0 ? result.to_i : result
8
+ })
@@ -0,0 +1,55 @@
1
+ # import all functions from Ruby's Math module
2
+ require_relative '../function'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class RubyMath < Function
7
+ def self.[](method)
8
+ klass_name = method.to_s.capitalize
9
+ klass = const_set(klass_name , Class.new(self))
10
+ klass.implement(method)
11
+ const_get(klass_name)
12
+ end
13
+
14
+ def self.implement(method)
15
+ @name = method
16
+ @implementation = Math.method(method)
17
+ end
18
+
19
+ def self.name
20
+ @name
21
+ end
22
+
23
+ def self.arity
24
+ @implementation.arity < 0 ? nil : @implementation.arity
25
+ end
26
+
27
+ def self.min_param_count
28
+ @implementation.parameters.select { |type, _name| type == :req }.count
29
+ end
30
+
31
+ def self.max_param_count
32
+ @implementation.parameters.select { |type, _name| type == :rest }.any? ? Float::INFINITY : @implementation.parameters.count
33
+ end
34
+
35
+ def self.call(*args)
36
+ @implementation.call(*args)
37
+ end
38
+
39
+ def value(context = {})
40
+ args = @args.flatten.map { |a| Dentaku::AST::Function.numeric(a.value(context)) }
41
+ self.class.call(*args)
42
+ end
43
+
44
+ ARRAY_RETURN_TYPES = [:frexp, :lgamma].freeze
45
+
46
+ def type
47
+ ARRAY_RETURN_TYPES.include?(@name) ? :array : :numeric
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ Math.methods(false).each do |method|
54
+ Dentaku::AST::Function.register_class(method, Dentaku::AST::RubyMath[method])
55
+ end
@@ -0,0 +1,212 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ module StringFunctions
6
+ class Base < Function
7
+ def type
8
+ :string
9
+ end
10
+
11
+ def negative_argument_failure(fun, arg = 'length')
12
+ raise Dentaku::ArgumentError.for(
13
+ :invalid_value,
14
+ function_name: "#{fun}()"
15
+ ), "#{fun}() requires #{arg} to be positive"
16
+ end
17
+ end
18
+
19
+ class Left < Base
20
+ def self.min_param_count
21
+ 2
22
+ end
23
+
24
+ def self.max_param_count
25
+ 2
26
+ end
27
+
28
+ def initialize(*args)
29
+ super
30
+ @string, @length = *@args
31
+ end
32
+
33
+ def value(context = {})
34
+ string = @string.value(context).to_s
35
+ length = Dentaku::AST::Function.numeric(@length.value(context)).to_i
36
+ negative_argument_failure('LEFT') if length < 0
37
+ string[0, length]
38
+ end
39
+ end
40
+
41
+ class Right < Base
42
+ def self.min_param_count
43
+ 2
44
+ end
45
+
46
+ def self.max_param_count
47
+ 2
48
+ end
49
+
50
+ def initialize(*args)
51
+ super
52
+ @string, @length = *@args
53
+ end
54
+
55
+ def value(context = {})
56
+ string = @string.value(context).to_s
57
+ length = Dentaku::AST::Function.numeric(@length.value(context)).to_i
58
+ negative_argument_failure('RIGHT') if length < 0
59
+ string[length * -1, length] || string
60
+ end
61
+ end
62
+
63
+ class Mid < Base
64
+ def self.min_param_count
65
+ 3
66
+ end
67
+
68
+ def self.max_param_count
69
+ 3
70
+ end
71
+
72
+ def initialize(*args)
73
+ super
74
+ @string, @offset, @length = *@args
75
+ end
76
+
77
+ def value(context = {})
78
+ string = @string.value(context).to_s
79
+ offset = Dentaku::AST::Function.numeric(@offset.value(context)).to_i
80
+ negative_argument_failure('MID', 'offset') if offset < 0
81
+ length = Dentaku::AST::Function.numeric(@length.value(context)).to_i
82
+ negative_argument_failure('MID') if length < 0
83
+ string[offset - 1, length].to_s
84
+ end
85
+ end
86
+
87
+ class Len < Base
88
+ def self.min_param_count
89
+ 1
90
+ end
91
+
92
+ def self.max_param_count
93
+ 1
94
+ end
95
+
96
+ def initialize(*args)
97
+ super
98
+ @string = @args[0]
99
+ end
100
+
101
+ def value(context = {})
102
+ string = @string.value(context).to_s
103
+ string.length
104
+ end
105
+
106
+ def type
107
+ :numeric
108
+ end
109
+ end
110
+
111
+ class Find < Base
112
+ def self.min_param_count
113
+ 2
114
+ end
115
+
116
+ def self.max_param_count
117
+ 2
118
+ end
119
+
120
+ def initialize(*args)
121
+ super
122
+ @needle, @haystack = *@args
123
+ end
124
+
125
+ def value(context = {})
126
+ needle = @needle.value(context)
127
+ needle = needle.to_s unless needle.is_a?(Regexp)
128
+ haystack = @haystack.value(context).to_s
129
+ pos = haystack.index(needle)
130
+ pos && pos + 1
131
+ end
132
+
133
+ def type
134
+ :numeric
135
+ end
136
+ end
137
+
138
+ class Substitute < Base
139
+ def self.min_param_count
140
+ 3
141
+ end
142
+
143
+ def self.max_param_count
144
+ 3
145
+ end
146
+
147
+ def initialize(*args)
148
+ super
149
+ @original, @search, @replacement = *@args
150
+ end
151
+
152
+ def value(context = {})
153
+ original = @original.value(context).to_s
154
+ search = @search.value(context)
155
+ search = search.to_s unless search.is_a?(Regexp)
156
+ replacement = @replacement.value(context).to_s
157
+ original.sub(search, replacement)
158
+ end
159
+ end
160
+
161
+ class Concat < Base
162
+ def self.min_param_count
163
+ 1
164
+ end
165
+
166
+ def self.max_param_count
167
+ Float::INFINITY
168
+ end
169
+
170
+ def initialize(*args)
171
+ super
172
+ end
173
+
174
+ def value(context = {})
175
+ @args.map { |arg| arg.value(context).to_s }.join
176
+ end
177
+ end
178
+
179
+ class Contains < Base
180
+ def self.min_param_count
181
+ 2
182
+ end
183
+
184
+ def self.max_param_count
185
+ 2
186
+ end
187
+
188
+ def initialize(*args)
189
+ super
190
+ @needle, @haystack = *args
191
+ end
192
+
193
+ def value(context = {})
194
+ @haystack.value(context).to_s.include? @needle.value(context).to_s
195
+ end
196
+
197
+ def type
198
+ :logical
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ Dentaku::AST::Function.register_class(:left, Dentaku::AST::StringFunctions::Left)
206
+ Dentaku::AST::Function.register_class(:right, Dentaku::AST::StringFunctions::Right)
207
+ Dentaku::AST::Function.register_class(:mid, Dentaku::AST::StringFunctions::Mid)
208
+ Dentaku::AST::Function.register_class(:len, Dentaku::AST::StringFunctions::Len)
209
+ Dentaku::AST::Function.register_class(:find, Dentaku::AST::StringFunctions::Find)
210
+ Dentaku::AST::Function.register_class(:substitute, Dentaku::AST::StringFunctions::Substitute)
211
+ Dentaku::AST::Function.register_class(:concat, Dentaku::AST::StringFunctions::Concat)
212
+ Dentaku::AST::Function.register_class(:contains, Dentaku::AST::StringFunctions::Contains)
@@ -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
+ })
@@ -0,0 +1,8 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:switch, :logical, lambda { |*args|
4
+ value = args.shift
5
+ default = args.pop if args.size.odd?
6
+ match = args.find_index.each_with_index { |arg, index| index.even? && arg == value }
7
+ match ? args[match + 1] : default
8
+ })