matchers 0.1.0.pre.test

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.

Potentially problematic release.


This version of matchers might be problematic. Click here for more details.

Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/lib/matcher/assertions.rb +21 -0
  3. data/lib/matcher/base.rb +189 -0
  4. data/lib/matcher/builder.rb +74 -0
  5. data/lib/matcher/chain.rb +60 -0
  6. data/lib/matcher/debug.rb +48 -0
  7. data/lib/matcher/errors/and_error.rb +86 -0
  8. data/lib/matcher/errors/boolean_collector.rb +51 -0
  9. data/lib/matcher/errors/element_error.rb +24 -0
  10. data/lib/matcher/errors/empty_error.rb +23 -0
  11. data/lib/matcher/errors/error.rb +39 -0
  12. data/lib/matcher/errors/error_collector.rb +99 -0
  13. data/lib/matcher/errors/nested_error.rb +96 -0
  14. data/lib/matcher/errors/or_error.rb +86 -0
  15. data/lib/matcher/expression_cache.rb +57 -0
  16. data/lib/matcher/expression_labeler.rb +91 -0
  17. data/lib/matcher/expressions/array_expression.rb +45 -0
  18. data/lib/matcher/expressions/block.rb +153 -0
  19. data/lib/matcher/expressions/call.rb +338 -0
  20. data/lib/matcher/expressions/call_error.rb +45 -0
  21. data/lib/matcher/expressions/constant.rb +53 -0
  22. data/lib/matcher/expressions/expression.rb +147 -0
  23. data/lib/matcher/expressions/expression_building.rb +258 -0
  24. data/lib/matcher/expressions/expression_walker.rb +73 -0
  25. data/lib/matcher/expressions/hash_expression.rb +59 -0
  26. data/lib/matcher/expressions/proc_expression.rb +92 -0
  27. data/lib/matcher/expressions/range_expression.rb +58 -0
  28. data/lib/matcher/expressions/recorder.rb +86 -0
  29. data/lib/matcher/expressions/rescue_last_error_expression.rb +44 -0
  30. data/lib/matcher/expressions/set_expression.rb +45 -0
  31. data/lib/matcher/expressions/string_expression.rb +53 -0
  32. data/lib/matcher/expressions/symbol_proc.rb +53 -0
  33. data/lib/matcher/expressions/variable.rb +85 -0
  34. data/lib/matcher/hash_stack.rb +53 -0
  35. data/lib/matcher/list.rb +102 -0
  36. data/lib/matcher/markers/optional.rb +80 -0
  37. data/lib/matcher/markers/others.rb +28 -0
  38. data/lib/matcher/matcher_cache.rb +18 -0
  39. data/lib/matcher/matchers/all_matcher.rb +60 -0
  40. data/lib/matcher/matchers/always_matcher.rb +28 -0
  41. data/lib/matcher/matchers/any_matcher.rb +70 -0
  42. data/lib/matcher/matchers/array_matcher.rb +35 -0
  43. data/lib/matcher/matchers/block_matcher.rb +59 -0
  44. data/lib/matcher/matchers/boolean_matcher.rb +35 -0
  45. data/lib/matcher/matchers/dig_matcher.rb +146 -0
  46. data/lib/matcher/matchers/each_matcher.rb +52 -0
  47. data/lib/matcher/matchers/each_pair_matcher.rb +119 -0
  48. data/lib/matcher/matchers/equal_matcher.rb +197 -0
  49. data/lib/matcher/matchers/equal_set_matcher.rb +99 -0
  50. data/lib/matcher/matchers/expression_matcher.rb +73 -0
  51. data/lib/matcher/matchers/filter_matcher.rb +111 -0
  52. data/lib/matcher/matchers/hash_matcher.rb +223 -0
  53. data/lib/matcher/matchers/imply_matcher.rb +81 -0
  54. data/lib/matcher/matchers/imply_some_matcher.rb +112 -0
  55. data/lib/matcher/matchers/index_by_matcher.rb +175 -0
  56. data/lib/matcher/matchers/inline_matcher.rb +99 -0
  57. data/lib/matcher/matchers/keys_matcher.rb +121 -0
  58. data/lib/matcher/matchers/kind_of_matcher.rb +35 -0
  59. data/lib/matcher/matchers/lazy_all_matcher.rb +68 -0
  60. data/lib/matcher/matchers/lazy_any_matcher.rb +68 -0
  61. data/lib/matcher/matchers/let_matcher.rb +73 -0
  62. data/lib/matcher/matchers/map_matcher.rb +129 -0
  63. data/lib/matcher/matchers/matcher_building.rb +5 -0
  64. data/lib/matcher/matchers/negated_array_matcher.rb +38 -0
  65. data/lib/matcher/matchers/negated_each_matcher.rb +36 -0
  66. data/lib/matcher/matchers/negated_each_pair_matcher.rb +38 -0
  67. data/lib/matcher/matchers/negated_imply_some_matcher.rb +46 -0
  68. data/lib/matcher/matchers/negated_matcher.rb +23 -0
  69. data/lib/matcher/matchers/negated_project_matcher.rb +31 -0
  70. data/lib/matcher/matchers/never_matcher.rb +29 -0
  71. data/lib/matcher/matchers/one_matcher.rb +70 -0
  72. data/lib/matcher/matchers/optional_matcher.rb +38 -0
  73. data/lib/matcher/matchers/parse_float_matcher.rb +86 -0
  74. data/lib/matcher/matchers/parse_integer_matcher.rb +98 -0
  75. data/lib/matcher/matchers/parse_iso8601_matcher.rb +92 -0
  76. data/lib/matcher/matchers/parse_json_matcher.rb +95 -0
  77. data/lib/matcher/matchers/project_matcher.rb +68 -0
  78. data/lib/matcher/matchers/raises_matcher.rb +124 -0
  79. data/lib/matcher/matchers/range_matcher.rb +47 -0
  80. data/lib/matcher/matchers/reference_matcher.rb +111 -0
  81. data/lib/matcher/matchers/reference_matcher_collection.rb +57 -0
  82. data/lib/matcher/matchers/regexp_matcher.rb +84 -0
  83. data/lib/matcher/messages/expected_phrasing.rb +342 -0
  84. data/lib/matcher/messages/message.rb +102 -0
  85. data/lib/matcher/messages/message_builder.rb +35 -0
  86. data/lib/matcher/messages/message_rules.rb +223 -0
  87. data/lib/matcher/messages/namespaced_message_builder.rb +19 -0
  88. data/lib/matcher/messages/phrasing.rb +57 -0
  89. data/lib/matcher/messages/standard_message_builder.rb +105 -0
  90. data/lib/matcher/once_before.rb +18 -0
  91. data/lib/matcher/optional_chain.rb +24 -0
  92. data/lib/matcher/patterns/ast_mapping.rb +42 -0
  93. data/lib/matcher/patterns/capture_hole.rb +33 -0
  94. data/lib/matcher/patterns/constant_hole.rb +14 -0
  95. data/lib/matcher/patterns/hole.rb +30 -0
  96. data/lib/matcher/patterns/method_hole.rb +58 -0
  97. data/lib/matcher/patterns/pattern.rb +92 -0
  98. data/lib/matcher/patterns/pattern_building.rb +39 -0
  99. data/lib/matcher/patterns/pattern_capture.rb +11 -0
  100. data/lib/matcher/patterns/pattern_match.rb +29 -0
  101. data/lib/matcher/patterns/variable_hole.rb +14 -0
  102. data/lib/matcher/reporter.rb +98 -0
  103. data/lib/matcher/rules/message_factory.rb +25 -0
  104. data/lib/matcher/rules/message_rule.rb +18 -0
  105. data/lib/matcher/rules/message_rule_context.rb +24 -0
  106. data/lib/matcher/rules/rule_builder.rb +29 -0
  107. data/lib/matcher/rules/rule_set.rb +57 -0
  108. data/lib/matcher/rules/transform_builder.rb +24 -0
  109. data/lib/matcher/rules/transform_mapping.rb +5 -0
  110. data/lib/matcher/rules/transform_rule.rb +21 -0
  111. data/lib/matcher/state.rb +40 -0
  112. data/lib/matcher/testing/error_builder.rb +62 -0
  113. data/lib/matcher/testing/error_checker.rb +496 -0
  114. data/lib/matcher/testing/error_testing.rb +37 -0
  115. data/lib/matcher/testing/pattern_testing.rb +11 -0
  116. data/lib/matcher/testing/pattern_testing_scope.rb +34 -0
  117. data/lib/matcher/testing.rb +102 -0
  118. data/lib/matcher/undefined.rb +10 -0
  119. data/lib/matcher/utils/mapping_utils.rb +61 -0
  120. data/lib/matcher/utils.rb +72 -0
  121. data/lib/matcher/version.rb +5 -0
  122. data/lib/matcher.rb +337 -0
  123. metadata +167 -0
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Call < Expression
5
+ attr_reader :receiver, :method, :args, :kwargs, :block
6
+
7
+ UNARY_OPERATORS = %i[! ~ +@ -@].freeze
8
+ BINARY_OPERATORS = %i[+ - * ** / % < > <= >= <=> == === != =~ !~ & | ^ << >> && ||].freeze
9
+
10
+ OPERATOR_PRECEDENCE = begin
11
+ precedence = {}
12
+
13
+ # see https://ruby-doc.org/3.2.2/syntax/precedence_rdoc.html
14
+ [
15
+ %i[! ~ +@],
16
+ %i[**],
17
+ %i[-@],
18
+ %i[* / %],
19
+ %i[+ -],
20
+ %i[<< >>],
21
+ %i[&],
22
+ %i[| ^],
23
+ %i[> >= < <=],
24
+ %i[<=> == === != =~ !~],
25
+ %i[&&],
26
+ %i[||],
27
+ ].each_with_index do |operators, index|
28
+ operators.each { precedence[_1] = index }
29
+ end
30
+
31
+ precedence.freeze
32
+ end
33
+
34
+ def self.last_assign
35
+ Matcher.build_session&.dig(Call, :last_assign)
36
+ end
37
+
38
+ def self.reset_last_assign
39
+ Matcher.build_session&.[](Call)&.delete(:last_assign)
40
+ end
41
+
42
+ def initialize(receiver, method, args = [], kwargs = {}, block = nil)
43
+ super()
44
+
45
+ @receiver = receiver
46
+ @method = method
47
+ @args = args
48
+ @kwargs = kwargs
49
+ @block = block
50
+
51
+ if binary? && Matcher.settings[:logical_operators]
52
+ case method
53
+ when :&
54
+ @method = :'&&'
55
+ when :|
56
+ @method = :'||'
57
+ end
58
+ end
59
+
60
+ set_last_assign if method.end_with?('=')
61
+ end
62
+
63
+ def unary?
64
+ knary?(0)
65
+ end
66
+
67
+ def binary?
68
+ knary?(1)
69
+ end
70
+
71
+ def knary?(arity)
72
+ @args.length == arity && @kwargs.empty? && !@block
73
+ end
74
+
75
+ def assignment?
76
+ @method.end_with?('=') && !%i[<= >= == === !=].include?(@method)
77
+ end
78
+
79
+ def precedence
80
+ @precedence ||= begin
81
+ has_precedence = (unary? && UNARY_OPERATORS.include?(@method)) ||
82
+ (binary? && BINARY_OPERATORS.include?(@method))
83
+
84
+ # if method is not an operator then precedence is highest (-1)
85
+ has_precedence ? OPERATOR_PRECEDENCE[@method] : -1
86
+ end
87
+ end
88
+
89
+ def evaluate(values)
90
+ receiver = @receiver.evaluate(values)
91
+
92
+ return receiver if lazy?(receiver)
93
+
94
+ args = @args.map { _1.evaluate(values) }
95
+ kwargs = @kwargs.transform_values { _1.evaluate(values) }
96
+
97
+ return args[0] if logical_operator?
98
+
99
+ invoke(values, receiver, args, kwargs)
100
+ end
101
+
102
+ def evaluate_tree(values)
103
+ receiver_t = @receiver.evaluate_tree(values)
104
+ receiver = receiver_t.last
105
+
106
+ return [receiver_t, nil, nil, receiver] if lazy?(receiver)
107
+
108
+ args, args_t = evaluate_args_tree(values)
109
+ kwargs, kwargs_t = evaluate_kwargs_tree(values)
110
+
111
+ return [receiver_t, args_t, kwargs_t, args[0]] if logical_operator?
112
+
113
+ value = invoke(values, receiver, args, kwargs)
114
+
115
+ [receiver_t, args_t, kwargs_t, value]
116
+ end
117
+
118
+ def variables
119
+ @variables ||= begin
120
+ variables = @receiver.variables +
121
+ @args.flat_map(&:variables) +
122
+ @kwargs.each_value.flat_map(&:variables)
123
+
124
+ variables.concat(@block.variables) if @block
125
+
126
+ variables.uniq
127
+ end
128
+ end
129
+
130
+ def ==(other)
131
+ return true if equal?(other)
132
+
133
+ other.instance_of?(Call) &&
134
+ other.receiver == @receiver &&
135
+ other.method == @method &&
136
+ other.args.eql?(@args) &&
137
+ other.kwargs.eql?(@kwargs) &&
138
+ other.block == @block
139
+ end
140
+ alias eql? ==
141
+
142
+ def hash
143
+ @hash ||= [self.class, @receiver, @args, @method, @kwargs, @block].hash
144
+ end
145
+
146
+ def visit(&)
147
+ return to_enum(:visit) unless block_given?
148
+
149
+ @receiver.visit(&)
150
+ @args.each { _1.visit(&) }
151
+ @kwargs.each_value { _1.visit(&) }
152
+
153
+ yield self
154
+ end
155
+
156
+ def substitute(replacements)
157
+ replacement_names = replacements.keys
158
+
159
+ return self unless replacement_names.intersect?(variables)
160
+
161
+ receiver = @receiver.substitute(replacements)
162
+
163
+ no_change = nil
164
+ substitute = lambda do |expression|
165
+ result = expression.substitute(replacements)
166
+ no_change = false unless result.equal?(expression)
167
+
168
+ result
169
+ end
170
+
171
+ no_change = true
172
+ args = @args.map(&substitute)
173
+ args = @args if no_change
174
+
175
+ no_change = true
176
+ kwargs = @kwargs.transform_values(&substitute)
177
+ kwargs = @kwargs if no_change
178
+
179
+ block = block.is_a?(Block) ? @block.substitute(replacements) : @block
180
+
181
+ Call.new(receiver, @method, args, kwargs, block)
182
+ end
183
+
184
+ def to_s
185
+ receiver = parenthesize(@receiver, false)
186
+
187
+ case @method
188
+ when :!, :~, :+@, :-@
189
+ # !foo
190
+ return "#{@method[0]}#{receiver}" if unary?
191
+ when :+, :-, :*, :/, :%, :**,:<, :>, :<=, :>=, :<=>, :==, :===, :!=, :=~, :!~, :&, :|, :^, :<<, :>>, :'&&', :'||'
192
+ if binary?
193
+ operand = parenthesize(@args[0], true)
194
+
195
+ # foo**2
196
+ return "#{receiver}**#{operand}" if @method == :**
197
+
198
+ # foo + bar
199
+ return "#{receiver} #{@method} #{operand}"
200
+ end
201
+ when :[]
202
+ # foo[a, b, ...]
203
+ return "#{receiver}[#{args_and_kwargs_string}]#{block_string}"
204
+ when :[]=
205
+ # foo[a, b, ...] = 1
206
+ if @args.length >= 2 && @kwargs.empty? && !@block
207
+ first_args = @args[0..-2].map(&:to_s).join(', ')
208
+ last_arg = @args[-1].to_s
209
+
210
+ return "#{receiver}[#{first_args}] = #{last_arg}"
211
+ end
212
+ end
213
+
214
+ if @method.end_with?('=') && @method != :[]= && binary?
215
+ # foo.bar = 42
216
+
217
+ "#{receiver}.#{@method[0..-2]} = #{@args[0]}"
218
+ else
219
+ # foo.bar OR foo.bar(arg1, arg2, ...)
220
+
221
+ is_kernel = @receiver.is_a?(Constant) && @receiver.value == Kernel
222
+ args_and_kwargs = args_and_kwargs_string
223
+ string = is_kernel ? @method.to_s : "#{receiver}.#{@method}"
224
+ string += "(#{args_and_kwargs})" unless args_and_kwargs.empty?
225
+ string += block_string
226
+
227
+ string
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ def lazy?(receiver)
234
+ @method == :'&&' && !receiver || @method == :'||' && receiver
235
+ end
236
+
237
+ def logical_operator?
238
+ %i[&& ||].include?(@method)
239
+ end
240
+
241
+ def invoke(values, receiver, args, kwargs)
242
+ block = @block.is_a?(Block) ? @block&.to_proc(values:) : @block
243
+
244
+ begin
245
+ result = receiver.send(@method, *args, **kwargs, &block)
246
+ assignment? ? args.last : result
247
+ rescue => e
248
+ message = "#{self} raised #{e.class}: #{e.message}"
249
+ given = given_for(values)
250
+
251
+ raise CallError.new(message, self, given)
252
+ end
253
+ end
254
+
255
+ def set_last_assign
256
+ build_session = Matcher.build_session
257
+
258
+ return unless build_session
259
+
260
+ call_session = (build_session[Call] ||= {})
261
+ call_session[:last_assign] = self
262
+ end
263
+
264
+ def evaluate_args_tree(values)
265
+ n = @args.length
266
+ args = Array.new(n)
267
+ args_t = Array.new(n)
268
+
269
+ @args.each_with_index do |arg, i|
270
+ arg_t = arg.evaluate_tree(values)
271
+ args[i] = arg_t.last
272
+ args_t[i] = arg_t
273
+ end
274
+
275
+ [args, args_t]
276
+ end
277
+
278
+ def evaluate_kwargs_tree(values)
279
+ kwargs = {}
280
+ kwargs_t = {}
281
+
282
+ @kwargs.each do |key, kwarg|
283
+ kwarg_t = kwarg.evaluate_tree(values)
284
+ kwargs[key] = kwarg_t.last
285
+ kwargs_t[key] = kwarg_t
286
+ end
287
+
288
+ [kwargs, kwargs_t]
289
+ end
290
+
291
+ def args_and_kwargs_string
292
+ args = @args.map(&:to_s)
293
+ kwargs = @kwargs.map do |k, v|
294
+ v_to_s = v.to_s
295
+
296
+ if k.is_a?(Symbol)
297
+ "#{k}: #{v_to_s}"
298
+ else
299
+ "#{k.inspect} => #{v_to_s}"
300
+ end
301
+ end
302
+
303
+ list = args + kwargs
304
+ list << "&#{@block.symbol.inspect}" if @block&.is_a?(SymbolProc)
305
+
306
+ list.join(', ')
307
+ end
308
+
309
+ def block_string
310
+ if @block.is_a?(Block)
311
+ " #{@block.to_s(as_block: true)}"
312
+ elsif @block && !@block.is_a?(SymbolProc)
313
+ ' { ... }'
314
+ else
315
+ ''
316
+ end
317
+ end
318
+
319
+ def parenthesize(operand, is_rhs)
320
+ operand_string = operand.to_s
321
+
322
+ return "(#{operand_string})" if operand.is_a?(RescueLastErrorExpression)
323
+ return operand_string unless operand.instance_of?(Call)
324
+
325
+ # parenthesize if operand's precedence is lower (higher index) than ours
326
+ need_parentheses = if is_rhs
327
+ # also parenthesize rhs if precedence is the same
328
+ operand.precedence >= precedence
329
+ else
330
+ # also parenthesize lhs if both operators are any of: <=> == === != =~ !~
331
+ operand.precedence > precedence ||
332
+ operand.precedence == precedence && %i[<=> == === != =~ !~].include?(@method)
333
+ end
334
+
335
+ need_parentheses ? "(#{operand_string})" : operand_string
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class CallError < StandardError
5
+ attr_reader :call, :given
6
+
7
+ def initialize(message, call, given)
8
+ super(message)
9
+
10
+ @call = call
11
+ @given = given
12
+ end
13
+
14
+ def message_for_errors(actual)
15
+ case cause
16
+ when NoMethodError
17
+ not_responding_message(actual)
18
+ else
19
+ raising_message(actual)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def raising_message(actual)
26
+ Message.new(%i[expression raising], false, actual, @call, cause, @given)
27
+ end
28
+
29
+ def not_responding_message(actual)
30
+ if @call.receiver == Variable.actual
31
+ Message.new(:responding_to, true, cause.receiver, @call.method)
32
+ else
33
+ Message.new(
34
+ %i[expression responding_to],
35
+ true,
36
+ actual,
37
+ @call.receiver,
38
+ cause.receiver,
39
+ @call.method,
40
+ @given,
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Constant < Expression
5
+ def self.cache(value, expression_cache = ExpressionCache.current)
6
+ if expression_cache
7
+ expression_cache.constant_for(value)
8
+ else
9
+ Constant.new(value)
10
+ end
11
+ end
12
+
13
+ attr_reader :value
14
+
15
+ def initialize(value)
16
+ super()
17
+
18
+ @value = value
19
+ end
20
+
21
+ def negated
22
+ Constant.new(!@value)
23
+ end
24
+
25
+ def variables
26
+ []
27
+ end
28
+
29
+ def evaluate(_values)
30
+ @value
31
+ end
32
+
33
+ def ==(other)
34
+ return true if equal?(other)
35
+
36
+ other.instance_of?(Constant) &&
37
+ @value.eql?(other.value)
38
+ end
39
+ alias eql? ==
40
+
41
+ def hash
42
+ @hash ||= [self.class, @value].hash
43
+ end
44
+
45
+ def substitute(_replacements)
46
+ self
47
+ end
48
+
49
+ def to_s(substitutions: nil)
50
+ @value.inspect
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Expression
5
+ class ExpressionBuilder
6
+ include ExpressionBuilding
7
+
8
+ def initialize(build_session: Matcher.build_session)
9
+ ExpressionBuilding.init(self, build_session)
10
+ end
11
+ end
12
+
13
+ def self.build(&)
14
+ Matcher.with_build_session do |build_session|
15
+ builder = ExpressionBuilder.new(build_session:)
16
+ result = builder.instance_exec(&)
17
+ builder.expression_of(result)
18
+ end
19
+ end
20
+
21
+ def self.expression_or_value(obj, expression_cache: nil)
22
+ case obj
23
+ when -> { Recorder.recorder?(_1) }
24
+ return Recorder.to_expression(obj)
25
+ when Base
26
+ raise ArgumentError, 'Cannot use matcher as expression'
27
+ when NoExpression
28
+ raise ArgumentError, "Cannot use #{obj.class} as expression"
29
+ when Proc
30
+ raise ArgumentError, "Cannot use Proc as expression. Use `expr { ... }' instead"
31
+ when Array
32
+ items = obj.map { expression_or_value(_1, expression_cache:) }
33
+
34
+ if items.any? { _1.is_a?(Expression) }
35
+ items.each_with_index do |item, i|
36
+ items[i] = Constant.cache(item, expression_cache) unless item.is_a?(Expression)
37
+ end
38
+
39
+ return ArrayExpression.new(items)
40
+ end
41
+ when Hash
42
+ pairs = obj.map do |key, value|
43
+ key = expression_or_value(key, expression_cache:)
44
+ value = expression_or_value(value, expression_cache:)
45
+
46
+ [key, value]
47
+ end
48
+
49
+ if pairs.any? { |k, v| k.is_a?(Expression) || v.is_a?(Expression) }
50
+ pairs.each do |pair|
51
+ k, v = pair
52
+
53
+ pair[0] = Constant.cache(k, expression_cache) unless k.is_a?(Expression)
54
+ pair[1] = Constant.cache(v, expression_cache) unless v.is_a?(Expression)
55
+ end
56
+
57
+ return HashExpression.new(pairs)
58
+ end
59
+ when Range
60
+ from = expression_or_value(obj.begin, expression_cache:)
61
+ to = expression_or_value(obj.end, expression_cache:)
62
+
63
+ if from.is_a?(Expression) || to.is_a?(Expression)
64
+ from = Constant.cache(from, expression_cache) unless from.is_a?(Expression)
65
+ to = Constant.cache(to, expression_cache) unless to.is_a?(Expression)
66
+
67
+ return RangeExpression.new(from, to, obj.exclude_end?)
68
+ end
69
+ when Set
70
+ items = obj.map { expression_or_value(_1, expression_cache:) }
71
+
72
+ if items.any? { _1.is_a?(Expression) }
73
+ items.each_with_index do |item, i|
74
+ items[i] = Constant.cache(item, expression_cache) unless item.is_a?(Expression)
75
+ end
76
+
77
+ return SetExpression.new(items)
78
+ end
79
+ end
80
+
81
+ obj
82
+ end
83
+
84
+ def self.of(obj, expression_cache: nil)
85
+ obj = expression_or_value(obj, expression_cache:)
86
+
87
+ if obj.is_a?(Expression)
88
+ obj
89
+ else
90
+ Constant.cache(obj, expression_cache)
91
+ end
92
+ end
93
+
94
+ def self.try_recorder(obj)
95
+ return obj unless Recorder.recorder?(obj)
96
+
97
+ Recorder.to_expression(obj)
98
+ end
99
+
100
+ def initialize
101
+ raise 'abstract class' if instance_of?(Expression)
102
+ end
103
+
104
+ def evaluate_tree(values)
105
+ [evaluate(values)]
106
+ end
107
+
108
+ def given_for(values)
109
+ variables.each_with_object({}) do |symbol, given|
110
+ given[symbol] = values[symbol]
111
+ end
112
+ end
113
+
114
+ def visit
115
+ return to_enum(:visit) unless block_given?
116
+
117
+ yield self
118
+ end
119
+
120
+ def free_symbol(symbol)
121
+ parameters = ExpressionWalker.each_block(self).flat_map do |block|
122
+ block.parameters.map { |_type, name| name }
123
+ end
124
+
125
+ identifiers = (variables + parameters).to_set
126
+
127
+ return symbol unless identifiers.include?(symbol)
128
+
129
+ i = 2
130
+ loop do
131
+ symbol_i = :"#{symbol}#{i}"
132
+
133
+ return symbol_i unless identifiers.include?(symbol_i)
134
+
135
+ i += 1
136
+ end
137
+ end
138
+
139
+ def inspect
140
+ to_s
141
+ end
142
+
143
+ def to_recorder
144
+ Recorder.new(self)
145
+ end
146
+ end
147
+ end