def _to_diagram_element( element: pyparsing.ParserElement, diagrams=None, vertical: typing.Union[int, bool] = 5, ) -> typing.Tuple[railroad.DiagramItem, typing.Dict[int, NamedDiagram]]: """ Recursively converts a PyParsing Element to a railroad Element :param vertical: Controls at what point we make a list of elements vertical. If this is an integer (the default), it sets the threshold of the number of items before we go vertical. If True, always go vertical, if False, never do so :returns: A tuple, where the first item is the converted version of the input element, and the second item is a list of extra diagrams that also need to be displayed in order to represent recursive grammars """ if diagrams is None: diagrams = {} else: # We don't want to be modifying the parent's version of the dict, although we do use it as a foundation diagrams = diagrams.copy() # Convert the nebulous list of child elements into a single list objects for easy use if hasattr(element, "exprs"): exprs = element.exprs elif hasattr(element, "expr"): exprs = [element.expr] else: exprs = [] name = get_name(element) if isinstance(element, pyparsing.Forward): # If we encounter a forward reference, we have to split the diagram in two and return a new diagram which # represents the forward reference on its own # Python's id() is used to provide a unique identifier for elements el_id = id(element) if el_id in diagrams: name = diagrams[el_id].name else: # If the Forward has no real name, we name it Group N to at least make it unique count = len(diagrams) + 1 name = get_name(element, "Group {}".format(count)) # We have to first put in a placeholder so that, if we encounter this element deeper down in the tree, # we won't have an infinite loop diagrams[el_id] = NamedDiagram(name=name, diagram=None) # At this point we create a new subdiagram, and add it to the dictionary of diagrams forward_element, forward_diagrams = _to_diagram_element( exprs[0], diagrams) diagram = railroad.Diagram(forward_element) diagrams.update(forward_diagrams) diagrams[el_id] = diagrams[el_id]._replace(diagram=diagram) diagram.format(20) # Here we just use the element's name as a placeholder for the recursive grammar which is defined separately ret = railroad.NonTerminal(text=name) else: # If we don't encounter a Forward, we can continue to recurse into the tree # Recursively convert child elements children = [] for expr in exprs: item, subdiagrams = _to_diagram_element(expr, diagrams) children.append(item) diagrams.update(subdiagrams) # Here we find the most relevant Railroad element for matching pyparsing Element if isinstance(element, pyparsing.And): if _should_vertical(vertical, len(children)): ret = railroad.Stack(*children) else: ret = railroad.Sequence(*children) elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)): if _should_vertical(vertical, len(children)): ret = railroad.HorizontalChoice(*children) else: ret = railroad.Choice(0, *children) elif isinstance(element, pyparsing.Optional): ret = railroad.Optional(children[0]) elif isinstance(element, pyparsing.OneOrMore): ret = railroad.OneOrMore(children[0]) elif isinstance(element, pyparsing.ZeroOrMore): ret = railroad.ZeroOrMore(children[0]) elif isinstance(element, pyparsing.Group): # Generally there isn't any merit in labelling a group as a group if it doesn't have a custom name ret = railroad.Group(children[0], label=get_name(element, "")) elif len(exprs) > 1: ret = railroad.Sequence(children[0]) elif len(exprs) > 0: ret = railroad.Group(children[0], label=name) else: ret = railroad.Terminal(name) return ret, diagrams
def __init__(self, *items): choice_item = railroad.Choice(len(items) - 1, *items) one_or_more_item = railroad.OneOrMore(item=choice_item) super().__init__(one_or_more_item, label=self.all_label)
class Parser: exit_cmd = rr.Terminal("exit") echo_cmd = rr.Sequence( "echo", rr.Choice(1, rr.NonTerminal("{path}"), rr.NonTerminal("{string}"), rr.NonTerminal("[variable_access]") ) ) pwd_cmd = rr.Terminal("pwd") showpath_cmd = rr.Terminal("showpath") addpath_cmd = rr.Sequence( "addpath", rr.NonTerminal("{path}"), ) delpath_cmd = rr.Terminal("delpath") internal_cmd = rr.Group( rr.Choice(0, echo_cmd, pwd_cmd, showpath_cmd, addpath_cmd, delpath_cmd, exit_cmd, ), "internal_cmd" ) ######################################################################## external_cmd = rr.Group( rr.Sequence( rr.MultipleChoice(0, "any", rr.NonTerminal("{path}"), rr.NonTerminal("{arg}"), rr.NonTerminal("[variable_access]"), ), # rr.Terminal("<<EOL>>") ), "external_cmd" ) ######################################################################## variables = rr.Choice(0, rr.Group( rr.Sequence( rr.NonTerminal("{id}"), rr.Terminal("="), rr.Choice(1, rr.Skip(), rr.NonTerminal("{word}"), rr.NonTerminal("{string}"), ) ), "variable_assign" ), rr.Group( rr.Sequence( "$", rr.NonTerminal("{id}"), ), "variable_access" ) ) ######################################################################## handlers = rr.Group( rr.Sequence( rr.Choice(0, rr.Sequence(">", rr.NonTerminal("{path}")), rr.Sequence(">>", rr.NonTerminal("{path}")), rr.Sequence("|", rr.NonTerminal("[CMD]")), rr.Sequence("&"), ) ), "handlers_cmd" ) ######################################################################## cmd = rr.Group( rr.Choice(0, rr.NonTerminal("[internal_cmd]"), rr.NonTerminal("[external_cmd]"), ), "CMD" ) shell = rr.Choice(1, rr.NonTerminal("[variables]"), rr.Sequence( cmd, handlers, ), ) ######################################################################## parser_SHELL = rr.Diagram( rr.Start(label="Parser"), rr.Choice(0, shell, rr.Comment("ERROR"), ) ) parser_VAR = rr.Diagram( rr.Start(type="complex", label="variables"), variables, rr.End(type="complex") ) parser_INT_CMD = rr.Diagram( rr.Start(type="complex", label="internal_cmd"), internal_cmd, rr.End(type="complex") ) parser_EXT_CMD = rr.Diagram( rr.Start(type="complex", label="external_cmd"), external_cmd, rr.End(type="complex") ) def create_diagram(self): generate_svg(self.parser_SHELL, "parser-SHELL") generate_svg(self.parser_VAR, "parser-VAR") generate_svg(self.parser_INT_CMD, "parser-INT-CMD") generate_svg(self.parser_EXT_CMD, "parser-EXT-CMD")