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.
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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dd26d76a69e586fdc17822b522ad0a1761f2ad87526ecbf818018f7505b52620
4
+ data.tar.gz: 4c621e0c1f678c9846b9dfdffddc089db092b797eddc9db8b919ad496a2d8598
5
+ SHA512:
6
+ metadata.gz: fe00a5777c09a4d01a904c0337131474c3c77cee05b4b0a31a5c3ff5a8a915c74164d78102880226062cbe2ad6c00d8b5b3430f9521bdd44f38aa23225091509
7
+ data.tar.gz: 5492feb0ec66aab747ef9d32f9d5c3c5f66eb9c357533c979e1ed57909358337cebdd7a0ef3aec6cb5f54fd13ee1fdd30ba2547111dd5412d0cf052f72de2ff4
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ module Assertions
5
+ def assert_structure(actual, &)
6
+ errors = Matcher.build(&).match(actual)
7
+
8
+ # rubocop:disable Minitest/AssertWithExpectedArgument
9
+ assert(false, <<~TEXT.chomp) unless errors.valid?
10
+ For object:
11
+
12
+ #{actual.inspect}
13
+
14
+ The following conditions were not satisfied:
15
+
16
+ #{Reporter.report(errors)}
17
+ TEXT
18
+ # rubocop:enable Minitest/AssertWithExpectedArgument
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Base
5
+ include NoExpression
6
+ include NoKey
7
+
8
+ ##
9
+ # Negates this matcher
10
+ def ~
11
+ matcher_cache = MatcherCache.current
12
+
13
+ return negate unless matcher_cache
14
+
15
+ cache = (matcher_cache.negated_matchers ||= {}.compare_by_identity)
16
+ negated = cache[self]
17
+
18
+ unless negated
19
+ negated = negate
20
+ cache[self] = negated
21
+ cache[negated] = self
22
+ end
23
+
24
+ negated
25
+ end
26
+
27
+ def negate
28
+ NegatedMatcher.new(self)
29
+ end
30
+ protected :negate
31
+
32
+ ##
33
+ # Combines with matcher to AnyMatcher
34
+ # @param matcher
35
+ # @return [AnyMatcher]
36
+ # @see MatcherBuilding#any
37
+ def +(matcher)
38
+ matcher = Matcher.cache(matcher)
39
+
40
+ matchers = if matcher.is_a?(AnyMatcher)
41
+ [self].concat(matcher.matchers)
42
+ else
43
+ [self, matcher]
44
+ end
45
+
46
+ AnyMatcher.new(matchers)
47
+ end
48
+
49
+ ##
50
+ # Combines with matcher to AllMatcher
51
+ # @param matcher
52
+ # @return [AllMatcher]
53
+ # @see MatcherBuilding#all
54
+ def *(matcher)
55
+ matcher = Matcher.cache(matcher)
56
+
57
+ matchers = if matcher.is_a?(AllMatcher)
58
+ [self].concat(matcher.matchers)
59
+ else
60
+ [self, matcher]
61
+ end
62
+
63
+ AllMatcher.new(matchers)
64
+ end
65
+
66
+ ##
67
+ # Combines with matcher to LazyAnyMatcher
68
+ # @param matcher
69
+ # @return [LazyAnyMatcher]
70
+ # @see MatcherBuilding#lazy_any
71
+ def |(matcher)
72
+ matcher = Matcher.cache(matcher)
73
+
74
+ matchers = if matcher.is_a?(LazyAnyMatcher)
75
+ [self].concat(matcher.matchers)
76
+ else
77
+ [self, matcher]
78
+ end
79
+
80
+ LazyAnyMatcher.new(matchers)
81
+ end
82
+
83
+ ##
84
+ # Combines with matcher to LazyAllMatcher
85
+ # @param matcher
86
+ # @return [LazyAllMatcher]
87
+ # @see MatcherBuilding#lazy_all
88
+ def &(matcher)
89
+ matcher = Matcher.cache(matcher)
90
+
91
+ matchers = if matcher.is_a?(LazyAllMatcher)
92
+ [self].concat(matcher.matchers)
93
+ else
94
+ [self, matcher]
95
+ end
96
+
97
+ LazyAllMatcher.new(matchers)
98
+ end
99
+
100
+ ##
101
+ # Implies another matcher
102
+ # @param matcher
103
+ # @return [ImplyMatcher]
104
+ # @see MatcherBuilding#imply
105
+ def >>(matcher)
106
+ ImplyMatcher.new(self, Matcher.cache(matcher))
107
+ end
108
+
109
+ StackData = Struct.new(:actual, :vals, :errors)
110
+
111
+ ##
112
+ # Returns +true+ if actual matches, +false+ otherwise
113
+ # @example
114
+ # Matcher.build { Integer }.match?(42) # => true
115
+ # @param actual the value to match against
116
+ # @param ** values
117
+ # @return [Boolean]
118
+ def match?(actual, **)
119
+ match_helper(true, actual:, **).valid?
120
+ end
121
+ alias === match?
122
+
123
+ ##
124
+ # Returns an error tree describing all mismatches
125
+ # @example
126
+ # errors = Matcher.build { Integer }.match("foo")
127
+ # puts Matcher::Reporter.report(errors)
128
+ # # > root: expected a kind of Integer but got "foo"
129
+ # @param actual the value to match against
130
+ # @param ** values
131
+ # @return [Error]
132
+ def match(actual, **)
133
+ match_helper(false, actual:, **)
134
+ end
135
+
136
+ def match_helper(boolean, **)
137
+ hash_stack = HashStack.new
138
+
139
+ invoke = lambda do |matcher, act = UNDEFINED, **kwargs|
140
+ state = State.new(hash_stack, boolean:)
141
+ kwargs[:actual] = act unless Matcher.undefined?(act)
142
+
143
+ hash_stack.push(kwargs)
144
+
145
+ catch(:mismatch) do
146
+ matcher.validate(state, &invoke)
147
+ end
148
+
149
+ hash_stack.pop(kwargs)
150
+
151
+ state.result
152
+ end
153
+
154
+ Matcher.with_session do
155
+ invoke.call(self, **)
156
+ end
157
+ end
158
+ private :match_helper
159
+
160
+ def validate(state)
161
+ raise NotImplementedError
162
+ end
163
+ protected :validate
164
+
165
+ def inspect
166
+ to_s
167
+ end
168
+
169
+ protected
170
+
171
+ ##
172
+ # Stores information for this matcher instance during match time.
173
+ def session(key = object_id)
174
+ Matcher.session[key] ||= {}
175
+ end
176
+
177
+ ##
178
+ # Stores information for this matcher class during match time.
179
+ def self.session
180
+ Matcher.session[self] ||= {}
181
+ end
182
+
183
+ ##
184
+ # Stores information for this matcher's class during match time.
185
+ def class_session
186
+ self.class.session
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Builder
5
+ include ExpressionBuilding
6
+ include MatcherBuilding
7
+
8
+ def initialize(outside, build_session: Matcher.build_session)
9
+ ExpressionBuilding.init(self, build_session)
10
+
11
+ @outside = outside
12
+ @matcher_cache = MatcherCache.current(build_session)
13
+ end
14
+
15
+ ##
16
+ # Turns a matchable value into a matcher
17
+ # @example
18
+ # of(String).class # => Matcher::KindOfMatcher
19
+ # of(String) & !_.empty? # matches non-empty strings
20
+ # @param value
21
+ # @return [Base]
22
+ def matcher_of(value)
23
+ Matcher.of(
24
+ value,
25
+ matcher_cache: @matcher_cache,
26
+ expression_cache: @expression_cache,
27
+ )
28
+ end
29
+ alias of matcher_of
30
+
31
+ ##
32
+ # Accesses the outside context within a build block
33
+ # @example
34
+ # # access instance variables or methods from the enclosing scope
35
+ # Matcher.build do
36
+ # [outside { @ivar }, outside.my_method]
37
+ # end
38
+ # @return [Object] the outside context
39
+ def outside(&)
40
+ if block_given?
41
+ @outside.instance_eval(&)
42
+ else
43
+ @outside
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Negates a matcher
49
+ # @example
50
+ # # matches anything except 1
51
+ # neg(1)
52
+ # # alternatively:
53
+ # ~equal(1)
54
+ # @param matcher
55
+ # @return [Base]
56
+ # @see Base#~
57
+ def neg(matcher)
58
+ ~matcher_of(matcher)
59
+ end
60
+
61
+ ##
62
+ # Matches given matcher but not +nil+
63
+ # @example
64
+ # present(my_value)
65
+ # @param matcher
66
+ # @return [AllMatcher]
67
+ def present(matcher)
68
+ AllMatcher.new([
69
+ ~equal(nil),
70
+ matcher_of(matcher),
71
+ ])
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Chain
5
+ include NoMatcher
6
+ include NoExpression
7
+ include NoKey
8
+
9
+ def initialize(negated: false, &block)
10
+ @block = block
11
+ @negated = negated
12
+ end
13
+
14
+ def ~
15
+ Chain.new(negated: !@negated, &@block)
16
+ end
17
+
18
+ ##
19
+ # Chains this with a matcher or another chain
20
+ #
21
+ # Many helpers return a Chain that accepts a child matcher via +^+.
22
+ # Chains can also be composed: +each ^ map(_.to_i) ^ (_ > 0)+.
23
+ # @example
24
+ # each ^ Integer
25
+ # map(_.to_i) ^ [1, 2]
26
+ # filter(_.odd?) ^ [1, 3, 5]
27
+ # @param operand matcher or chain
28
+ # @return [Base]
29
+ def ^(operand)
30
+ if !Recorder.recorder?(operand) && operand.is_a?(Chain)
31
+ Chain.new { @block.call(operand ^ _1) }
32
+ else
33
+ matcher = Matcher.cache(operand)
34
+ result = @block.call(matcher)
35
+ result = ~result if @negated
36
+ result
37
+ end
38
+ end
39
+
40
+ def optional(fallback = AlwaysMatcher.instance)
41
+ OptionalChain.new(self, fallback)
42
+ end
43
+ end
44
+
45
+ module MatcherBuilding
46
+ ##
47
+ # Reduces multiple chains to one
48
+ # @example
49
+ # chain(let(limit: 10), map(_.compact), filter(_.even?), _ < vars[:limit])
50
+ # # instead of
51
+ # let(limit: 10) ^ map(_.compact) ^ filter(_.even?) ^ (_ < vars[:limit])
52
+ # # which is equivalent to
53
+ # let({ limit: 10 }, map(_.compact, filter(_.even?, _ < vars[:limit])))
54
+ # @param *chains
55
+ # @return [Chain]
56
+ def chain(*chains)
57
+ chains.reduce(:^)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ module Debug
5
+ def self.enable
6
+ init(force: true)
7
+ end
8
+
9
+ DEBUGGERS = %w[/bin/irb: ruby-debug-ide].freeze
10
+
11
+ def self.init(force: false)
12
+ return if @initialized
13
+
14
+ main_caller = caller[-1]
15
+
16
+ return if !force && DEBUGGERS.none? { main_caller.include?(_1) }
17
+
18
+ Recorder.prepend(ExpressionRecorderDebug)
19
+ @initialized = true
20
+ end
21
+
22
+ def self.debugging?(last_caller)
23
+ %w[puts p].any? { last_caller.end_with?(":in `#{_1}'") } ||
24
+ last_caller.include?('ruby-debug-ide')
25
+ end
26
+ end
27
+
28
+ module ExpressionRecorderDebug
29
+ private
30
+
31
+ def method_missing(method, *args, **kwargs, &block)
32
+ if Debug.debugging?(caller[0])
33
+ return @expression.to_s if %i[to_s inspect].include?(method)
34
+
35
+ return Object.instance_method(method)
36
+ .bind_call(self, *args, **kwargs, &block)
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ def respond_to_missing?(method, _include_private = false)
43
+ return Object.instance_methods.include?(method) if Debug.debugging?(caller[0])
44
+
45
+ super
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class AndError < 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?(AndError)
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?(AndError) &&
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 AndError
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,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class BooleanCollector
5
+ def initialize
6
+ @result = true
7
+ @mode = :and
8
+ end
9
+
10
+ def or!
11
+ @mode = :or
12
+ self
13
+ end
14
+
15
+ def or?
16
+ @mode == :or
17
+ end
18
+
19
+ def and?
20
+ @mode == :and
21
+ end
22
+
23
+ def empty?
24
+ @result
25
+ end
26
+
27
+ INVALID_ERROR = ElementError.new('invalid')
28
+ private_constant :INVALID_ERROR
29
+
30
+ def error
31
+ @result ? EmptyError.instance : INVALID_ERROR
32
+ end
33
+
34
+ def <<(error)
35
+ if !error.is_a?(Error) || !error.valid?
36
+ @result = false
37
+ throw :mismatch if and?
38
+ end
39
+
40
+ self.error
41
+ end
42
+
43
+ def [](_key)
44
+ self
45
+ end
46
+
47
+ def clear
48
+ @result = true
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class ElementError < Error
5
+ attr_reader :message
6
+
7
+ def initialize(message)
8
+ super()
9
+
10
+ @message = message
11
+ end
12
+
13
+ def ==(other)
14
+ return true if equal?(other)
15
+
16
+ other.instance_of?(ElementError) &&
17
+ @message == other.message
18
+ end
19
+
20
+ def to_s
21
+ @message.inspect
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class EmptyError < Error
5
+ include Singleton
6
+
7
+ def &(other)
8
+ other
9
+ end
10
+
11
+ def |(other)
12
+ other
13
+ end
14
+
15
+ def valid?
16
+ true
17
+ end
18
+
19
+ def to_s
20
+ '<>'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Error
5
+ def &(other)
6
+ case other
7
+ when EmptyError
8
+ self
9
+ when AndError
10
+ AndError.new([self].concat(other.children))
11
+ else
12
+ AndError.new([self, other])
13
+ end
14
+ end
15
+
16
+ def |(other)
17
+ case other
18
+ when EmptyError
19
+ self
20
+ when OrError
21
+ OrError.new([self].concat(other.children))
22
+ else
23
+ OrError.new([self, other])
24
+ end
25
+ end
26
+
27
+ def valid?
28
+ false
29
+ end
30
+
31
+ def inspect
32
+ to_s
33
+ end
34
+
35
+ def report
36
+ Reporter.report(self)
37
+ end
38
+ end
39
+ end