Lambda ist der Parser-Generator der Intersult.

Anwendung#

Einige Anwendungen können nicht in Java oder XML geschrieben werden, es wäre geschickter wenn man eine eigene Sprache hätte. Lambda vereinfacht die Implementierung einer Sprache extrem, zu wenigen Zeilen Code. Der Parser erzeugt einen Abstract-Syntax-Tree, also eine Baumstruktur mit den im Eingabestring gefundenen Elementen.

Der Parser enthält bereits eine Reihe von Parsern für bestimmte Ausdrücke, die durch eine Metasprache angewendet werden können.

Metasprache#

Diese Metasprache (auch Parser-Parser) ist eine Sprache zur Erzeugung von Sprachen und an die Backus-Naur-Form angelehnt.

Die grundlegenden Elemente sind:

SymbolParserBeschreibung
"quote-literalEin String (auch Literal) kann von zwei Quotes '"' (Anrührungsstrichen) umschlossen sein.
'apos-literalEin String kann von zwei Apos "'" (Apostrophenzeichen) umschlossen sein.
| choice-expressionDefiniert eine Entscheidung dass entweder der linke oder der rechte Teil neben dem Symbol verwendet werden darf.
+sequence-expressionLegt fest, dass zuerst der linke Teil und dann der rechte Teil neben dem Symbol kommen muss.
#delimiter-repetition-parserDrückt aus dass der linke Teil beliebig oft wiederholt werden darf, wenn sich zwischen den Wiederholungen jeweils der rechte Teil befindet.
!simple-repetition-parserDrückt aus, dass der linke Teil beliebig oft wiederholt werden darf.
.terminal-parser-expressionZeigt an, dass der linke Teil am Ende der Eingabe stehen muss.
;parser-setTrennt zwei Parser-Definitionen voneinander ab.
:=parser-definitionOrdnet dem linken Teil "Parsernamen" die Definition auf der rechten Seite zu.

Ergebnis#

Das Ergebnis ist ein Abstract-Syntax-Tree mit Nodes vom Typ ParseNode. Die Klasse ParseNode ist abstrakt und hat folgende Methoden:
  • parser: Gibt den Parser zurück, der diese Node geparsed hat.
  • length: Liefert die Anzahl der Zeichen des von diesem Parser akzeptierten Ausdrucks.
  • value: Gibt den Ausdruck zurück, der von diesem Parser akzeptiert wurde.

ParseNode hat zwei Implementierungen:

  • Terminal: Zeigt an, dass es sich um eine Zeichenfolge handelt, die nicht weiter herunter gebrochen werden kann.
  • NonTerminal: Wurde von einem Parser erzeugt, der sich selbst aus anderen Parsern zusammensetzt (Operator ':='). NonTerminal enthält zwei weitere Methoden:
    • count: Anzahl der untergeordneten ParseNode-Elemente.
    • get: Liefert die ParseNode einer bestimmten Position zurück. Die Position n liegt dabei zwischen 0 <= n < count.

Beispiel#

    Parsers parsers = Parsers.getGlobal().clone();
    parsers.create("test := operation-low + eof;");
    parsers.create("bracket-expression := '(' + operation-low + ')';");
    parsers.create("operand-high := symbol | bracket-expression;");");
    parsers.create("operation-high := operand-high #1 ('*'| '/');");
    parsers.create("operand-low := operation-high | symbol;");
    parsers.create("operation-low := operand-low #1 ('+' | '-')");)";

Die Anwendung sieht dann so aus:

    Scanner scanner = new Scanner("(x+y)*(a+b)+z");
    ParseNode tree = parsers.parse("test", scanner);
    System.out.println(tree);

Das Ergebnis ist ein Abstract-Syntax-Tree (oft als AST bezeichnet) mit der Wurzel bei der lokalen Variable tree vom Typ ParseNode.

ParseNode hat zu Debug-Zwecken eine toString-Methode, die folgendes Ergebnis liefert:

test(
    operation-low(
        operand-low(
            operation-high(
                operand-high(
                    bracket-expression(
                        "(",
                        operation-low(
                            operand-low(
                                operation-high(
                                    operand-high("x")
                                )
                            ),
                            "+",
                            operand-low(
                                operation-high(
                                    operand-high("y")
                                )
                            )
                        ),
                        ")"
                    )
                ),
                "*",
                operand-high(
                    bracket-expression(
                        "(",
                        operation-low(
                            operand-low(
                                operation-high(
                                    operand-high("a")
                                )
                            ),
                            "+",
                            operand-low(
                                operation-high(
                                    operand-high("b")
                                )
                            )
                        ),
                        ")"
                    )
                )
            )
        ),
        "+",
        operand-low(
            operation-high(
                operand-high("z")
            )
        )
    ),
    "eof"
)

Eigene Parser-Klassen#

Parser können nicht nur über die Metasprache, sondern auch über Java-Klassen erzeugt werden. In der Praxis braucht man das nur, falls die Metasprache erweitert werden soll.
    new ChoiceParser(parsers, "sequence-element", "repetition-parser", "sequence-expression", "parser-element");