summaryrefslogtreecommitdiff
path: root/pyparsing
diff options
context:
space:
mode:
Diffstat (limited to 'pyparsing')
-rw-r--r--pyparsing/__init__.py3
-rw-r--r--pyparsing/core.py41
-rw-r--r--pyparsing/diagram/__init__.py29
-rw-r--r--pyparsing/helpers.py37
4 files changed, 83 insertions, 27 deletions
diff --git a/pyparsing/__init__.py b/pyparsing/__init__.py
index 1e5c625..816b0e5 100644
--- a/pyparsing/__init__.py
+++ b/pyparsing/__init__.py
@@ -103,7 +103,7 @@ __version__ = (
__version_info__.releaseLevel == "final"
]
)
-__versionTime__ = "25 October 2020 05:09 UTC"
+__versionTime__ = "2 November 2020 17:20 UTC"
__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
from .util import *
@@ -149,6 +149,7 @@ __all__ = [
"Forward",
"GoToColumn",
"Group",
+ "IndentedBlock",
"Keyword",
"LineEnd",
"LineStart",
diff --git a/pyparsing/core.py b/pyparsing/core.py
index c242d40..fed71a9 100644
--- a/pyparsing/core.py
+++ b/pyparsing/core.py
@@ -1793,6 +1793,26 @@ class ParserElement(ABC):
return success, allResults
+ def create_diagram(expr: "ParserElement", output_html, vertical=3, **kwargs):
+ """
+
+ """
+
+ try:
+ from .diagram import to_railroad, railroad_to_html
+ except ImportError as ie:
+ raise Exception(
+ "must install 'railroad' to generate parser railroad diagrams"
+ ) from ie
+
+ railroad = to_railroad(expr, vertical=vertical, diagram_kwargs=kwargs)
+ if isinstance(output_html, str):
+ with open(output_html, "w", encoding="utf-8") as diag_file:
+ diag_file.write(railroad_to_html(railroad))
+ else:
+ # we were passed a file-like object, just write to it
+ output_html.write(railroad_to_html(railroad))
+
class _PendingSkip(ParserElement):
# internal placeholder class to hold a place were '...' is added to a parser element,
@@ -3233,7 +3253,8 @@ class And(ParseExpression):
)
errorStop = False
for e in self.exprs[1:]:
- if isinstance(e, And._ErrorStop):
+ # if isinstance(e, And._ErrorStop):
+ if type(e) is And._ErrorStop:
errorStop = True
continue
if errorStop:
@@ -4541,16 +4562,30 @@ class Dict(TokenConverter):
for i, tok in enumerate(tokenlist):
if len(tok) == 0:
continue
+
ikey = tok[0]
if isinstance(ikey, int):
- ikey = str(tok[0]).strip()
+ ikey = str(ikey).strip()
+
if len(tok) == 1:
tokenlist[ikey] = _ParseResultsWithOffset("", i)
+
elif len(tok) == 2 and not isinstance(tok[1], ParseResults):
tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i)
+
else:
- dictvalue = tok.copy() # ParseResults(i)
+ try:
+ dictvalue = tok.copy() # ParseResults(i)
+ except Exception:
+ exc = TypeError(
+ "could not extract dict values from parsed results"
+ " - Dict expression must contain Grouped expressions"
+ )
+ exc.__cause__ = None
+ raise exc
+
del dictvalue[0]
+
if len(dictvalue) != 1 or (
isinstance(dictvalue, ParseResults) and dictvalue.haskeys()
):
diff --git a/pyparsing/diagram/__init__.py b/pyparsing/diagram/__init__.py
index 9678368..e10a50b 100644
--- a/pyparsing/diagram/__init__.py
+++ b/pyparsing/diagram/__init__.py
@@ -2,7 +2,6 @@ import railroad
import pyparsing
from pkg_resources import resource_filename
from typing import (
- Union,
List,
Optional,
NamedTuple,
@@ -104,12 +103,13 @@ def resolve_partial(partial: "EditablePartial[T]") -> T:
def to_railroad(
element: pyparsing.ParserElement,
diagram_kwargs: dict = {},
- vertical: Union[int, bool] = 5,
+ vertical: int = None,
) -> List[NamedDiagram]:
"""
Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram
creation if you want to access the Railroad tree before it is converted to HTML
:param diagram_kwargs: kwargs to pass to the Diagram() constructor
+ :param vertical: (optional)
"""
# Convert the whole tree underneath the root
lookup = ConverterState(diagram_kwargs=diagram_kwargs)
@@ -125,16 +125,14 @@ def to_railroad(
return sorted(resolved, key=lambda diag: diag.index)
-def _should_vertical(specification: Union[int, bool], count: int) -> bool:
+def _should_vertical(specification: int, count: int) -> bool:
"""
Returns true if we should return a vertical list of elements
"""
- if isinstance(specification, bool):
- return specification
- elif isinstance(specification, int):
- return count >= specification
+ if specification is None:
+ return False
else:
- raise Exception()
+ return count >= specification
class ElementState:
@@ -275,7 +273,7 @@ def _to_diagram_element(
element: pyparsing.ParserElement,
parent: Optional[EditablePartial],
lookup: ConverterState = None,
- vertical: Union[int, bool] = 3,
+ vertical: int = None,
index: int = 0,
name_hint: str = None,
) -> Optional[EditablePartial]:
@@ -424,16 +422,3 @@ def _to_diagram_element(
)
else:
return ret
-
-# monkeypatch .create_diagram method onto ParserElement
-def _create_diagram(expr: pyparsing.ParserElement, output_html):
- railroad = to_railroad(expr)
- if isinstance(output_html, str):
- with open(output_html, "w", encoding="utf-8") as diag_file:
- diag_file.write(railroad_to_html(railroad))
- else:
- # we were passed a file-like object, just write to it
- output_html.write(railroad_to_html(railroad))
-
-
-pyparsing.ParserElement.create_diagram = _create_diagram
diff --git a/pyparsing/helpers.py b/pyparsing/helpers.py
index d2325c9..cf59107 100644
--- a/pyparsing/helpers.py
+++ b/pyparsing/helpers.py
@@ -699,7 +699,9 @@ def infixNotation(baseExpr, opList, lpar=Suppress("("), rpar=Suppress(")")):
thisExpr = Forward().setName(termName)
if rightLeftAssoc is opAssoc.LEFT:
if arity == 1:
- matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr + opExpr[...])
+ matchExpr = _FB(lastExpr + opExpr) + Group(
+ lastExpr + opExpr + opExpr[...]
+ )
elif arity == 2:
if opExpr is not None:
matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group(
@@ -760,6 +762,9 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]
A valid block must contain at least one ``blockStatement``.
+ (Note that indentedBlock uses internal parse actions which make it
+ incompatible with packrat parsing.)
+
Example::
data = '''
@@ -881,6 +886,36 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]
return smExpr.setName("indented block")
+class IndentedBlock(ParseElementEnhance):
+ """
+ Expression to match one or more expressions at a given indentation level.
+ Useful for parsing text where structure is implied by indentation (like Python source code).
+ """
+
+ def __init__(self, expr, recursive=True):
+ super().__init__(expr, savelist=True)
+ self._recursive = recursive
+
+ def parseImpl(self, instring, loc, doActions=True):
+ # see if self.expr matches at the current location - if not it will raise an exception
+ # and no further work is necessary
+ self.expr.parseImpl(instring, loc, doActions)
+
+ indent_col = col(loc, instring)
+ peer_parse_action = matchOnlyAtCol(indent_col)
+ peer_expr = FollowedBy(self.expr).addParseAction(peer_parse_action)
+ inner_expr = Empty() + peer_expr.suppress() + self.expr
+
+ if self._recursive:
+ indent_parse_action = conditionAsParseAction(
+ lambda s, l, t, relative_to_col=indent_col: col(l, s) > relative_to_col
+ )
+ indent_expr = FollowedBy(self.expr).addParseAction(indent_parse_action)
+ inner_expr += Optional(indent_expr + self)
+
+ return OneOrMore(inner_expr).parseImpl(instring, loc, doActions)
+
+
# it's easy to get these comment structures wrong - they're very common, so may as well make them available
cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/").setName(
"C style comment"