def test_from_diff_multiple(self): self.assertEqual( matcher.MatchInfo.from_diff('metavar', 'a\nb\nc\n', '1\nb\n3\n'), matcher.MatchInfo( match.Match(), bindings={ 'metavar.0': matcher.BoundValue( match.SpanMatch( string='a\n', span=(0, 2), )), 'metavar.1': matcher.BoundValue( match.SpanMatch( string='c\n', span=(4, 6), )), }, replacements={ 'metavar.0': formatting.LiteralTemplate('1\n'), 'metavar.1': formatting.LiteralTemplate('3\n'), }, ), )
def test_multi_bind(self): self.assertEqual( base_matchers.AllOf(base_matchers.Bind('foo'), base_matchers.Bind('bar')).match( _FAKE_CONTEXT, 1), matcher.MatchInfo( match.ObjectMatch(1), { 'foo': matcher.BoundValue(match.ObjectMatch(1)), 'bar': matcher.BoundValue(match.ObjectMatch(1)), }))
def test_on_merge_differs(self): with self.assertRaises(matcher.MatchError): matcher.merge_bindings( { 'a': matcher.BoundValue(0, on_merge=matcher.BindMerge.KEEP_FIRST) }, { 'a': matcher.BoundValue(0, on_merge=matcher.BindMerge.KEEP_LAST) }, )
def test_on_conflict_differs(self): with self.assertRaises(matcher.MatchError): matcher.merge_bindings( { 'a': matcher.BoundValue(0, on_conflict=matcher.BindConflict.SKIP) }, { 'a': matcher.BoundValue(0, on_conflict=matcher.BindConflict.ERROR) }, )
def test_bind_2arg(self): self.assertEqual( base_matchers.Bind('foo', base_matchers.Anything()).match( _FAKE_CONTEXT, 1), matcher.MatchInfo( match.ObjectMatch(1), {'foo': matcher.BoundValue(match.ObjectMatch(1))}))
def test_match(self): container = [1] self.assertEqual( base_matchers.ItemsAre([base_matchers.Bind('a') ]).match(_FAKE_CONTEXT, container), matcher.MatchInfo(match.ObjectMatch(container), {'a': matcher.BoundValue(match.ObjectMatch(1))}))
def test_string(self): self.assertEqual( base_matchers.HasItem(1, base_matchers.Bind('a')).match( _FAKE_CONTEXT, 'xy'), matcher.MatchInfo( match.StringMatch('xy'), {'a': matcher.BoundValue(match.StringMatch('y'))}))
def _match(self, context, candidate): """Returns the submatcher's match, with a binding introduced by this Bind. Args: context: The match context. candidate: The candidate node to be matched. Returns: An extended :class:`~refex.python.matcher.MatchInfo` with the new binding specified in the constructor. Conflicts are merged according to ``on_conflict``. If there was no match, or on_conflict result in a skip, then this returns ``None``. See matcher.merge_bindings for more details. """ result = self._submatcher.match(context, candidate) if result is None: return None bindings = matcher.merge_bindings( result.bindings, { self.name: matcher.BoundValue(result.match, on_conflict=self._on_conflict, on_merge=self._on_merge) }) if bindings is None: return None return attr.evolve(result, bindings=bindings)
def test_contains_binds(self): items = [1, 2, 3] m = base_matchers.Contains(base_matchers.Bind('foo', 1)) expected = matcher.MatchInfo( match.ObjectMatch(items), {'foo': matcher.BoundValue(match.ObjectMatch(1))}) self.assertEqual(m.match(_FAKE_CONTEXT, items), expected)
def test_bindings(self): m = base_matchers.Once(base_matchers.Bind('foo')) self.assertEqual( m.match(_FAKE_CONTEXT.new(), 1), matcher.MatchInfo( match.ObjectMatch(1), {'foo': matcher.BoundValue(match.ObjectMatch(1))})) self.assertEqual(m.bind_variables, {'foo'})
def test_multi_bind_first(self): self.assertEqual( base_matchers.AnyOf(base_matchers.Bind('foo'), base_matchers.Bind('bar'), _NOTHING).match(_FAKE_CONTEXT, 1), matcher.MatchInfo( match.ObjectMatch(1), {'foo': matcher.BoundValue(match.ObjectMatch(1))}))
def test_negative_index(self): container = ['xyz'] self.assertEqual( base_matchers.HasItem(-1, base_matchers.Bind('a')).match( _FAKE_CONTEXT, container), matcher.MatchInfo( match.ObjectMatch(container), {'a': matcher.BoundValue(match.StringMatch('xyz'))}))
def _re_match_to_bindings(compiled_regex, text, m): """Converts an ``re.Match`` to a bindings dict.""" return { bind_name: matcher.BoundValue(match.SpanMatch.from_text(text, m.regs[match_index])) for bind_name, match_index in compiled_regex.groupindex.items() }
def test_error_after_skip(self): """ERROR should still raise even if it is processed after a skip.""" # This is a gnarly thing to test, because dicts aren't ordered. # Even if we used OrderedDict, ordered dict key views don't (and can't) # preserve order when they're intersected and so on. @attr.s() class OrderedKeyView(object): """Ordered fake key view for deterministic key iteration order.""" keys = attr.ib() def __sub__(self, other_ordered_key_view): if self.keys != other_ordered_key_view.keys: raise NotImplementedError( "Test ordered key view doesn't support this.") return frozenset() def __and__(self, other_ordered_key_view): # can't preserve order otherwise... if self.keys != other_ordered_key_view.keys: raise NotImplementedError("Can't preserve order.") return list(self.keys) @attr.s() class OrderedBindings(object): """OrderedDict wrapper that returns OrderedKeyView.""" _dict = attr.ib(converter=collections.OrderedDict) def __getitem__(self, v): return self._dict[v] def keys(self): return OrderedKeyView(self._dict) viewkeys = keys bad_bindings = [ ('a', matcher.BoundValue(0, matcher.BindConflict.SKIP)), ('b', matcher.BoundValue(0, matcher.BindConflict.ERROR)), ] for bindings in [bad_bindings, bad_bindings[::-1]]: with self.subTest(bindings=bindings): bindings = OrderedBindings(bindings) with self.assertRaises(matcher.MatchError): matcher.merge_bindings(bindings, bindings)
def test_multi_overlap(self): # TODO: it'd be nice to give a good error at some point, instead. self.assertEqual( base_matchers.AllOf(base_matchers.Bind('foo'), base_matchers.Bind('foo')).match( _FAKE_CONTEXT, 1), matcher.MatchInfo( match.ObjectMatch(1), {'foo': matcher.BoundValue(match.ObjectMatch(1))}))
def test_simple(self): for nonempty_container in (('x', 'y'), ['x', 'y'], {1: 'y'}): with self.subTest(nonempty_container=nonempty_container): self.assertEqual( base_matchers.HasItem(1, base_matchers.Bind('a')).match( _FAKE_CONTEXT, nonempty_container), matcher.MatchInfo( match.ObjectMatch(nonempty_container), {'a': matcher.BoundValue(match.StringMatch('y'))}))
def test_variable_name(self): parsed = matcher.parse_ast('3', '<string>') expr = parsed.tree.body[0].value expr_match = matcher.LexicalASTMatch(expr, parsed.text, expr.first_token, expr.last_token) self.assertEqual( syntax_matchers.ExprPattern('$name').match( matcher.MatchContext(parsed), expr), matcher.MatchInfo(expr_match, {'name': matcher.BoundValue(expr_match)}))
def test_from_diff_change(self): self.assertEqual( matcher.MatchInfo.from_diff('metavar', 'a\nb\n', 'a\nc\n'), matcher.MatchInfo( match.Match(), bindings={ 'metavar.0': matcher.BoundValue( match.SpanMatch( string='b\n', span=(2, 4), )) }, replacements={'metavar.0': formatting.LiteralTemplate('c\n')}, ), )
def test_matches(self): parsed = matcher.parse_ast('xy = 2', '<string>') matches = list( matcher.find_iter( base_matchers.MatchesRegex(r'^(?P<name>.)(.)$', base_matchers.Bind('inner')), parsed)) # There is only one AST node of length >= 2 (which the regex requires): xy. self.assertEqual(matches, [ matcher.MatchInfo( mock.ANY, { 'inner': mock.ANY, 'name': matcher.BoundValue(match.SpanMatch('x', (0, 1))), }) ]) [matchinfo] = matches self.assertEqual(matchinfo.match.span, (0, 2)) self.assertEqual(matchinfo.match, matchinfo.bindings['inner'].value)
def test_eq(self, m): self.assertEqual( base_matchers.Bind('a', m).match(_FAKE_CONTEXT, 3), matcher.MatchInfo(match.ObjectMatch(3), {'a': matcher.BoundValue(match.ObjectMatch(3))}))
def test_noconflict(self): bindings = {'a': matcher.BoundValue(0)} self.assertEqual(matcher.merge_bindings({}, bindings), bindings)
def test_skip(self): skip_bindings = {'a': matcher.BoundValue(0, matcher.BindConflict.SKIP)} self.assertIsNone(matcher.merge_bindings(skip_bindings, skip_bindings))
def test_error(self): error_bindings = { 'a': matcher.BoundValue(0, matcher.BindConflict.ERROR) } with self.assertRaises(matcher.MatchError): matcher.merge_bindings(error_bindings, error_bindings)