test_nodes.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. # -*- coding: utf-8 -*-
  2. from unittest import SkipTest, TestCase
  3. from parsimonious import Grammar, NodeVisitor, VisitationError, rule
  4. from parsimonious.expressions import Literal
  5. from parsimonious.nodes import Node
  6. class HtmlFormatter(NodeVisitor):
  7. """Visitor that turns a parse tree into HTML fragments"""
  8. grammar = Grammar("""bold_open = '(('""") # just partial
  9. def visit_bold_open(self, node, visited_children):
  10. return '<b>'
  11. def visit_bold_close(self, node, visited_children):
  12. return '</b>'
  13. def visit_text(self, node, visited_children):
  14. """Return the text verbatim."""
  15. return node.text
  16. def visit_bold_text(self, node, visited_children):
  17. return ''.join(visited_children)
  18. class ExplosiveFormatter(NodeVisitor):
  19. """Visitor which raises exceptions"""
  20. def visit_boom(self, node, visited_children):
  21. raise ValueError
  22. class SimpleTests(TestCase):
  23. def test_visitor(self):
  24. """Assert a tree gets visited correctly."""
  25. grammar = Grammar(r'''
  26. bold_text = bold_open text bold_close
  27. text = ~'[a-zA-Z 0-9]*'
  28. bold_open = '(('
  29. bold_close = '))'
  30. ''')
  31. text = '((o hai))'
  32. tree = Node(grammar['bold_text'], text, 0, 9,
  33. [Node(grammar['bold_open'], text, 0, 2),
  34. Node(grammar['text'], text, 2, 7),
  35. Node(grammar['bold_close'], text, 7, 9)])
  36. self.assertEqual(grammar.parse(text), tree)
  37. result = HtmlFormatter().visit(tree)
  38. self.assertEqual(result, '<b>o hai</b>')
  39. def test_visitation_exception(self):
  40. self.assertRaises(VisitationError,
  41. ExplosiveFormatter().visit,
  42. Node(Literal(''), '', 0, 0))
  43. def test_str(self):
  44. """Test str and unicode of ``Node``."""
  45. n = Node(Literal('something', name='text'), 'o hai', 0, 5)
  46. good = '<Node called "text" matching "o hai">'
  47. self.assertEqual(str(n), good)
  48. def test_repr(self):
  49. """Test repr of ``Node``."""
  50. s = 'hai ö'
  51. boogie = 'böogie'
  52. n = Node(Literal(boogie), s, 0, 3, children=[
  53. Node(Literal(' '), s, 3, 4), Node(Literal('ö'), s, 4, 5)])
  54. self.assertEqual(repr(n),
  55. str("""s = {hai_o}\nNode({boogie}, s, 0, 3, children=[Node({space}, s, 3, 4), Node({o}, s, 4, 5)])""").format(
  56. hai_o=repr(s),
  57. boogie=repr(Literal(boogie)),
  58. space=repr(Literal(" ")),
  59. o=repr(Literal("ö")),
  60. )
  61. )
  62. def test_parse_shortcut(self):
  63. """Exercise the simple case in which the visitor takes care of parsing."""
  64. self.assertEqual(HtmlFormatter().parse('(('), '<b>')
  65. def test_match_shortcut(self):
  66. """Exercise the simple case in which the visitor takes care of matching."""
  67. self.assertEqual(HtmlFormatter().match('((other things'), '<b>')
  68. class CoupledFormatter(NodeVisitor):
  69. @rule('bold_open text bold_close')
  70. def visit_bold_text(self, node, visited_children):
  71. return ''.join(visited_children)
  72. @rule('"(("')
  73. def visit_bold_open(self, node, visited_children):
  74. return '<b>'
  75. @rule('"))"')
  76. def visit_bold_close(self, node, visited_children):
  77. return '</b>'
  78. @rule('~"[a-zA-Z 0-9]*"')
  79. def visit_text(self, node, visited_children):
  80. """Return the text verbatim."""
  81. return node.text
  82. class DecoratorTests(TestCase):
  83. def test_rule_decorator(self):
  84. """Make sure the @rule decorator works."""
  85. self.assertEqual(CoupledFormatter().parse('((hi))'), '<b>hi</b>')
  86. def test_rule_decorator_subclassing(self):
  87. """Make sure we can subclass and override visitor methods without blowing
  88. away the rules attached to them."""
  89. class OverridingFormatter(CoupledFormatter):
  90. def visit_text(self, node, visited_children):
  91. """Return the text capitalized."""
  92. return node.text.upper()
  93. @rule('"not used"')
  94. def visit_useless(self, node, visited_children):
  95. """Get in the way. Tempt the metaclass to pave over the
  96. superclass's grammar with a new one."""
  97. raise SkipTest("I haven't got around to making this work yet.")
  98. self.assertEqual(OverridingFormatter().parse('((hi))'), '<b>HI</b>')
  99. class PrimalScream(Exception):
  100. pass
  101. class SpecialCasesTests(TestCase):
  102. def test_unwrapped_exceptions(self):
  103. class Screamer(NodeVisitor):
  104. grammar = Grammar("""greeting = 'howdy'""")
  105. unwrapped_exceptions = (PrimalScream,)
  106. def visit_greeting(self, thing, visited_children):
  107. raise PrimalScream('This should percolate up!')
  108. self.assertRaises(PrimalScream, Screamer().parse, 'howdy')
  109. def test_node_inequality(self):
  110. node = Node(Literal('12345'), 'o hai', 0, 5)
  111. self.assertTrue(node != 5)
  112. self.assertTrue(node != None)
  113. self.assertTrue(node != Node(Literal('23456'), 'o hai', 0, 5))
  114. self.assertTrue(not (node != Node(Literal('12345'), 'o hai', 0, 5)))
  115. def test_generic_visit_NotImplementedError_unnamed_node(self):
  116. """
  117. Test that generic_visit provides informative error messages
  118. when visitors are not defined.
  119. Regression test for https://github.com/erikrose/parsimonious/issues/110
  120. """
  121. class MyVisitor(NodeVisitor):
  122. grammar = Grammar(r'''
  123. bar = "b" "a" "r"
  124. ''')
  125. unwrapped_exceptions = (NotImplementedError, )
  126. with self.assertRaises(NotImplementedError) as e:
  127. MyVisitor().parse('bar')
  128. self.assertIn("No visitor method was defined for this expression: 'b'", str(e.exception))
  129. def test_generic_visit_NotImplementedError_named_node(self):
  130. """
  131. Test that generic_visit provides informative error messages
  132. when visitors are not defined.
  133. """
  134. class MyVisitor(NodeVisitor):
  135. grammar = Grammar(r'''
  136. bar = myrule myrule myrule
  137. myrule = ~"[bar]"
  138. ''')
  139. unwrapped_exceptions = (NotImplementedError, )
  140. with self.assertRaises(NotImplementedError) as e:
  141. MyVisitor().parse('bar')
  142. self.assertIn("No visitor method was defined for this expression: myrule = ~'[bar]'", str(e.exception))