dentaku_zevo 3.5.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ })