diff options
Diffstat (limited to 'pyparsing')
-rw-r--r-- | pyparsing/__init__.py | 3 | ||||
-rw-r--r-- | pyparsing/core.py | 41 | ||||
-rw-r--r-- | pyparsing/diagram/__init__.py | 29 | ||||
-rw-r--r-- | pyparsing/helpers.py | 37 |
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" |