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.
- checksums.yaml +7 -0
- data/lib/matcher/assertions.rb +21 -0
- data/lib/matcher/base.rb +189 -0
- data/lib/matcher/builder.rb +74 -0
- data/lib/matcher/chain.rb +60 -0
- data/lib/matcher/debug.rb +48 -0
- data/lib/matcher/errors/and_error.rb +86 -0
- data/lib/matcher/errors/boolean_collector.rb +51 -0
- data/lib/matcher/errors/element_error.rb +24 -0
- data/lib/matcher/errors/empty_error.rb +23 -0
- data/lib/matcher/errors/error.rb +39 -0
- data/lib/matcher/errors/error_collector.rb +99 -0
- data/lib/matcher/errors/nested_error.rb +96 -0
- data/lib/matcher/errors/or_error.rb +86 -0
- data/lib/matcher/expression_cache.rb +57 -0
- data/lib/matcher/expression_labeler.rb +91 -0
- data/lib/matcher/expressions/array_expression.rb +45 -0
- data/lib/matcher/expressions/block.rb +153 -0
- data/lib/matcher/expressions/call.rb +338 -0
- data/lib/matcher/expressions/call_error.rb +45 -0
- data/lib/matcher/expressions/constant.rb +53 -0
- data/lib/matcher/expressions/expression.rb +147 -0
- data/lib/matcher/expressions/expression_building.rb +258 -0
- data/lib/matcher/expressions/expression_walker.rb +73 -0
- data/lib/matcher/expressions/hash_expression.rb +59 -0
- data/lib/matcher/expressions/proc_expression.rb +92 -0
- data/lib/matcher/expressions/range_expression.rb +58 -0
- data/lib/matcher/expressions/recorder.rb +86 -0
- data/lib/matcher/expressions/rescue_last_error_expression.rb +44 -0
- data/lib/matcher/expressions/set_expression.rb +45 -0
- data/lib/matcher/expressions/string_expression.rb +53 -0
- data/lib/matcher/expressions/symbol_proc.rb +53 -0
- data/lib/matcher/expressions/variable.rb +85 -0
- data/lib/matcher/hash_stack.rb +53 -0
- data/lib/matcher/list.rb +102 -0
- data/lib/matcher/markers/optional.rb +80 -0
- data/lib/matcher/markers/others.rb +28 -0
- data/lib/matcher/matcher_cache.rb +18 -0
- data/lib/matcher/matchers/all_matcher.rb +60 -0
- data/lib/matcher/matchers/always_matcher.rb +28 -0
- data/lib/matcher/matchers/any_matcher.rb +70 -0
- data/lib/matcher/matchers/array_matcher.rb +35 -0
- data/lib/matcher/matchers/block_matcher.rb +59 -0
- data/lib/matcher/matchers/boolean_matcher.rb +35 -0
- data/lib/matcher/matchers/dig_matcher.rb +146 -0
- data/lib/matcher/matchers/each_matcher.rb +52 -0
- data/lib/matcher/matchers/each_pair_matcher.rb +119 -0
- data/lib/matcher/matchers/equal_matcher.rb +197 -0
- data/lib/matcher/matchers/equal_set_matcher.rb +99 -0
- data/lib/matcher/matchers/expression_matcher.rb +73 -0
- data/lib/matcher/matchers/filter_matcher.rb +111 -0
- data/lib/matcher/matchers/hash_matcher.rb +223 -0
- data/lib/matcher/matchers/imply_matcher.rb +81 -0
- data/lib/matcher/matchers/imply_some_matcher.rb +112 -0
- data/lib/matcher/matchers/index_by_matcher.rb +175 -0
- data/lib/matcher/matchers/inline_matcher.rb +99 -0
- data/lib/matcher/matchers/keys_matcher.rb +121 -0
- data/lib/matcher/matchers/kind_of_matcher.rb +35 -0
- data/lib/matcher/matchers/lazy_all_matcher.rb +68 -0
- data/lib/matcher/matchers/lazy_any_matcher.rb +68 -0
- data/lib/matcher/matchers/let_matcher.rb +73 -0
- data/lib/matcher/matchers/map_matcher.rb +129 -0
- data/lib/matcher/matchers/matcher_building.rb +5 -0
- data/lib/matcher/matchers/negated_array_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_each_matcher.rb +36 -0
- data/lib/matcher/matchers/negated_each_pair_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_imply_some_matcher.rb +46 -0
- data/lib/matcher/matchers/negated_matcher.rb +23 -0
- data/lib/matcher/matchers/negated_project_matcher.rb +31 -0
- data/lib/matcher/matchers/never_matcher.rb +29 -0
- data/lib/matcher/matchers/one_matcher.rb +70 -0
- data/lib/matcher/matchers/optional_matcher.rb +38 -0
- data/lib/matcher/matchers/parse_float_matcher.rb +86 -0
- data/lib/matcher/matchers/parse_integer_matcher.rb +98 -0
- data/lib/matcher/matchers/parse_iso8601_matcher.rb +92 -0
- data/lib/matcher/matchers/parse_json_matcher.rb +95 -0
- data/lib/matcher/matchers/project_matcher.rb +68 -0
- data/lib/matcher/matchers/raises_matcher.rb +124 -0
- data/lib/matcher/matchers/range_matcher.rb +47 -0
- data/lib/matcher/matchers/reference_matcher.rb +111 -0
- data/lib/matcher/matchers/reference_matcher_collection.rb +57 -0
- data/lib/matcher/matchers/regexp_matcher.rb +84 -0
- data/lib/matcher/messages/expected_phrasing.rb +342 -0
- data/lib/matcher/messages/message.rb +102 -0
- data/lib/matcher/messages/message_builder.rb +35 -0
- data/lib/matcher/messages/message_rules.rb +223 -0
- data/lib/matcher/messages/namespaced_message_builder.rb +19 -0
- data/lib/matcher/messages/phrasing.rb +57 -0
- data/lib/matcher/messages/standard_message_builder.rb +105 -0
- data/lib/matcher/once_before.rb +18 -0
- data/lib/matcher/optional_chain.rb +24 -0
- data/lib/matcher/patterns/ast_mapping.rb +42 -0
- data/lib/matcher/patterns/capture_hole.rb +33 -0
- data/lib/matcher/patterns/constant_hole.rb +14 -0
- data/lib/matcher/patterns/hole.rb +30 -0
- data/lib/matcher/patterns/method_hole.rb +58 -0
- data/lib/matcher/patterns/pattern.rb +92 -0
- data/lib/matcher/patterns/pattern_building.rb +39 -0
- data/lib/matcher/patterns/pattern_capture.rb +11 -0
- data/lib/matcher/patterns/pattern_match.rb +29 -0
- data/lib/matcher/patterns/variable_hole.rb +14 -0
- data/lib/matcher/reporter.rb +98 -0
- data/lib/matcher/rules/message_factory.rb +25 -0
- data/lib/matcher/rules/message_rule.rb +18 -0
- data/lib/matcher/rules/message_rule_context.rb +24 -0
- data/lib/matcher/rules/rule_builder.rb +29 -0
- data/lib/matcher/rules/rule_set.rb +57 -0
- data/lib/matcher/rules/transform_builder.rb +24 -0
- data/lib/matcher/rules/transform_mapping.rb +5 -0
- data/lib/matcher/rules/transform_rule.rb +21 -0
- data/lib/matcher/state.rb +40 -0
- data/lib/matcher/testing/error_builder.rb +62 -0
- data/lib/matcher/testing/error_checker.rb +496 -0
- data/lib/matcher/testing/error_testing.rb +37 -0
- data/lib/matcher/testing/pattern_testing.rb +11 -0
- data/lib/matcher/testing/pattern_testing_scope.rb +34 -0
- data/lib/matcher/testing.rb +102 -0
- data/lib/matcher/undefined.rb +10 -0
- data/lib/matcher/utils/mapping_utils.rb +61 -0
- data/lib/matcher/utils.rb +72 -0
- data/lib/matcher/version.rb +5 -0
- data/lib/matcher.rb +337 -0
- 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
|