summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES29
-rw-r--r--docs/whats_new_in_3_0_0.rst33
-rw-r--r--examples/left_recursion.py45
-rw-r--r--pyparsing/__init__.py2
-rw-r--r--pyparsing/core.py5
5 files changed, 110 insertions, 4 deletions
diff --git a/CHANGES b/CHANGES
index 504e44e..94307ac 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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.