Exemple #1
0
def parse(string):
    '''
    Parses a DSL for railroad diagrams,
    based on significant whitespace.
    Each command must be on its own line,
    and is written like "Sequence:\n".
    Children are indented on following lines.
    Some commands have non-child arguments;
    these are put on the same line as the command name,
    like:
        Choice: 0
            Terminal: foo
            Terminal: bar
    '''
    import re

    lines = string.splitlines()

    # Strip off any common initial whitespace from lines.
    initialIndent = re.match(r"(\s*)", lines[0]).group(1)
    for i, line in enumerate(lines):
        if line.startswith(initialIndent):
            lines[i] = line[len(initialIndent):]
        else:
            die(
                "Inconsistent indentation: line {0} is indented less than the first line.",
                i)
            return rr.Diagram()

    # Determine subsequent indentation
    for line in lines:
        match = re.match(r"(\s+)", line)
        if match:
            indentText = match.group(1)
            break
    else:
        indentText = "\t"

    # Turn lines into tree
    lastIndent = 0
    tree = {"command": "Diagram", "prelude": "", "children": []}
    activeCommands = {"0": tree}
    for i, line in enumerate(lines):
        indent = 0
        while line.startswith(indentText):
            indent += 1
            line = line[len(indentText):]
        if indent > lastIndent + 1:
            die(
                "Line {0} jumps more than 1 indent level from the previous line:\n{1}",
                i, line.strip())
            return rr.Diagram()
        lastIndent = indent
        match = re.match(r"\s*([\w]+)\s*:\s*(.*)", line)
        if not match:
            die(
                "Line {0} doesn't match the grammar 'Command: optional-prelude'. Got:\n{1}",
                i, line.strip())
            return rr.Diagram()
        command = match.group(1)
        prelude = match.group(2).strip()

        node = {
            "command": command,
            "prelude": prelude,
            "children": [],
            "line": i
        }
        activeCommands[str(indent)]['children'].append(node)
        activeCommands[str(indent + 1)] = node

    return _createDiagram(**tree)
Exemple #2
0
def _createDiagram(command, prelude, children, line=-1):
    '''
    From a tree of commands,
    create an actual Diagram class.
    Each command must be {command, prelude, children}
    '''
    if command == "Diagram":
        children = filter(None,
                          [_createDiagram(**child) for child in children])
        return rr.Diagram(*children)
    elif command in ("T", "Terminal"):
        if children:
            return die("Line {0} - Terminal commands cannot have children.",
                       line)
        return rr.Terminal(prelude)
    elif command in ("N", "NonTerminal"):
        if children:
            return die("Line {0} - NonTerminal commands cannot have children.",
                       line)
        return rr.NonTerminal(prelude)
    elif command in ("C", "Comment"):
        if children:
            return die("Line {0} - Comment commands cannot have children.",
                       line)
        return rr.Comment(prelude)
    elif command in ("S", "Skip"):
        if children:
            return die("Line {0} - Skip commands cannot have children.", line)
        if prelude:
            return die("Line {0} - Skip commands cannot have preludes.", line)
        return rr.Skip()
    elif command in ("And", "Seq", "Sequence"):
        if prelude:
            return die("Line {0} - Sequence commands cannot have preludes.",
                       line)
        if not children:
            return die("Line {0} - Sequence commands need at least one child.",
                       line)
        children = filter(None,
                          [_createDiagram(**child) for child in children])
        return rr.Sequence(*children)
    elif command in ("Or", "Choice"):
        if prelude == "":
            default = 0
        else:
            try:
                default = int(prelude)
            except:
                die("Line {0} - Choice preludes must be an integer. Got:\n{1}",
                    line, prelude)
                default = 0
        if not children:
            return die("Line {0} - Choice commands need at least one child.",
                       line)
        children = filter(None,
                          [_createDiagram(**child) for child in children])
        return rr.Choice(default, *children)
    elif command in ("Opt", "Optional"):
        if prelude not in ("", "skip"):
            return die(
                "Line {0} - Optional preludes must be nothing or 'skip'. Got:\n{1}",
                line, prelude)
        if len(children) != 1:
            return die("Line {0} - Optional commands need exactly one child.",
                       line)
        children = filter(None,
                          [_createDiagram(**child) for child in children])
        return rr.Optional(*children, skip=(prelude == "skip"))
    elif command in ("Plus", "OneOrMore"):
        if prelude:
            return die("Line {0} - OneOrMore commands cannot have preludes.",
                       line)
        if 0 == len(children) > 2:
            return die(
                "Line {0} - OneOrMore commands must have one or two children.",
                line)
        children = filter(None,
                          [_createDiagram(**child) for child in children])
        return rr.OneOrMore(*children)
    elif command in ("Star", "ZeroOrMore"):
        if prelude:
            return die("Line {0} - ZeroOrMore commands cannot have preludes.",
                       line)
        if 0 == len(children) > 2:
            return die(
                "Line {0} - ZeroOrMore commands must have one or two children.",
                line)
        children = filter(None,
                          [_createDiagram(**child) for child in children])
        return rr.ZeroOrMore(*children)
    else:
        return die("Line {0} - Unknown command '{1}'.", line, command)
Exemple #3
0
def parse(string):
    '''
    Parses a DSL for railroad diagrams,
    based on significant whitespace.
    Each command must be on its own line,
    and is written like "Sequence:\n".
    Children are indented on following lines.
    Some commands have non-child arguments;
    for block commands they are put on the same line, after the :,
    for text commands they are put on the same line *before* the :,
    like:
        Choice: 0
            Terminal: foo
            Terminal raw: bar
    '''
    import re

    lines = string.splitlines()

    # Strip off any common initial whitespace from lines.
    initialIndent = re.match(r"(\s*)", lines[0]).group(1)
    for i, line in enumerate(lines):
        if line.startswith(initialIndent):
            lines[i] = line[len(initialIndent):]
        else:
            die(
                "Inconsistent indentation: line {0} is indented less than the first line.",
                i)
            return rr.Diagram()

    # Determine subsequent indentation
    for line in lines:
        match = re.match(r"(\s+)", line)
        if match:
            indentText = match.group(1)
            break
    else:
        indentText = "\t"

    # Turn lines into tree
    lastIndent = 0
    tree = {"command": "Diagram", "prelude": "", "children": []}
    activeCommands = {"0": tree}
    blockNames = "And|Seq|Sequence|Stack|Or|Choice|Opt|Optional|Plus|OneOrMore|Star|ZeroOrMore"
    textNames = "T|Terminal|N|NonTerminal|C|Comment|S|Skip"
    for i, line in enumerate(lines):
        indent = 0
        while line.startswith(indentText):
            indent += 1
            line = line[len(indentText):]
        if indent > lastIndent + 1:
            die(
                "Line {0} jumps more than 1 indent level from the previous line:\n{1}",
                i, line.strip())
            return rr.Diagram()
        lastIndent = indent
        if re.match(r"\s*({0})\W".format(blockNames), line):
            match = re.match(r"\s*(\w+)\s*:\s*(.*)", line)
            if not match:
                die(
                    "Line {0} doesn't match the grammar 'Command: optional-prelude'. Got:\n{1}",
                    i, line.strip())
                return rr.Diagram()
            command = match.group(1)
            prelude = match.group(2).strip()
            node = {
                "command": command,
                "prelude": prelude,
                "children": [],
                "line": i
            }
        elif re.match(r"\s*({0})\W".format(textNames), line):
            match = re.match(r"\s*(\w+)(\s[\w\s]+)?:\s*(.*)", line)
            if not match:
                die(
                    "Line {0} doesn't match the grammar 'Command [optional prelude]: text'. Got:\n{1},",
                    i, line.strip())
                return rr.Diagram()
            command = match.group(1)
            if match.group(2):
                prelude = match.group(2).strip()
            else:
                prelude = ""
            text = match.group(3).strip()
            node = {
                "command": command,
                "prelude": prelude,
                "text": text,
                "children": [],
                "line": i
            }

        activeCommands[str(indent)]['children'].append(node)
        activeCommands[str(indent + 1)] = node

    return _createDiagram(**tree)