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,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ErrorCollector
|
|
5
|
+
def self.error_from(obj)
|
|
6
|
+
case obj
|
|
7
|
+
when String
|
|
8
|
+
ElementError.new(obj)
|
|
9
|
+
when Message
|
|
10
|
+
ElementError.new(obj)
|
|
11
|
+
when Error
|
|
12
|
+
obj
|
|
13
|
+
else
|
|
14
|
+
"Unexpected error object: #{obj.inspect}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :error
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@error = EmptyError.instance
|
|
22
|
+
@mode = :and
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def empty?
|
|
26
|
+
@error.valid?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def or!
|
|
30
|
+
@mode = :or
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def or?
|
|
35
|
+
@mode == :or
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def and?
|
|
39
|
+
@mode == :and
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Brackets
|
|
43
|
+
def initialize(parent, key)
|
|
44
|
+
@parent = parent
|
|
45
|
+
@key = key
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def error
|
|
49
|
+
@parent.error
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def <<(error)
|
|
53
|
+
error = ErrorCollector.error_from(error)
|
|
54
|
+
|
|
55
|
+
return error if error.is_a?(EmptyError) || @key == Variable.actual
|
|
56
|
+
|
|
57
|
+
key = @key
|
|
58
|
+
key = Call.new(Variable.actual, :[], [Constant.new(key)]) unless key.is_a?(Expression)
|
|
59
|
+
|
|
60
|
+
@parent << NestedError.new(key, error)
|
|
61
|
+
|
|
62
|
+
@parent.error
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def [](key)
|
|
66
|
+
Brackets.new(self, key)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def <<(error)
|
|
71
|
+
return @error if error.is_a?(EmptyError)
|
|
72
|
+
|
|
73
|
+
error = ErrorCollector.error_from(error)
|
|
74
|
+
|
|
75
|
+
case @error
|
|
76
|
+
when EmptyError
|
|
77
|
+
@error = error
|
|
78
|
+
when and? ? AndError : OrError
|
|
79
|
+
@error << error
|
|
80
|
+
else
|
|
81
|
+
if and?
|
|
82
|
+
@error &= error
|
|
83
|
+
else
|
|
84
|
+
@error |= error
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@error
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def [](key)
|
|
92
|
+
Brackets.new(self, key)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def clear
|
|
96
|
+
@error = EmptyError.instance
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class NestedError < Error
|
|
5
|
+
def self.from(key, child)
|
|
6
|
+
return child if child.is_a?(EmptyError) || key == Variable.actual
|
|
7
|
+
|
|
8
|
+
key = Call.new(Variable.actual, :[], [Constant.new(key)]) unless key.is_a?(Expression)
|
|
9
|
+
|
|
10
|
+
NestedError.new(key, child)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.key_to_s(key, path)
|
|
14
|
+
remaining = 20 + path.length
|
|
15
|
+
key.visit do |expr|
|
|
16
|
+
next if !expr.is_a?(Variable) || expr.symbol != :actual
|
|
17
|
+
|
|
18
|
+
remaining -= path.length
|
|
19
|
+
break if remaining < 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if remaining >= 0 && key.variables.include?(:actual)
|
|
23
|
+
Variable.with_substitutions(actual: path) do
|
|
24
|
+
key.to_s
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
"#{path} -> #{key}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader :key, :child
|
|
32
|
+
|
|
33
|
+
def initialize(key, child)
|
|
34
|
+
super()
|
|
35
|
+
|
|
36
|
+
@key = key
|
|
37
|
+
@child = child
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ==(other)
|
|
41
|
+
return true if equal?(other)
|
|
42
|
+
|
|
43
|
+
other.instance_of?(NestedError) &&
|
|
44
|
+
@key.eql?(other.key) &&
|
|
45
|
+
@child == other.child
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def &(other)
|
|
49
|
+
return self if other.is_a?(EmptyError)
|
|
50
|
+
|
|
51
|
+
if other.is_a?(NestedError) && @key == other.key
|
|
52
|
+
NestedError.new(@key, @child & other.child)
|
|
53
|
+
elsif other.is_a?(AndError)
|
|
54
|
+
errors = other.children
|
|
55
|
+
index = errors.find_index { _1.is_a?(NestedError) && _1.key == @key }
|
|
56
|
+
|
|
57
|
+
if index
|
|
58
|
+
new_errors = errors.dup
|
|
59
|
+
new_errors[index] = self & errors[index]
|
|
60
|
+
else
|
|
61
|
+
new_errors = [self] + errors
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
AndError.new(new_errors)
|
|
65
|
+
else
|
|
66
|
+
AndError.new([self, other])
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def |(other)
|
|
71
|
+
return self if other.is_a?(EmptyError)
|
|
72
|
+
|
|
73
|
+
if other.is_a?(NestedError) && other.key == @key
|
|
74
|
+
NestedError.new(@key, @child | other.child)
|
|
75
|
+
elsif other.is_a?(OrError)
|
|
76
|
+
errors = other.children
|
|
77
|
+
index = errors.find_index { _1.is_a?(NestedError) && _1.key == @key }
|
|
78
|
+
|
|
79
|
+
if index
|
|
80
|
+
new_errors = errors.dup
|
|
81
|
+
new_errors[index] = self | errors[index]
|
|
82
|
+
else
|
|
83
|
+
new_errors = [self] + errors
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
OrError.new(new_errors)
|
|
87
|
+
else
|
|
88
|
+
OrError.new([self, other])
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_s
|
|
93
|
+
"#{@key} -> #{@child}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class OrError < Error
|
|
5
|
+
attr_reader :children
|
|
6
|
+
|
|
7
|
+
def self.from(children)
|
|
8
|
+
length = children.length
|
|
9
|
+
|
|
10
|
+
return EmptyError.instance if length == 0
|
|
11
|
+
|
|
12
|
+
children.reduce do |left, right|
|
|
13
|
+
if left.is_a?(OrError)
|
|
14
|
+
left << right
|
|
15
|
+
else
|
|
16
|
+
left | right
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(children)
|
|
22
|
+
raise 'children fewer than 2' if children.length < 2
|
|
23
|
+
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
@children = children
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ==(other)
|
|
30
|
+
return true if equal?(other)
|
|
31
|
+
|
|
32
|
+
other.instance_of?(OrError) &&
|
|
33
|
+
@children == other.children
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def |(other)
|
|
37
|
+
return self if other.is_a?(EmptyError)
|
|
38
|
+
|
|
39
|
+
clone << other
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add(other)
|
|
43
|
+
case other
|
|
44
|
+
when OrError
|
|
45
|
+
right = other.children.dup
|
|
46
|
+
|
|
47
|
+
@children.each_with_index do |l, i|
|
|
48
|
+
next unless l.is_a?(NestedError)
|
|
49
|
+
|
|
50
|
+
index = right.find_index { _1.is_a?(NestedError) && _1.key == l.key }
|
|
51
|
+
|
|
52
|
+
@children[i] = l | right.delete_at(index) if index
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@children.concat(right)
|
|
56
|
+
when NestedError
|
|
57
|
+
index = @children.find_index { _1.is_a?(NestedError) && _1.key == other.key }
|
|
58
|
+
|
|
59
|
+
if index
|
|
60
|
+
@children[index] |= other
|
|
61
|
+
else
|
|
62
|
+
@children << other
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
@children << other
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
alias << add
|
|
71
|
+
|
|
72
|
+
def clone
|
|
73
|
+
klone = super
|
|
74
|
+
klone.instance_exec do
|
|
75
|
+
@children = @children.dup
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
klone
|
|
79
|
+
end
|
|
80
|
+
alias dup clone
|
|
81
|
+
|
|
82
|
+
def to_s
|
|
83
|
+
"(#{@children.map(&:to_s).join(' | ')})"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ExpressionCache < ExpressionLabeler
|
|
5
|
+
def self.current(build_session = Matcher.build_session)
|
|
6
|
+
build_session[:_expression_cache] ||= new if build_session
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super
|
|
11
|
+
|
|
12
|
+
@cache = Hash.new.compare_by_identity
|
|
13
|
+
@index = [Variable.actual]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def [](expression)
|
|
17
|
+
@index[label(expression)]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def label(expression, actual_label = ROOT)
|
|
21
|
+
cached_label = @cache[expression]
|
|
22
|
+
|
|
23
|
+
return cached_label if cached_label
|
|
24
|
+
|
|
25
|
+
count = @label_count
|
|
26
|
+
label = super
|
|
27
|
+
|
|
28
|
+
if label > count
|
|
29
|
+
if expression.is_a?(Variable)
|
|
30
|
+
symbol = expression.symbol
|
|
31
|
+
expression = Variable.send(symbol) if Variable.well_known?(symbol)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@cache[expression] = label
|
|
35
|
+
@index[label] = expression
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
label
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def constant_for(value)
|
|
42
|
+
label = label_for(@constant_labels, value)
|
|
43
|
+
@index[label] ||= Constant.new(value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def variable_for(symbol)
|
|
47
|
+
return Variable.send(symbol) if Variable.well_known?(symbol)
|
|
48
|
+
|
|
49
|
+
less_known_variable_for(symbol)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def less_known_variable_for(symbol)
|
|
53
|
+
label = label_for(@variable_labels, symbol)
|
|
54
|
+
@index[label] ||= Variable.new(symbol)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ExpressionLabeler
|
|
5
|
+
ROOT = 0
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@label_count = 0
|
|
9
|
+
|
|
10
|
+
@constant_labels = {}
|
|
11
|
+
@variable_labels = {}
|
|
12
|
+
@call_labels = {}
|
|
13
|
+
@block_labels = {}
|
|
14
|
+
@proc_labels = {}
|
|
15
|
+
@array_labels = {}
|
|
16
|
+
@set_labels = {}
|
|
17
|
+
@hash_labels = {}
|
|
18
|
+
@range_labels = {}
|
|
19
|
+
@string_labels = {}
|
|
20
|
+
@rescue_labels = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def label(expression, actual_label = ROOT)
|
|
24
|
+
case expression
|
|
25
|
+
when Constant
|
|
26
|
+
label_for(@constant_labels, expression.value)
|
|
27
|
+
when Variable
|
|
28
|
+
if expression.symbol == :actual
|
|
29
|
+
actual_label
|
|
30
|
+
else
|
|
31
|
+
label_for(@variable_labels, expression.symbol)
|
|
32
|
+
end
|
|
33
|
+
when Call
|
|
34
|
+
receiver_l = label(expression.receiver, actual_label)
|
|
35
|
+
args_l = expression.args.map { label(_1, actual_label) }
|
|
36
|
+
kwargs_l = expression.kwargs.transform_values { label(_1, actual_label) }
|
|
37
|
+
block_l = label_for_block(expression.block)
|
|
38
|
+
|
|
39
|
+
label_for(@call_labels, [receiver_l, expression.method, args_l, kwargs_l, block_l])
|
|
40
|
+
when ProcExpression
|
|
41
|
+
label_for(@proc_labels, expression.block)
|
|
42
|
+
when ArrayExpression
|
|
43
|
+
items_l = expression.items.map { label(_1, actual_label) }
|
|
44
|
+
|
|
45
|
+
label_for(@array_labels, items_l)
|
|
46
|
+
when SetExpression
|
|
47
|
+
items_l = expression.items.map { label(_1, actual_label) }
|
|
48
|
+
|
|
49
|
+
label_for(@set_labels, items_l)
|
|
50
|
+
when HashExpression
|
|
51
|
+
pairs_l = expression.pairs.flat_map do |k, v|
|
|
52
|
+
[label(k, actual_label), label(v, actual_label)]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
label_for(@hash_labels, pairs_l)
|
|
56
|
+
when RangeExpression
|
|
57
|
+
begin_l = label(expression.begin, actual_label)
|
|
58
|
+
end_l = label(expression.end, actual_label)
|
|
59
|
+
|
|
60
|
+
label_for(@range_labels, [begin_l, end_l, expression.exclude_end?])
|
|
61
|
+
when StringExpression
|
|
62
|
+
parts_l = expression.parts.map { label(_1, actual_label) }
|
|
63
|
+
|
|
64
|
+
label_for(@string_labels, parts_l)
|
|
65
|
+
when RescueLastErrorExpression
|
|
66
|
+
expression_l = label(expression.expression, actual_label)
|
|
67
|
+
|
|
68
|
+
label_for(@rescue_labels, expression_l)
|
|
69
|
+
else
|
|
70
|
+
raise "unexpected expression: #{expression.inspect}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def label_for(index, key)
|
|
77
|
+
index[key] ||= (@label_count += 1)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def label_for_block(block)
|
|
81
|
+
case block
|
|
82
|
+
when Block
|
|
83
|
+
label_for(@block_labels, [block.parameters, label(block.expression)])
|
|
84
|
+
when SymbolProc
|
|
85
|
+
label_for(@block_labels, block.symbol)
|
|
86
|
+
else # nil, Proc
|
|
87
|
+
label_for(@block_labels, block)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ArrayExpression < Expression
|
|
5
|
+
def initialize(items)
|
|
6
|
+
super()
|
|
7
|
+
|
|
8
|
+
@items = items
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :items
|
|
12
|
+
|
|
13
|
+
def ==(other)
|
|
14
|
+
return true if equal?(other)
|
|
15
|
+
|
|
16
|
+
other.instance_of?(ArrayExpression) &&
|
|
17
|
+
other.items.eql?(@items)
|
|
18
|
+
end
|
|
19
|
+
alias eql? ==
|
|
20
|
+
|
|
21
|
+
def hash
|
|
22
|
+
@hash ||= [self.class, @items].hash
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def variables
|
|
26
|
+
@variables ||= @items.flat_map(&:variables).uniq
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def evaluate(values)
|
|
30
|
+
@items.map { _1.evaluate(values) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def substitute(replacements)
|
|
34
|
+
return self unless replacements.keys.intersect?(variables)
|
|
35
|
+
|
|
36
|
+
substituted_items = @items.map { _1.substitute(replacements) }
|
|
37
|
+
|
|
38
|
+
ArrayExpression.new(substituted_items)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
"[#{@items.map(&:to_s).join(', ')}]"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class Block
|
|
5
|
+
class Context
|
|
6
|
+
attr_reader :expression, :values
|
|
7
|
+
|
|
8
|
+
def initialize(expression, values)
|
|
9
|
+
@expression = expression
|
|
10
|
+
@values = values
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def evaluate(values)
|
|
14
|
+
values.merge!(@values) { |_k, _l, r| r }
|
|
15
|
+
|
|
16
|
+
@expression.evaluate(values)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.build(expression_cache: nil, &block)
|
|
21
|
+
return SymbolProc.new(block) if
|
|
22
|
+
block.parameters == [[:req], [:rest]] &&
|
|
23
|
+
/\(&:(\w+[!?]?|".*")\)/.match?(block.to_s)
|
|
24
|
+
|
|
25
|
+
parameters = block.parameters
|
|
26
|
+
args = []
|
|
27
|
+
kwargs = {}
|
|
28
|
+
parameter_names = Set.new
|
|
29
|
+
variable_object_ids = Set.new
|
|
30
|
+
|
|
31
|
+
parameters.each do |type, name|
|
|
32
|
+
parameter_names << name
|
|
33
|
+
variable = Variable.cache(name, expression_cache:)
|
|
34
|
+
variable_object_ids << variable.object_id
|
|
35
|
+
|
|
36
|
+
case type
|
|
37
|
+
when :req, :opt
|
|
38
|
+
args << variable.to_recorder
|
|
39
|
+
when :key, :keyreq
|
|
40
|
+
kwargs[name] = variable.to_recorder
|
|
41
|
+
when :rest
|
|
42
|
+
raise "*#{name unless name == :*} not allowed"
|
|
43
|
+
when :keyrest
|
|
44
|
+
raise "**#{name unless name == :**} not allowed"
|
|
45
|
+
when :block
|
|
46
|
+
raise "&#{name unless name == :&} not allowed"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
result = block.call(*args, **kwargs)
|
|
51
|
+
expression = Expression.of(result, expression_cache:)
|
|
52
|
+
|
|
53
|
+
return SymbolProc.new(expression.method) if
|
|
54
|
+
args.length == 1 &&
|
|
55
|
+
expression.is_a?(Call) &&
|
|
56
|
+
expression.unary? &&
|
|
57
|
+
expression.receiver == Recorder.to_expression(args[0])
|
|
58
|
+
|
|
59
|
+
ExpressionWalker.each_variable(expression) do |variable|
|
|
60
|
+
raise "parameter `#{variable.symbol}' shadows an outer variable" if
|
|
61
|
+
parameter_names.include?(variable.symbol) &&
|
|
62
|
+
!variable_object_ids.include?(variable.object_id)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
new(parameters, expression)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :parameters, :expression
|
|
69
|
+
|
|
70
|
+
def initialize(parameters, expression)
|
|
71
|
+
@parameters = parameters
|
|
72
|
+
@expression = expression
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ==(other)
|
|
76
|
+
return true if equal?(other)
|
|
77
|
+
|
|
78
|
+
other.instance_of?(Block) &&
|
|
79
|
+
@parameters.eql?(other.parameters) &&
|
|
80
|
+
@expression == other.expression
|
|
81
|
+
end
|
|
82
|
+
alias eql? ==
|
|
83
|
+
|
|
84
|
+
def hash
|
|
85
|
+
[self.class, @parameters, @expression].hash
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def variables
|
|
89
|
+
@variables ||= @expression.variables - @parameters.map { _2 }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def substitute(replacements)
|
|
93
|
+
replacements = replacements.slice(*variables)
|
|
94
|
+
|
|
95
|
+
return self if replacements.empty?
|
|
96
|
+
|
|
97
|
+
expression = @expression.substitute(replacements)
|
|
98
|
+
|
|
99
|
+
Block.new(@parameters, expression)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def to_proc(values: nil)
|
|
103
|
+
@proc ||= begin
|
|
104
|
+
kwlist = @parameters.map { |_type, name| "#{name}:" }.join(', ')
|
|
105
|
+
|
|
106
|
+
instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
107
|
+
->(#{arg_list}) { evaluate({ #{kwlist} }) } # ->(arg, kwarg:) { evaluate({ arg:, kwarg: }) }
|
|
108
|
+
RUBY
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if values && !variables.empty?
|
|
112
|
+
values = values.slice(*variables)
|
|
113
|
+
|
|
114
|
+
return @proc if values.empty?
|
|
115
|
+
|
|
116
|
+
context = Context.new(@expression, values)
|
|
117
|
+
|
|
118
|
+
lambda do |*args, **kwargs|
|
|
119
|
+
context.instance_exec(*args, **kwargs, &@proc)
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
@proc
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def to_s(as_block: false)
|
|
127
|
+
if @parameters.empty?
|
|
128
|
+
as_block ? "{ #{@expression} }" : "-> { #{@expression} }"
|
|
129
|
+
else
|
|
130
|
+
args = arg_list
|
|
131
|
+
as_block ? "{ |#{args}| #{@expression} }" : "->(#{args}) { #{@expression} }"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
alias inspect to_s
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def arg_list
|
|
139
|
+
@parameters.map do |type, name|
|
|
140
|
+
case type
|
|
141
|
+
when :req, :opt
|
|
142
|
+
name
|
|
143
|
+
when :key, :keyreq
|
|
144
|
+
"#{name}:"
|
|
145
|
+
end
|
|
146
|
+
end.join(', ')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def evaluate(values)
|
|
150
|
+
@expression.evaluate(**values)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|