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.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +26 -0
  3. data/.github/workflows/rubocop.yml +14 -0
  4. data/.gitignore +14 -0
  5. data/.pryrc +2 -0
  6. data/.rubocop.yml +114 -0
  7. data/.travis.yml +10 -0
  8. data/CHANGELOG.md +328 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE +21 -0
  11. data/README.md +352 -0
  12. data/Rakefile +31 -0
  13. data/hayadentaku.gemspec +35 -0
  14. data/lib/dentaku/ast/access.rb +44 -0
  15. data/lib/dentaku/ast/arithmetic.rb +292 -0
  16. data/lib/dentaku/ast/array.rb +38 -0
  17. data/lib/dentaku/ast/bitwise.rb +42 -0
  18. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  19. data/lib/dentaku/ast/case/case_else.rb +35 -0
  20. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  21. data/lib/dentaku/ast/case/case_then.rb +35 -0
  22. data/lib/dentaku/ast/case/case_when.rb +39 -0
  23. data/lib/dentaku/ast/case.rb +93 -0
  24. data/lib/dentaku/ast/combinators.rb +50 -0
  25. data/lib/dentaku/ast/comparators.rb +88 -0
  26. data/lib/dentaku/ast/datetime.rb +8 -0
  27. data/lib/dentaku/ast/function.rb +56 -0
  28. data/lib/dentaku/ast/function_registry.rb +107 -0
  29. data/lib/dentaku/ast/functions/abs.rb +5 -0
  30. data/lib/dentaku/ast/functions/all.rb +19 -0
  31. data/lib/dentaku/ast/functions/and.rb +25 -0
  32. data/lib/dentaku/ast/functions/any.rb +19 -0
  33. data/lib/dentaku/ast/functions/avg.rb +13 -0
  34. data/lib/dentaku/ast/functions/count.rb +26 -0
  35. data/lib/dentaku/ast/functions/duration.rb +51 -0
  36. data/lib/dentaku/ast/functions/enum.rb +54 -0
  37. data/lib/dentaku/ast/functions/filter.rb +21 -0
  38. data/lib/dentaku/ast/functions/if.rb +47 -0
  39. data/lib/dentaku/ast/functions/intercept.rb +33 -0
  40. data/lib/dentaku/ast/functions/map.rb +19 -0
  41. data/lib/dentaku/ast/functions/max.rb +5 -0
  42. data/lib/dentaku/ast/functions/min.rb +5 -0
  43. data/lib/dentaku/ast/functions/mul.rb +12 -0
  44. data/lib/dentaku/ast/functions/not.rb +5 -0
  45. data/lib/dentaku/ast/functions/or.rb +25 -0
  46. data/lib/dentaku/ast/functions/pluck.rb +34 -0
  47. data/lib/dentaku/ast/functions/reduce.rb +60 -0
  48. data/lib/dentaku/ast/functions/round.rb +5 -0
  49. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  50. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  51. data/lib/dentaku/ast/functions/ruby_math.rb +57 -0
  52. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  53. data/lib/dentaku/ast/functions/sum.rb +12 -0
  54. data/lib/dentaku/ast/functions/switch.rb +8 -0
  55. data/lib/dentaku/ast/functions/xor.rb +44 -0
  56. data/lib/dentaku/ast/grouping.rb +23 -0
  57. data/lib/dentaku/ast/identifier.rb +52 -0
  58. data/lib/dentaku/ast/literal.rb +30 -0
  59. data/lib/dentaku/ast/logical.rb +8 -0
  60. data/lib/dentaku/ast/negation.rb +54 -0
  61. data/lib/dentaku/ast/nil.rb +13 -0
  62. data/lib/dentaku/ast/node.rb +29 -0
  63. data/lib/dentaku/ast/numeric.rb +8 -0
  64. data/lib/dentaku/ast/operation.rb +44 -0
  65. data/lib/dentaku/ast/string.rb +15 -0
  66. data/lib/dentaku/ast.rb +42 -0
  67. data/lib/dentaku/bulk_expression_solver.rb +158 -0
  68. data/lib/dentaku/calculator.rb +192 -0
  69. data/lib/dentaku/date_arithmetic.rb +60 -0
  70. data/lib/dentaku/dependency_resolver.rb +29 -0
  71. data/lib/dentaku/exceptions.rb +116 -0
  72. data/lib/dentaku/flat_hash.rb +161 -0
  73. data/lib/dentaku/parser.rb +318 -0
  74. data/lib/dentaku/print_visitor.rb +112 -0
  75. data/lib/dentaku/string_casing.rb +7 -0
  76. data/lib/dentaku/token.rb +48 -0
  77. data/lib/dentaku/token_matcher.rb +138 -0
  78. data/lib/dentaku/token_matchers.rb +29 -0
  79. data/lib/dentaku/token_scanner.rb +240 -0
  80. data/lib/dentaku/tokenizer.rb +127 -0
  81. data/lib/dentaku/version.rb +3 -0
  82. data/lib/dentaku/visitor/infix.rb +86 -0
  83. data/lib/dentaku.rb +69 -0
  84. data/spec/ast/abs_spec.rb +26 -0
  85. data/spec/ast/addition_spec.rb +67 -0
  86. data/spec/ast/all_spec.rb +38 -0
  87. data/spec/ast/and_function_spec.rb +35 -0
  88. data/spec/ast/and_spec.rb +32 -0
  89. data/spec/ast/any_spec.rb +36 -0
  90. data/spec/ast/arithmetic_spec.rb +147 -0
  91. data/spec/ast/avg_spec.rb +42 -0
  92. data/spec/ast/case_spec.rb +84 -0
  93. data/spec/ast/comparator_spec.rb +87 -0
  94. data/spec/ast/count_spec.rb +40 -0
  95. data/spec/ast/division_spec.rb +64 -0
  96. data/spec/ast/filter_spec.rb +25 -0
  97. data/spec/ast/function_spec.rb +69 -0
  98. data/spec/ast/intercept_spec.rb +30 -0
  99. data/spec/ast/map_spec.rb +40 -0
  100. data/spec/ast/max_spec.rb +33 -0
  101. data/spec/ast/min_spec.rb +33 -0
  102. data/spec/ast/mul_spec.rb +43 -0
  103. data/spec/ast/negation_spec.rb +48 -0
  104. data/spec/ast/node_spec.rb +43 -0
  105. data/spec/ast/numeric_spec.rb +16 -0
  106. data/spec/ast/or_spec.rb +35 -0
  107. data/spec/ast/pluck_spec.rb +49 -0
  108. data/spec/ast/reduce_spec.rb +22 -0
  109. data/spec/ast/round_spec.rb +35 -0
  110. data/spec/ast/rounddown_spec.rb +35 -0
  111. data/spec/ast/roundup_spec.rb +35 -0
  112. data/spec/ast/string_functions_spec.rb +217 -0
  113. data/spec/ast/sum_spec.rb +43 -0
  114. data/spec/ast/switch_spec.rb +30 -0
  115. data/spec/ast/xor_spec.rb +35 -0
  116. data/spec/benchmark.rb +70 -0
  117. data/spec/bulk_expression_solver_spec.rb +241 -0
  118. data/spec/calculator_spec.rb +1003 -0
  119. data/spec/dentaku_spec.rb +52 -0
  120. data/spec/dependency_resolver_spec.rb +18 -0
  121. data/spec/exceptions_spec.rb +9 -0
  122. data/spec/external_function_spec.rb +177 -0
  123. data/spec/parser_spec.rb +183 -0
  124. data/spec/print_visitor_spec.rb +77 -0
  125. data/spec/spec_helper.rb +69 -0
  126. data/spec/token_matcher_spec.rb +134 -0
  127. data/spec/token_scanner_spec.rb +49 -0
  128. data/spec/token_spec.rb +16 -0
  129. data/spec/tokenizer_spec.rb +375 -0
  130. data/spec/visitor/infix_spec.rb +52 -0
  131. data/spec/visitor_spec.rb +139 -0
  132. metadata +353 -0
@@ -0,0 +1,161 @@
1
+ module Dentaku
2
+ class FlatHash
3
+ # Flattens a nested hash so that each leaf becomes a top-level entry whose
4
+ # key is the dot-joined path of segments leading to it. Non-Hash values
5
+ # (including Arrays) are treated as leaves. Empty nested Hashes are dropped,
6
+ # matching the historical behavior of this method.
7
+ #
8
+ # The flattened key preserves the type (Symbol vs String) of the
9
+ # outermost key in the path: if the top-level key is a Symbol the joined
10
+ # key is a Symbol, otherwise it stays a String.
11
+ def self.from_hash(h)
12
+ return { "" => h } unless h.is_a?(Hash)
13
+
14
+ acc = {}
15
+ h.each do |k, v|
16
+ if v.is_a?(Hash)
17
+ unless v.empty?
18
+ if k.is_a?(Symbol)
19
+ flatten_leaves_sym(v, k.to_s, acc)
20
+ else
21
+ flatten_leaves_str(v, k.to_s, acc)
22
+ end
23
+ end
24
+ else
25
+ acc[k] = v
26
+ end
27
+ end
28
+ acc
29
+ end
30
+
31
+ # Like {.from_hash}, but additionally retains every intermediate nested Hash
32
+ # under its dot-joined path key.
33
+ def self.from_hash_with_intermediates(h)
34
+ return { "" => h } unless h.is_a?(Hash)
35
+
36
+ acc = {}
37
+ h.each do |k, v|
38
+ acc[k] = v
39
+ if v.is_a?(Hash) && !v.empty?
40
+ if k.is_a?(Symbol)
41
+ flatten_with_intermediates_sym(v, k.to_s, acc)
42
+ else
43
+ flatten_with_intermediates_str(v, k.to_s, acc)
44
+ end
45
+ end
46
+ end
47
+ acc
48
+ end
49
+
50
+ def self.flatten_keys(hash)
51
+ result = {}
52
+ hash.each { |k, v| result[flatten_key(k)] = v }
53
+ result
54
+ end
55
+
56
+ def self.flatten_key(segments)
57
+ return segments.first if segments.length == 1
58
+ key = segments.join('.')
59
+ segments.first.is_a?(Symbol) ? key.to_sym : key
60
+ end
61
+
62
+ # Inverse of {.from_hash}: re-nests a hash whose keys may contain dots.
63
+ def self.expand(hash)
64
+ result = {}
65
+ hash.each do |k, v|
66
+ key_str = k.to_s
67
+ if key_str.include?('.')
68
+ parts = key_str.split('.', -1)
69
+ last = parts.length - 1
70
+ current = result
71
+ if k.is_a?(Symbol)
72
+ i = 0
73
+ while i < last
74
+ current = (current[parts[i].to_sym] ||= {})
75
+ i += 1
76
+ end
77
+ current[parts[last].to_sym] = v
78
+ else
79
+ i = 0
80
+ while i < last
81
+ current = (current[parts[i]] ||= {})
82
+ i += 1
83
+ end
84
+ current[parts[last]] = v
85
+ end
86
+ else
87
+ result[k] = v
88
+ end
89
+ end
90
+ result
91
+ end
92
+
93
+ # Writes the flattened "intermediates" form of `{identifier => value}`
94
+ # directly into `target`, without allocating an intermediate Hash. Used by
95
+ # the iterator AST nodes (map/filter/reduce/all/any/enum) where the result
96
+ # of `from_hash_with_intermediates` is otherwise immediately discarded after
97
+ # being merged into the per-iteration context.
98
+ #
99
+ # Returns nothing meaningful; mutates `target`.
100
+ def self.write_pair_with_intermediates!(identifier, value, target)
101
+ target[identifier] = value
102
+ return unless value.is_a?(Hash) && !value.empty?
103
+
104
+ if identifier.is_a?(Symbol)
105
+ flatten_with_intermediates_sym(value, identifier.to_s, target)
106
+ else
107
+ flatten_with_intermediates_str(value, identifier.to_s, target)
108
+ end
109
+ end
110
+
111
+ # --- internals ---------------------------------------------------------
112
+
113
+ # The four helpers below are duplicated by output-key type (Symbol vs
114
+ # String) on purpose: hoisting the Symbol/String branch out of the inner
115
+ # loop is measurably faster than checking it on every leaf.
116
+
117
+ def self.flatten_leaves_sym(h, prefix, acc)
118
+ h.each do |k, v|
119
+ if v.is_a?(Hash)
120
+ flatten_leaves_sym(v, "#{prefix}.#{k}", acc) unless v.empty?
121
+ else
122
+ acc[:"#{prefix}.#{k}"] = v
123
+ end
124
+ end
125
+ end
126
+ private_class_method :flatten_leaves_sym
127
+
128
+ def self.flatten_leaves_str(h, prefix, acc)
129
+ h.each do |k, v|
130
+ if v.is_a?(Hash)
131
+ flatten_leaves_str(v, "#{prefix}.#{k}", acc) unless v.empty?
132
+ else
133
+ acc["#{prefix}.#{k}"] = v
134
+ end
135
+ end
136
+ end
137
+ private_class_method :flatten_leaves_str
138
+
139
+ def self.flatten_with_intermediates_sym(h, prefix, acc)
140
+ h.each do |k, v|
141
+ new_prefix = "#{prefix}.#{k}"
142
+ acc[new_prefix.to_sym] = v
143
+ if v.is_a?(Hash) && !v.empty?
144
+ flatten_with_intermediates_sym(v, new_prefix, acc)
145
+ end
146
+ end
147
+ end
148
+ private_class_method :flatten_with_intermediates_sym
149
+
150
+ def self.flatten_with_intermediates_str(h, prefix, acc)
151
+ h.each do |k, v|
152
+ new_prefix = "#{prefix}.#{k}"
153
+ acc[new_prefix] = v
154
+ if v.is_a?(Hash) && !v.empty?
155
+ flatten_with_intermediates_str(v, new_prefix, acc)
156
+ end
157
+ end
158
+ end
159
+ private_class_method :flatten_with_intermediates_str
160
+ end
161
+ end
@@ -0,0 +1,318 @@
1
+ require_relative './ast'
2
+
3
+ module Dentaku
4
+ class Parser
5
+ AST_OPERATIONS = {
6
+ add: AST::Addition,
7
+ subtract: AST::Subtraction,
8
+ multiply: AST::Multiplication,
9
+ divide: AST::Division,
10
+ pow: AST::Exponentiation,
11
+ negate: AST::Negation,
12
+ mod: AST::Modulo,
13
+
14
+ bitor: AST::BitwiseOr,
15
+ bitand: AST::BitwiseAnd,
16
+ bitshiftleft: AST::BitwiseShiftLeft,
17
+ bitshiftright: AST::BitwiseShiftRight,
18
+
19
+ lt: AST::LessThan,
20
+ gt: AST::GreaterThan,
21
+ le: AST::LessThanOrEqual,
22
+ ge: AST::GreaterThanOrEqual,
23
+ ne: AST::NotEqual,
24
+ eq: AST::Equal,
25
+
26
+ and: AST::And,
27
+ or: AST::Or,
28
+ xor: AST::Xor,
29
+ }.freeze
30
+
31
+ attr_reader :input, :output, :operations, :arities, :case_sensitive
32
+
33
+ def initialize(tokens, options = {})
34
+ @input = tokens.dup
35
+ @output = []
36
+ @operations = options.fetch(:operations, [])
37
+ @arities = options.fetch(:arities, [])
38
+ @function_registry = options.fetch(:function_registry, nil)
39
+ @case_sensitive = options.fetch(:case_sensitive, false)
40
+ @skip_indices = []
41
+ end
42
+
43
+ def consume(count = 2)
44
+ operator = operations.pop
45
+ fail! :invalid_statement if operator.nil?
46
+
47
+ output_size = output.length
48
+ args_size = operator.arity || count
49
+ min_size = operator.arity || operator.min_param_count || count
50
+ max_size = operator.arity || operator.max_param_count || count
51
+
52
+ if output_size < min_size || args_size < min_size
53
+ expect = min_size == max_size ? min_size : min_size..max_size
54
+ fail! :too_few_operands, operator: operator, expect: expect, actual: output_size
55
+ end
56
+
57
+ if (output_size > max_size && operations.empty?) || args_size > max_size
58
+ expect = min_size == max_size ? min_size : min_size..max_size
59
+ fail! :too_many_operands, operator: operator, expect: expect, actual: output_size
60
+ end
61
+
62
+ args = []
63
+ if operator == AST::Array && output.empty?
64
+ # special case: empty array literal '{}'
65
+ output.push(operator.new)
66
+ else
67
+ fail! :invalid_statement if output_size < args_size
68
+ args = Array.new(args_size) { output.pop }.reverse
69
+ output.push operator.new(*args)
70
+ end
71
+
72
+ if operator.respond_to?(:callback) && !operator.callback.nil?
73
+ operator.callback.call(args)
74
+ end
75
+ rescue ::ArgumentError => e
76
+ raise Dentaku::ArgumentError, e.message
77
+ rescue NodeError => e
78
+ fail! :node_invalid, operator: operator, child: e.child, expect: e.expect, actual: e.actual
79
+ end
80
+
81
+ def parse
82
+ return AST::Nil.new if input.empty?
83
+
84
+ i = 0
85
+ while i < input.length
86
+ if @skip_indices.include?(i)
87
+ i += 1
88
+ next
89
+ end
90
+
91
+ token = input[i]
92
+ lookahead = input[i + 1]
93
+ process_token(token, lookahead, i)
94
+ i += 1
95
+ end
96
+
97
+ consume while operations.any?
98
+
99
+ fail! :invalid_statement unless output.count == 1
100
+
101
+ output.first
102
+ end
103
+
104
+ def operation(token)
105
+ AST_OPERATIONS.fetch(token.value)
106
+ end
107
+
108
+ def function(token)
109
+ function_registry.get(token.value)
110
+ end
111
+
112
+ def function_registry
113
+ @function_registry ||= Dentaku::AST::FunctionRegistry.new
114
+ end
115
+
116
+ private
117
+
118
+ def process_token(token, lookahead, index)
119
+ case token.category
120
+ when :datetime then output << AST::DateTime.new(token)
121
+ when :numeric then output << AST::Numeric.new(token)
122
+ when :logical then output << AST::Logical.new(token)
123
+ when :string then output << AST::String.new(token)
124
+ when :identifier then output << AST::Identifier.new(token, case_sensitive: case_sensitive)
125
+ when :operator, :comparator, :combinator
126
+ handle_operator(token, lookahead)
127
+ when :null
128
+ output << AST::Nil.new
129
+ when :function
130
+ handle_function(token)
131
+ when :case
132
+ handle_case(token)
133
+ when :access
134
+ handle_access(token)
135
+ when :array
136
+ handle_array(token)
137
+ when :grouping
138
+ handle_grouping(token, lookahead, index)
139
+ else
140
+ fail! :not_implemented_token_category, token_category: token.category
141
+ end
142
+ end
143
+
144
+ def handle_operator(token, lookahead)
145
+ op_class = operation(token).resolve_class(lookahead)
146
+ if op_class.right_associative?
147
+ consume while operations.last && operations.last < AST::Operation && op_class.precedence < operations.last.precedence
148
+ else
149
+ consume while operations.last && operations.last < AST::Operation && op_class.precedence <= operations.last.precedence
150
+ end
151
+ operations.push op_class
152
+ end
153
+
154
+ def handle_function(token)
155
+ func = function(token)
156
+ fail! :undefined_function, function_name: token.value if func.nil?
157
+ arities.push 0
158
+ operations.push func
159
+ end
160
+
161
+ def handle_case(token)
162
+ # We always operate on the innermost (most recent) CASE on the stack.
163
+ case_index = operations.rindex(AST::Case) || -1
164
+ token_index = case_index + 1
165
+
166
+ case token.value
167
+ when :open
168
+ # Start a new CASE context.
169
+ operations.push AST::Case
170
+ arities.push(0)
171
+
172
+ when :close
173
+ # Finalize any trailing THEN/ELSE expression still on the stack.
174
+ if operations[token_index] == AST::CaseThen
175
+ consume_until(AST::Case)
176
+ operations.push(AST::CaseConditional)
177
+ consume(2)
178
+ arities[-1] += 1
179
+ elsif operations[token_index] == AST::CaseElse
180
+ consume_until(AST::Case)
181
+ arities[-1] += 1
182
+ end
183
+ fail! :unprocessed_token, token_name: token.value unless operations.last == AST::Case
184
+ consume(arities.pop.succ)
185
+
186
+ when :when
187
+ if operations[token_index] == AST::CaseThen
188
+ # Close out previous WHEN/THEN pair.
189
+ consume_until([AST::CaseWhen, AST::Case])
190
+ operations.push(AST::CaseConditional)
191
+ consume(2)
192
+ arities[-1] += 1
193
+ elsif operations.last == AST::Case
194
+ # First WHEN: finalize switch variable expression.
195
+ operations.push(AST::CaseSwitchVariable)
196
+ consume
197
+ end
198
+ operations.push(AST::CaseWhen)
199
+
200
+ when :then
201
+ if operations[token_index] == AST::CaseWhen
202
+ consume_until([AST::CaseThen, AST::Case])
203
+ end
204
+ operations.push(AST::CaseThen)
205
+
206
+ when :else
207
+ if operations[token_index] == AST::CaseThen
208
+ consume_until(AST::Case)
209
+ operations.push(AST::CaseConditional)
210
+ consume(2)
211
+ arities[-1] += 1
212
+ end
213
+ operations.push(AST::CaseElse)
214
+
215
+ else
216
+ fail! :unknown_case_token, token_name: token.value
217
+ end
218
+ end
219
+
220
+ def consume_until(target)
221
+ matcher =
222
+ case target
223
+ when Array then ->(op) { target.include?(op) }
224
+ else ->(op) { op == target }
225
+ end
226
+
227
+ consume while operations.any? && !matcher.call(operations.last)
228
+ end
229
+
230
+ def handle_access(token)
231
+ case token.value
232
+ when :lbracket
233
+ operations.push AST::Access
234
+
235
+ when :rbracket
236
+ consume while operations.any? && operations.last != AST::Access
237
+ fail! :unbalanced_bracket, token: token unless operations.last == AST::Access
238
+ consume
239
+ end
240
+ end
241
+
242
+ def handle_array(token)
243
+ case token.value
244
+ when :array_start
245
+ operations.push AST::Array
246
+ arities.push 0
247
+
248
+ when :array_end
249
+ consume while operations.any? && operations.last != AST::Array
250
+ fail! :unbalanced_bracket, token: token unless operations.last == AST::Array
251
+ consume(arities.pop.succ)
252
+ end
253
+ end
254
+
255
+ def handle_grouping(token, lookahead, token_index)
256
+ case token.value
257
+ when :open
258
+ if lookahead && lookahead.value == :close
259
+ # empty grouping (e.g. function with zero arguments) — we trigger consume later
260
+ # skip to the end
261
+ @skip_indices << token_index + 1
262
+ arities.pop
263
+ consume(0)
264
+ else
265
+ operations.push AST::Grouping
266
+ end
267
+
268
+ when :close
269
+ consume while operations.any? && operations.last != AST::Grouping
270
+ lparen = operations.pop
271
+ fail! :unbalanced_parenthesis, token unless lparen == AST::Grouping
272
+ if operations.last && operations.last < AST::Function
273
+ consume(arities.pop.succ)
274
+ end
275
+
276
+ when :comma
277
+ fail! :invalid_statement if arities.empty?
278
+ arities[-1] += 1
279
+ consume while operations.any? && operations.last != AST::Grouping && operations.last != AST::Array
280
+
281
+ else
282
+ fail! :unknown_grouping_token, token_name: token.value
283
+ end
284
+ end
285
+
286
+ def fail!(reason, **meta)
287
+ message =
288
+ case reason
289
+ when :node_invalid
290
+ "#{meta.fetch(:operator)} requires #{meta.fetch(:expect).join(', ')} operands, but got #{meta.fetch(:actual)}"
291
+ when :too_few_operands
292
+ "#{meta.fetch(:operator)} has too few operands (given #{meta.fetch(:actual)}, expected #{meta.fetch(:expect)})"
293
+ when :too_many_operands
294
+ "#{meta.fetch(:operator)} has too many operands (given #{meta.fetch(:actual)}, expected #{meta.fetch(:expect)})"
295
+ when :undefined_function
296
+ "Undefined function #{meta.fetch(:function_name)}"
297
+ when :unprocessed_token
298
+ "Unprocessed token #{meta.fetch(:token_name)}"
299
+ when :unknown_case_token
300
+ "Unknown case token #{meta.fetch(:token_name)}"
301
+ when :unbalanced_bracket
302
+ "Unbalanced bracket"
303
+ when :unbalanced_parenthesis
304
+ "Unbalanced parenthesis"
305
+ when :unknown_grouping_token
306
+ "Unknown grouping token #{meta.fetch(:token_name)}"
307
+ when :not_implemented_token_category
308
+ "Not implemented for tokens of category #{meta.fetch(:token_category)}"
309
+ when :invalid_statement
310
+ "Invalid statement"
311
+ else
312
+ raise ::ArgumentError, "Unhandled #{reason}"
313
+ end
314
+
315
+ raise ParseError.for(reason, **meta), message
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,112 @@
1
+ module Dentaku
2
+ class PrintVisitor
3
+ def initialize(node)
4
+ @output = ''
5
+ node.accept(self)
6
+ end
7
+
8
+ def visit_operation(node)
9
+ if node.left
10
+ visit_operand(node.left, node.class.precedence, suffix: node.operator_spacing, dir: :left)
11
+ end
12
+
13
+ @output << node.display_operator
14
+
15
+ if node.right
16
+ visit_operand(node.right, node.class.precedence, prefix: node.operator_spacing, dir: :right)
17
+ end
18
+ end
19
+
20
+ def visit_operand(node, precedence, prefix: "", suffix: "", dir: :none)
21
+ @output << prefix
22
+ @output << "(" if should_output?(node, precedence, dir == :right)
23
+ node.accept(self)
24
+ @output << ")" if should_output?(node, precedence, dir == :right)
25
+ @output << suffix
26
+ end
27
+
28
+ def should_output?(node, precedence, output_on_equal)
29
+ return false unless node.is_a?(Dentaku::AST::Operation)
30
+
31
+ target_precedence = node.class.precedence
32
+ target_precedence < precedence || (output_on_equal && target_precedence == precedence)
33
+ end
34
+
35
+ def visit_function(node)
36
+ @output << node.name
37
+ @output << "("
38
+ arg_count = node.args.length
39
+ node.args.each_with_index do |a, index|
40
+ a.accept(self)
41
+ @output << ", " unless index >= arg_count - 1
42
+ end
43
+ @output << ")"
44
+ end
45
+
46
+ def visit_case(node)
47
+ @output << "CASE "
48
+ node.switch.accept(self)
49
+ node.conditions.each { |c| c.accept(self) }
50
+ node.else && node.else.accept(self)
51
+ @output << " END"
52
+ end
53
+
54
+ def visit_switch(node)
55
+ node.node.accept(self)
56
+ end
57
+
58
+ def visit_case_conditional(node)
59
+ node.when.accept(self)
60
+ node.then.accept(self)
61
+ end
62
+
63
+ def visit_when(node)
64
+ @output << " WHEN "
65
+ node.node.accept(self)
66
+ end
67
+
68
+ def visit_then(node)
69
+ @output << " THEN "
70
+ node.node.accept(self)
71
+ end
72
+
73
+ def visit_else(node)
74
+ @output << " ELSE "
75
+ node.node.accept(self)
76
+ end
77
+
78
+ def visit_negation(node)
79
+ @output << "-"
80
+ @output << "(" unless node.node.is_a? Dentaku::AST::Literal
81
+ node.node.accept(self)
82
+ @output << ")" unless node.node.is_a? Dentaku::AST::Literal
83
+ end
84
+
85
+ def visit_access(node)
86
+ node.structure.accept(self)
87
+ @output << "["
88
+ node.index.accept(self)
89
+ @output << "]"
90
+ end
91
+
92
+ def visit_literal(node)
93
+ @output << node.quoted
94
+ end
95
+
96
+ def visit_identifier(node)
97
+ @output << node.identifier
98
+ end
99
+
100
+ def visit_nil(node)
101
+ @output << "NULL"
102
+ end
103
+
104
+ def visit_array(node)
105
+ @output << node.value.to_s
106
+ end
107
+
108
+ def to_s
109
+ @output
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,7 @@
1
+ module Dentaku
2
+ module StringCasing
3
+ def standardize_case(value)
4
+ case_sensitive ? value : value.downcase
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,48 @@
1
+ module Dentaku
2
+ class Token
3
+ attr_reader :category, :raw_value, :value
4
+
5
+ def initialize(category, value, raw_value = nil)
6
+ @category = category
7
+ @value = value
8
+ @raw_value = raw_value
9
+ end
10
+
11
+ def to_s
12
+ raw_value || value
13
+ end
14
+
15
+ def length
16
+ raw_value.to_s.length
17
+ end
18
+
19
+ def empty?
20
+ length.zero?
21
+ end
22
+
23
+ def operator?
24
+ is?(:operator)
25
+ end
26
+
27
+ def grouping?
28
+ is?(:grouping)
29
+ end
30
+
31
+ def open?
32
+ grouping? && value == :open
33
+ end
34
+
35
+ def close?
36
+ grouping? && value == :close
37
+ end
38
+
39
+ def is?(c)
40
+ category == c
41
+ end
42
+
43
+ def ==(other)
44
+ (category.nil? || other.category.nil? || category == other.category) &&
45
+ (value.nil? || other.value.nil? || value == other.value)
46
+ end
47
+ end
48
+ end