hayadentaku 3.5.7
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.
- checksums.yaml +7 -0
- data/.github/workflows/rspec.yml +26 -0
- data/.github/workflows/rubocop.yml +14 -0
- data/.gitignore +14 -0
- data/.pryrc +2 -0
- data/.rubocop.yml +114 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +352 -0
- data/Rakefile +31 -0
- data/hayadentaku.gemspec +35 -0
- data/lib/dentaku/ast/access.rb +44 -0
- data/lib/dentaku/ast/arithmetic.rb +292 -0
- data/lib/dentaku/ast/array.rb +38 -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 +93 -0
- data/lib/dentaku/ast/combinators.rb +50 -0
- data/lib/dentaku/ast/comparators.rb +88 -0
- data/lib/dentaku/ast/datetime.rb +8 -0
- data/lib/dentaku/ast/function.rb +56 -0
- data/lib/dentaku/ast/function_registry.rb +107 -0
- data/lib/dentaku/ast/functions/abs.rb +5 -0
- data/lib/dentaku/ast/functions/all.rb +19 -0
- data/lib/dentaku/ast/functions/and.rb +25 -0
- data/lib/dentaku/ast/functions/any.rb +19 -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 +54 -0
- data/lib/dentaku/ast/functions/filter.rb +21 -0
- data/lib/dentaku/ast/functions/if.rb +47 -0
- data/lib/dentaku/ast/functions/intercept.rb +33 -0
- data/lib/dentaku/ast/functions/map.rb +19 -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 +34 -0
- data/lib/dentaku/ast/functions/reduce.rb +60 -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 +57 -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 +29 -0
- data/lib/dentaku/ast/numeric.rb +8 -0
- data/lib/dentaku/ast/operation.rb +44 -0
- data/lib/dentaku/ast/string.rb +15 -0
- data/lib/dentaku/ast.rb +42 -0
- data/lib/dentaku/bulk_expression_solver.rb +158 -0
- data/lib/dentaku/calculator.rb +192 -0
- data/lib/dentaku/date_arithmetic.rb +60 -0
- data/lib/dentaku/dependency_resolver.rb +29 -0
- data/lib/dentaku/exceptions.rb +116 -0
- data/lib/dentaku/flat_hash.rb +161 -0
- data/lib/dentaku/parser.rb +318 -0
- data/lib/dentaku/print_visitor.rb +112 -0
- data/lib/dentaku/string_casing.rb +7 -0
- data/lib/dentaku/token.rb +48 -0
- data/lib/dentaku/token_matcher.rb +138 -0
- data/lib/dentaku/token_matchers.rb +29 -0
- data/lib/dentaku/token_scanner.rb +240 -0
- data/lib/dentaku/tokenizer.rb +127 -0
- data/lib/dentaku/version.rb +3 -0
- data/lib/dentaku/visitor/infix.rb +86 -0
- data/lib/dentaku.rb +69 -0
- data/spec/ast/abs_spec.rb +26 -0
- data/spec/ast/addition_spec.rb +67 -0
- data/spec/ast/all_spec.rb +38 -0
- data/spec/ast/and_function_spec.rb +35 -0
- data/spec/ast/and_spec.rb +32 -0
- data/spec/ast/any_spec.rb +36 -0
- data/spec/ast/arithmetic_spec.rb +147 -0
- data/spec/ast/avg_spec.rb +42 -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 +64 -0
- data/spec/ast/filter_spec.rb +25 -0
- data/spec/ast/function_spec.rb +69 -0
- data/spec/ast/intercept_spec.rb +30 -0
- data/spec/ast/map_spec.rb +40 -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 +49 -0
- data/spec/ast/reduce_spec.rb +22 -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 +241 -0
- data/spec/calculator_spec.rb +1003 -0
- data/spec/dentaku_spec.rb +52 -0
- data/spec/dependency_resolver_spec.rb +18 -0
- data/spec/exceptions_spec.rb +9 -0
- data/spec/external_function_spec.rb +177 -0
- data/spec/parser_spec.rb +183 -0
- data/spec/print_visitor_spec.rb +77 -0
- data/spec/spec_helper.rb +69 -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 +375 -0
- data/spec/visitor/infix_spec.rb +52 -0
- data/spec/visitor_spec.rb +139 -0
- metadata +353 -0
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
3
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def value(context = {})
|
|
16
|
+
collection = Array(@args[0].value(context))
|
|
17
|
+
|
|
18
|
+
unless collection.all? { |elem| elem.is_a?(Hash) }
|
|
19
|
+
raise ArgumentError.for(:incompatible_type, value: collection),
|
|
20
|
+
'PLUCK() requires first argument to be an array of hashes'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
pluck_path = @args[1].identifier
|
|
24
|
+
default = @args[2]
|
|
25
|
+
|
|
26
|
+
collection.map { |h|
|
|
27
|
+
h.transform_keys(&:to_s).fetch(pluck_path, default&.value(context))
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Dentaku::AST::Function.register_class(:pluck, Dentaku::AST::Pluck)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require_relative '../function'
|
|
2
|
+
require_relative '../../exceptions'
|
|
3
|
+
|
|
4
|
+
module Dentaku
|
|
5
|
+
module AST
|
|
6
|
+
class Reduce < Function
|
|
7
|
+
def self.min_param_count
|
|
8
|
+
4
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.max_param_count
|
|
12
|
+
5
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(*args)
|
|
16
|
+
super
|
|
17
|
+
|
|
18
|
+
validate_identifier(@args[1], 'second')
|
|
19
|
+
validate_identifier(@args[2], 'third')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dependencies(context = {})
|
|
23
|
+
collection = @args[0]
|
|
24
|
+
memo_identifier = @args[1].identifier
|
|
25
|
+
item_identifier = @args[2].identifier
|
|
26
|
+
expression = @args[3]
|
|
27
|
+
|
|
28
|
+
collection_deps = collection.dependencies(context)
|
|
29
|
+
expression_deps = expression.dependencies(context).reject do |i|
|
|
30
|
+
i == memo_identifier || i.start_with?("#{memo_identifier}.") ||
|
|
31
|
+
i == item_identifier || i.start_with?("#{item_identifier}.")
|
|
32
|
+
end
|
|
33
|
+
inital_value_deps = @args[4] ? @args[4].dependencies(context) : []
|
|
34
|
+
|
|
35
|
+
collection_deps + expression_deps + inital_value_deps
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def value(context = {})
|
|
39
|
+
collection = Array(@args[0].value(context))
|
|
40
|
+
memo_identifier = @args[1].identifier
|
|
41
|
+
item_identifier = @args[2].identifier
|
|
42
|
+
expression = @args[3]
|
|
43
|
+
initial_value = @args[4] && @args[4].value(context)
|
|
44
|
+
|
|
45
|
+
collection.reduce(initial_value) do |memo, item|
|
|
46
|
+
scratch = context.dup
|
|
47
|
+
FlatHash.write_pair_with_intermediates!(memo_identifier, memo, scratch)
|
|
48
|
+
FlatHash.write_pair_with_intermediates!(item_identifier, item, scratch)
|
|
49
|
+
expression.value(scratch)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate_identifier(arg, position, message = "#{name}() requires #{position} argument to be an identifier")
|
|
54
|
+
raise ParseError.for(:node_invalid), message unless arg.is_a?(Identifier)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Dentaku::AST::Function.register_class(:reduce, Dentaku::AST::Reduce)
|
|
@@ -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,57 @@
|
|
|
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
|
+
rescue Math::DomainError => _e
|
|
38
|
+
raise Dentaku::MathDomainError.new(name, args)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def value(context = {})
|
|
42
|
+
args = @args.flatten.map { |a| Dentaku::AST::Function.numeric(a.value(context)) }
|
|
43
|
+
self.class.call(*args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
ARRAY_RETURN_TYPES = [:frexp, :lgamma].freeze
|
|
47
|
+
|
|
48
|
+
def type
|
|
49
|
+
ARRAY_RETURN_TYPES.include?(@name) ? :array : :numeric
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
Math.methods(false).each do |method|
|
|
56
|
+
Dentaku::AST::Function.register_class(method, Dentaku::AST::RubyMath[method])
|
|
57
|
+
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
|
+
})
|
|
@@ -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)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require_relative "./node"
|
|
2
|
+
|
|
3
|
+
module Dentaku
|
|
4
|
+
module AST
|
|
5
|
+
class Grouping < Node
|
|
6
|
+
def initialize(node)
|
|
7
|
+
@node = node
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def value(context = {})
|
|
11
|
+
@node.value(context)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
@node.type
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def dependencies(context = {})
|
|
19
|
+
@node.dependencies(context)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require_relative '../exceptions'
|
|
2
|
+
require 'dentaku/string_casing'
|
|
3
|
+
|
|
4
|
+
module Dentaku
|
|
5
|
+
module AST
|
|
6
|
+
class Identifier < Node
|
|
7
|
+
include StringCasing
|
|
8
|
+
attr_reader :identifier, :case_sensitive
|
|
9
|
+
|
|
10
|
+
def initialize(token, options = {})
|
|
11
|
+
@case_sensitive = options.fetch(:case_sensitive, false)
|
|
12
|
+
@identifier = standardize_case(token.value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def value(context = {})
|
|
16
|
+
v = context.fetch(identifier) do
|
|
17
|
+
raise UnboundVariableError.new([identifier]),
|
|
18
|
+
"no value provided for variables: #{identifier}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
case v
|
|
22
|
+
when Node
|
|
23
|
+
value = v.value(context)
|
|
24
|
+
context[identifier] = value if Dentaku.cache_identifier?
|
|
25
|
+
value
|
|
26
|
+
when Proc
|
|
27
|
+
v.call
|
|
28
|
+
else
|
|
29
|
+
v
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dependencies(context = {})
|
|
34
|
+
context.key?(identifier) ? dependencies_of(context[identifier], context) : [identifier]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def accept(visitor)
|
|
38
|
+
visitor.visit_identifier(self)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
identifier.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def dependencies_of(node, context)
|
|
48
|
+
node.respond_to?(:dependencies) ? node.dependencies(context) : []
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Dentaku
|
|
2
|
+
module AST
|
|
3
|
+
class Literal < Node
|
|
4
|
+
attr_reader :type
|
|
5
|
+
|
|
6
|
+
def initialize(token)
|
|
7
|
+
@token = token
|
|
8
|
+
@value = token.value
|
|
9
|
+
@type = token.category
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def value(*)
|
|
13
|
+
@value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def dependencies(*)
|
|
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
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Dentaku
|
|
2
|
+
module AST
|
|
3
|
+
class Negation < Arithmetic
|
|
4
|
+
attr_reader :node
|
|
5
|
+
|
|
6
|
+
def initialize(node)
|
|
7
|
+
@node = node
|
|
8
|
+
|
|
9
|
+
unless valid_node?(node)
|
|
10
|
+
raise NodeError.new(:numeric, node.type, :node),
|
|
11
|
+
"#{self.class} requires numeric operands"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def operator
|
|
16
|
+
:*
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def value(context = {})
|
|
20
|
+
cast(@node.value(context)) * -1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def type
|
|
24
|
+
:numeric
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.arity
|
|
28
|
+
1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.right_associative?
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.precedence
|
|
36
|
+
40
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def dependencies(context = {})
|
|
40
|
+
@node.dependencies(context)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def accept(visitor)
|
|
44
|
+
visitor.visit_negation(self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def valid_node?(node)
|
|
50
|
+
node && (node.dependencies.any? || node.type == :numeric)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Dentaku
|
|
2
|
+
module AST
|
|
3
|
+
class Node
|
|
4
|
+
def self.precedence
|
|
5
|
+
0
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.arity
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.resolve_class(*)
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def dependencies(context = {})
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def type
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def name
|
|
25
|
+
self.class.name.to_s.split("::").last.upcase
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|