dentaku_zevo 3.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.pryrc +2 -0
- data/.rubocop.yml +114 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +281 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +342 -0
- data/Rakefile +31 -0
- data/dentaku.gemspec +32 -0
- data/lib/dentaku/ast/access.rb +47 -0
- data/lib/dentaku/ast/arithmetic.rb +241 -0
- data/lib/dentaku/ast/array.rb +41 -0
- data/lib/dentaku/ast/bitwise.rb +42 -0
- data/lib/dentaku/ast/case/case_conditional.rb +38 -0
- data/lib/dentaku/ast/case/case_else.rb +35 -0
- data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
- data/lib/dentaku/ast/case/case_then.rb +35 -0
- data/lib/dentaku/ast/case/case_when.rb +39 -0
- data/lib/dentaku/ast/case.rb +81 -0
- data/lib/dentaku/ast/combinators.rb +50 -0
- data/lib/dentaku/ast/comparators.rb +89 -0
- data/lib/dentaku/ast/datetime.rb +8 -0
- data/lib/dentaku/ast/function.rb +56 -0
- data/lib/dentaku/ast/function_registry.rb +98 -0
- data/lib/dentaku/ast/functions/all.rb +23 -0
- data/lib/dentaku/ast/functions/and.rb +25 -0
- data/lib/dentaku/ast/functions/any.rb +23 -0
- data/lib/dentaku/ast/functions/avg.rb +13 -0
- data/lib/dentaku/ast/functions/count.rb +26 -0
- data/lib/dentaku/ast/functions/duration.rb +51 -0
- data/lib/dentaku/ast/functions/enum.rb +37 -0
- data/lib/dentaku/ast/functions/filter.rb +23 -0
- data/lib/dentaku/ast/functions/if.rb +51 -0
- data/lib/dentaku/ast/functions/map.rb +23 -0
- data/lib/dentaku/ast/functions/max.rb +5 -0
- data/lib/dentaku/ast/functions/min.rb +5 -0
- data/lib/dentaku/ast/functions/mul.rb +12 -0
- data/lib/dentaku/ast/functions/not.rb +5 -0
- data/lib/dentaku/ast/functions/or.rb +25 -0
- data/lib/dentaku/ast/functions/pluck.rb +30 -0
- data/lib/dentaku/ast/functions/round.rb +5 -0
- data/lib/dentaku/ast/functions/rounddown.rb +8 -0
- data/lib/dentaku/ast/functions/roundup.rb +8 -0
- data/lib/dentaku/ast/functions/ruby_math.rb +55 -0
- data/lib/dentaku/ast/functions/string_functions.rb +212 -0
- data/lib/dentaku/ast/functions/sum.rb +12 -0
- data/lib/dentaku/ast/functions/switch.rb +8 -0
- data/lib/dentaku/ast/functions/xor.rb +44 -0
- data/lib/dentaku/ast/grouping.rb +23 -0
- data/lib/dentaku/ast/identifier.rb +52 -0
- data/lib/dentaku/ast/literal.rb +30 -0
- data/lib/dentaku/ast/logical.rb +8 -0
- data/lib/dentaku/ast/negation.rb +54 -0
- data/lib/dentaku/ast/nil.rb +13 -0
- data/lib/dentaku/ast/node.rb +28 -0
- data/lib/dentaku/ast/numeric.rb +8 -0
- data/lib/dentaku/ast/operation.rb +39 -0
- data/lib/dentaku/ast/string.rb +15 -0
- data/lib/dentaku/ast.rb +39 -0
- data/lib/dentaku/bulk_expression_solver.rb +128 -0
- data/lib/dentaku/calculator.rb +169 -0
- data/lib/dentaku/date_arithmetic.rb +45 -0
- data/lib/dentaku/dependency_resolver.rb +24 -0
- data/lib/dentaku/exceptions.rb +102 -0
- data/lib/dentaku/flat_hash.rb +38 -0
- data/lib/dentaku/parser.rb +349 -0
- data/lib/dentaku/print_visitor.rb +101 -0
- data/lib/dentaku/string_casing.rb +7 -0
- data/lib/dentaku/token.rb +36 -0
- data/lib/dentaku/token_matcher.rb +138 -0
- data/lib/dentaku/token_matchers.rb +29 -0
- data/lib/dentaku/token_scanner.rb +183 -0
- data/lib/dentaku/tokenizer.rb +110 -0
- data/lib/dentaku/version.rb +3 -0
- data/lib/dentaku/visitor/infix.rb +82 -0
- data/lib/dentaku.rb +69 -0
- data/spec/ast/addition_spec.rb +62 -0
- data/spec/ast/all_spec.rb +25 -0
- data/spec/ast/and_function_spec.rb +35 -0
- data/spec/ast/and_spec.rb +32 -0
- data/spec/ast/any_spec.rb +23 -0
- data/spec/ast/arithmetic_spec.rb +91 -0
- data/spec/ast/avg_spec.rb +37 -0
- data/spec/ast/case_spec.rb +84 -0
- data/spec/ast/comparator_spec.rb +87 -0
- data/spec/ast/count_spec.rb +40 -0
- data/spec/ast/division_spec.rb +35 -0
- data/spec/ast/filter_spec.rb +25 -0
- data/spec/ast/function_spec.rb +69 -0
- data/spec/ast/map_spec.rb +27 -0
- data/spec/ast/max_spec.rb +33 -0
- data/spec/ast/min_spec.rb +33 -0
- data/spec/ast/mul_spec.rb +43 -0
- data/spec/ast/negation_spec.rb +48 -0
- data/spec/ast/node_spec.rb +43 -0
- data/spec/ast/numeric_spec.rb +16 -0
- data/spec/ast/or_spec.rb +35 -0
- data/spec/ast/pluck_spec.rb +32 -0
- data/spec/ast/round_spec.rb +35 -0
- data/spec/ast/rounddown_spec.rb +35 -0
- data/spec/ast/roundup_spec.rb +35 -0
- data/spec/ast/string_functions_spec.rb +217 -0
- data/spec/ast/sum_spec.rb +43 -0
- data/spec/ast/switch_spec.rb +30 -0
- data/spec/ast/xor_spec.rb +35 -0
- data/spec/benchmark.rb +70 -0
- data/spec/bulk_expression_solver_spec.rb +201 -0
- data/spec/calculator_spec.rb +898 -0
- data/spec/dentaku_spec.rb +52 -0
- data/spec/exceptions_spec.rb +9 -0
- data/spec/external_function_spec.rb +106 -0
- data/spec/parser_spec.rb +166 -0
- data/spec/print_visitor_spec.rb +66 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/token_matcher_spec.rb +134 -0
- data/spec/token_scanner_spec.rb +49 -0
- data/spec/token_spec.rb +16 -0
- data/spec/tokenizer_spec.rb +359 -0
- data/spec/visitor/infix_spec.rb +31 -0
- data/spec/visitor_spec.rb +138 -0
- 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,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,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,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
|
+
})
|