Skip to content

Commit

Permalink
Add parse() and match() convenience methods to NodeVisitor, and…
Browse files Browse the repository at this point in the history
… add the concept of a default grammar for a visitor.

This makes the common case of parsing a string and applying exactly one visitor to the AST shorter and simpler.

It is unlikely that a visitor would ever be used with more than one grammar, so I expect all visitors to have an easily chosen default grammar.
  • Loading branch information
erikrose committed Jul 10, 2014
1 parent b954768 commit c973cd4
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ Version History

0.6
* Improve exception message when you forget to declare a visitor method.
* Add ``parse()`` and ``match()`` convenience methods to ``NodeVisitor``.
This makes the common case of parsing a string and applying exactly one
visitor to the AST shorter and simpler.

0.5
.. warning::
Expand Down
44 changes: 43 additions & 1 deletion parsimonious/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ class NodeVisitor(object):
Heaven forbid you're making it into a string or something else.
"""
#: The grammar recommended for use with this visitor. If you populate this,
#: you will be able to call :meth:`NodeVisitor.parse()` as a shortcut.
grammar = None

# TODO: If we need to optimize this, we can go back to putting subclasses
# in charge of visiting children; they know when not to bother. Or we can
Expand Down Expand Up @@ -193,8 +196,47 @@ def generic_visit(self, node, visited_children):
raise NotImplementedError("No visitor method was defined for %s." %
node.expr_name)

# Convenience methods you can call from your own visitors:
# Convenience methods:

def parse(self, text, pos=0):
"""Parse some text with this Visitor's default grammar.
``SomeVisitor().parse('some_string')`` is a shortcut for
``SomeVisitor().visit(some_grammar.parse('some_string'))``.
"""
return self._parse_or_match(text, pos, 'parse')

def match(self, text, pos=0):
"""Parse some text with this Visitor's default grammar, but don't
insist on parsing all the way to the end.
``SomeVisitor().match('some_string')`` is a shortcut for
``SomeVisitor().visit(some_grammar.match('some_string'))``.
"""
return self._parse_or_match(text, pos, 'match')

# Internal convenience methods to help you write your own visitors:

def lift_child(self, node, (first_child,)):
"""Lift the sole child of ``node`` up to replace the node."""
return first_child

# Private methods:

def _parse_or_match(self, text, pos, method_name):
"""Execute a parse or match on the default grammar, followed by a
visitation.
Raise RuntimeError if there is no default grammar specified.
"""
if not self.grammar:
raise RuntimeError(
"The {cls}.{method}() shortcut won't work because {cls} was "
"never associated with a specific " "grammar. Fill out its "
"`grammar` attribute, and try again.".format(
cls=self.__class__.__name__,
method=method_name))
return self.visit(getattr(self.grammar, method_name)(text, pos=pos))
13 changes: 13 additions & 0 deletions parsimonious/tests/test_nodes.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from nose.tools import eq_, assert_raises

from parsimonious.grammar import Grammar
from parsimonious.nodes import Node, NodeVisitor, VisitationError


class HtmlFormatter(NodeVisitor):
"""Visitor that turns a parse tree into HTML fragments"""

grammar = Grammar("""bold_open = '(('""") # just partial

def visit_bold_open(self, node, visited_children):
return '<b>'

Expand Down Expand Up @@ -73,3 +76,13 @@ def test_repr():
n = Node(boogie, s, 0, 3, children=[
Node('', s, 3, 4), Node('', s, 4, 5)])
eq_(repr(n), """s = {hai_o}\nNode({boogie}, s, 0, 3, children=[Node('', s, 3, 4), Node('', s, 4, 5)])""".format(hai_o=repr(s), boogie=repr(boogie)))


def test_parse_shortcut():
"""Exercise the simple case in which the visitor takes care of parsing."""
eq_(HtmlFormatter().parse('(('), '<b>')


def test_match_shortcut():
"""Exercise the simple case in which the visitor takes care of matching."""
eq_(HtmlFormatter().match('((other things'), '<b>')

0 comments on commit c973cd4

Please sign in to comment.