diff options
-rw-r--r-- | CHANGES | 29 | ||||
-rw-r--r-- | docs/whats_new_in_3_0_0.rst | 33 | ||||
-rw-r--r-- | examples/left_recursion.py | 45 | ||||
-rw-r--r-- | pyparsing/__init__.py | 2 | ||||
-rw-r--r-- | pyparsing/core.py | 5 |
5 files changed, 110 insertions, 4 deletions
@@ -2,8 +2,33 @@ Change Log ========== -Version 3.0.0c1 - April, 2021 ------------------------------ +Version 3.0.0b3 - August, 2021 +------------------------------ +- HUGE NEW FEATURE - Support for left-recursive parsers! + Following the method used in Python's PEG parser, pyparsing now supports + left-recursive parsers when left recursion is enabled. + + import pyparsing as pp + pp.ParserElement.enableLeftRecursion() + + # a common left-recursion definition + # define a list of items as 'list + item | item' + # BNF: + # item_list := item_list item | item + # item := word of alphas + item_list = pp.Forward() + item = pp.Word(pp.alphas) + item_list <<= item_list + item | item + + item_list.runTests("""\ + To parse or not to parse that is the question + """) + Prints: + + ['To', 'parse', 'or', 'not', 'to', 'parse', 'that', 'is', 'the', 'question'] + + Great work contributed by Max Fischer! + - Removed internal comparison of results values against b"", which raised a BytesWarning when run with `python -bb`. Fixes issue #271 reported by Florian Bruhin, thank you! diff --git a/docs/whats_new_in_3_0_0.rst b/docs/whats_new_in_3_0_0.rst index a10f97e..609f029 100644 --- a/docs/whats_new_in_3_0_0.rst +++ b/docs/whats_new_in_3_0_0.rst @@ -40,6 +40,39 @@ generator for documenting pyparsing parsers. You need to install (Contributed by Michael Milton) +Support for left-recursive parsers +---------------------------------- +Another significant enhancement in 3.0 is support for left-recursive (LR) +parsers. Previously, given a left-recursive parser, pyparsing would +recurse repeatedly until hitting the Python recursion limit. Following +the methods of the Python PEG parser, pyparsing uses a variation of +packrat parsing to detect and handle left-recursion during parsing.:: + + import pyparsing as pp + pp.ParserElement.enableLeftRecursion() + + # a common left-recursion definition + # define a list of items as 'list + item | item' + # BNF: + # item_list := item_list item | item + # item := word of alphas + item_list = pp.Forward() + item = pp.Word(pp.alphas) + item_list <<= item_list + item | item + + item_list.runTests("""\ + To parse or not to parse that is the question + """) + +Prints:: + + ['To', 'parse', 'or', 'not', 'to', 'parse', 'that', 'is', 'the', 'question'] + +See more examples in left_recursion.py in the pyparsing examples directory. + +(Contributed by Max Fischer) + + Refactored/added diagnostic flags --------------------------------- Expanded ``__diag__`` and ``__compat__`` to actual classes instead of diff --git a/examples/left_recursion.py b/examples/left_recursion.py new file mode 100644 index 0000000..f3977dc --- /dev/null +++ b/examples/left_recursion.py @@ -0,0 +1,45 @@ +# +# left_recursion.py +# +# Example code illustrating use of left-recursion in Pyparsing. +# +import pyparsing as pp + +# comment out this line to see the effects without LR parsing enabled +pp.ParserElement.enableLeftRecursion() + +item_list = pp.Forward() + +# a common left-recursion definition +# define a list of items as 'list + item | item' +# BNF: +# item_list := item_list item | item +# item := word of alphas +item = pp.Word(pp.alphas) +item_list <<= item_list + item | item + +item_list.runTests("""\ + To parse or not to parse that is the question + """) + +# Define a parser for an expression that can be an identifier, a quoted string, or a +# function call that starts with an expression +# BNF: +# expr := function_call | name | string | '(' expr ')' +# function_call := expr '(' expr,... ')' +# name := Python identifier +# string := a quoted string +# from https://stackoverflow.com/questions/32809389/parse-python-code-using-pyparsing/32822575#32822575 + +LPAR, RPAR = map(pp.Suppress, "()") +expr = pp.Forward() +string = pp.quotedString +function_call = expr + pp.Group(LPAR + pp.Optional(pp.delimitedList(expr)) + RPAR) +name = pp.Word(pp.alphas + '_', pp.alphanums + '_') +# left recursion - call starts with an expr +expr <<= function_call | string | name | pp.Group(LPAR + expr + RPAR) + +expr.runTests("""\ + print("Hello, World!") + (lookup_function("fprintf"))(stderr, "Hello, World!") + """, fullDump=False) diff --git a/pyparsing/__init__.py b/pyparsing/__init__.py index eb23aec..e0f1a66 100644 --- a/pyparsing/__init__.py +++ b/pyparsing/__init__.py @@ -96,7 +96,7 @@ classes inherit from. Use the docstrings for examples of how to: from collections import namedtuple version_info = namedtuple("version_info", "major minor micro releaseLevel serial") -__version_info__ = version_info(3, 0, 0, "beta", 2) +__version_info__ = version_info(3, 0, 0, "beta", 3) __version__ = ( "{}.{}.{}".format(*__version_info__[:3]) + ("{}{}".format(__version_info__.releaseLevel[0], __version_info__.serial), "")[ diff --git a/pyparsing/core.py b/pyparsing/core.py index d5ee05f..a3a6c96 100644 --- a/pyparsing/core.py +++ b/pyparsing/core.py @@ -793,7 +793,7 @@ class ParserElement(ABC): ParserElement._parse = ParserElement._parseNoCache @staticmethod - def enable_left_recursion(cache_size_limit: Optional[int] = None, *, force=False): + def enableLeftRecursion(cache_size_limit: Optional[int] = None, *, force=False): """ Enables "bounded recursion" parsing, which allows for both direct and indirect left-recursion. During parsing, left-recursive :class:`Forward` elements are @@ -838,6 +838,9 @@ class ParserElement(ABC): raise NotImplementedError("Memo size of %s" % cache_size_limit) ParserElement._left_recursion_enabled = True + # PEP-8 synonym - harbinger of things to come + enable_left_recursion = enableLeftRecursion + @staticmethod def enablePackrat(cache_size_limit=128, *, force=False): """Enables "packrat" parsing, which adds memoizing to the parsing logic. |