Ich glaube, ich verstehe das Ziel eines AST und habe schon einige Baumstrukturen gebaut, aber niemals einen AST. Ich bin größtenteils verwirrt, weil die Knoten aus Text und nicht aus Zahlen bestehen. Daher kann ich mir keine gute Möglichkeit vorstellen, ein Token / eine Zeichenfolge einzugeben, wenn ich Code analysiere.
Wenn ich zum Beispiel Diagramme von ASTs betrachtete, waren die Variable und ihr Wert Blattknoten mit einem Gleichheitszeichen. Das macht für mich durchaus Sinn, aber wie würde ich das umsetzen? Ich denke, ich kann es von Fall zu Fall tun, so dass ich, wenn ich auf ein "=" stoße, dieses als Knoten verwende und den vor dem "=" analysierten Wert als Blatt hinzufüge. Es scheint einfach falsch zu sein, weil ich wahrscheinlich, abhängig von der Syntax, Fälle für Unmengen von Dingen machen müsste.
Und dann bin ich auf ein anderes Problem gestoßen, wie wird der Baum überquert? Gehe ich den ganzen Weg die Höhe hinunter und gehe wieder einen Knoten hinauf, wenn ich unten bin, und mache das Gleiche für den Nachbarn?
Ich habe Unmengen von Diagrammen auf ASTs gesehen, aber ich konnte kein recht einfaches Beispiel im Code finden, was wahrscheinlich helfen würde.
quelle
Antworten:
Die kurze Antwort ist, dass Sie Stapel verwenden. Dies ist ein gutes Beispiel, aber ich werde es auf einen AST anwenden.
Zu Ihrer Information, dies ist der Shunting-Yard-Algorithmus von Edsger Dijkstra .
In diesem Fall verwende ich einen Operator- und einen Ausdrucksstapel. Da Zahlen in den meisten Sprachen als Ausdrücke betrachtet werden, verwende ich den Ausdrucksstapel, um sie zu speichern.
(Bitte seien Sie nett zu meinem Code. Ich weiß, dass er nicht robust ist; er soll nur Pseudocode sein.)
Wie Sie dem Code entnehmen können, können beliebige Ausdrücke Operanden für andere Ausdrücke sein. Wenn Sie die folgende Eingabe haben:
Der von mir geschriebene Code würde dieses AST erzeugen:
Wenn Sie dann den Code für diesen AST erstellen möchten, führen Sie eine Nachbestellungs-Baumdurchquerung durch . Wenn Sie einen Blattknoten (mit einer Zahl) besuchen, generieren Sie eine Konstante, da der Compiler die Operandenwerte kennen muss. Wenn Sie einen Knoten mit einem Operator besuchen, generieren Sie die entsprechende Anweisung vom Operator. Der Operator '+' gibt Ihnen beispielsweise eine Anweisung zum Hinzufügen.
quelle
Es gibt einen signifikanten Unterschied zwischen der Darstellung eines AST im Test (ein Baum mit Zahlen / Variablen an den Blattknoten und Symbolen an den inneren Knoten) und der tatsächlichen Implementierung.
Bei der typischen Implementierung eines AST (in einer OO-Sprache) wird Polymorphismus stark genutzt. Die Knoten im AST werden in der Regel mit einer Vielzahl von Klassen implementiert, die alle aus einer gemeinsamen Ableitung
ASTNode
Klasse. Für jedes syntaktisches Konstrukt in der Sprache verarbeiten, wird es eine Klasse für dieses sein Konstrukt in dem AST darstellt, wie beispielsweiseConstantNode
(für Konstanten, wie0x10
oder42
),VariableNode
(für Variablennamen),AssignmentNode
(für die Zuweisungsoperationen),ExpressionNode
(für generische Ausdrücke) usw.Jeder bestimmte Knotentyp gibt an, ob dieser Knoten untergeordnete Knoten hat, wie viele und möglicherweise von welchem Typ. Ein
ConstantNode
Testament hat normalerweise keine Kinder, einAssignmentNode
Testament hat zwei und ein TestamentExpressionBlockNode
kann eine beliebige Anzahl von Kindern haben.Der AST wird vom Parser erstellt, der weiß, welches Konstrukt er gerade analysiert hat, damit er die richtige Art von AST-Knoten erstellen kann.
Beim Überqueren des AST kommt der Polymorphismus der Knoten wirklich ins Spiel. Die Basis
ASTNode
definiert die Operationen, die an den Knoten ausgeführt werden können, und jeder spezifische Knotentyp implementiert diese Operationen auf die spezifische Weise für dieses spezielle Sprachkonstrukt.quelle
Das Erstellen des AST aus dem Quelltext ist "einfach" das Parsen . Wie genau es gemacht wird, hängt von der analysierten formalen Sprache und der Implementierung ab. Sie können Parser-Generatoren wie Menhir (für Ocaml) , GNU
bison
mitflex
oder ANTLR usw. usw. verwenden. Dies geschieht häufig "manuell" durch Codierung eines rekursiven Abstiegsparsers (siehe diese Antwort zur Erklärung der Gründe). Der Kontextaspekt des Parsens wird oft an anderer Stelle ausgeführt (Symboltabellen, Attribute, ....).In der Praxis sind AST jedoch weitaus komplexer als Sie glauben. In einem Compiler wie GCC speichert der AST beispielsweise Informationen zum Quellspeicherort und einige Tippinformationen. Lesen Sie mehr über generische Bäume in GCC und schauen Sie in die Datei gcc / tree.def . Übrigens, schauen Sie auch in GCC MELT (das ich entworfen und implementiert habe), es ist relevant für Ihre Frage.
quelle
--My comment #1 print("Hello, ".."world.")
verwandelt sich in "[{" type ":" - "," content ":" My comment # 1 "}, {" type ":" call "," name ":" print "," arguments ": [[{" type ":" str "," action ":" .. "," content ":" Hello, "}, {" type ":" str "," content ": "Welt." }]]}] `Ich denke, es ist in JS viel einfacher als in jeder anderen Sprache!Ich weiß, dass diese Frage mehr als 4 Jahre alt ist, aber ich denke, ich sollte eine detailliertere Antwort hinzufügen.
Abstrakte Syntaxbäume werden nicht anders als andere Bäume erstellt. Die wahrere Aussage in diesem Fall ist, dass Syntaxbaumknoten eine unterschiedliche Anzahl von Knoten haben, WIE ERFORDERLICH.
Ein Beispiel sind binäre Ausdrücke wie
1 + 2
Ein einfacher Ausdruck wie dieser würde einen einzelnen Wurzelknoten erzeugen, der einen rechten und einen linken Knoten enthält, der die Daten über die Zahlen enthält. In C würde es ungefähr so aussehenIhre Frage war auch, wie man überquert? Das Verfahren wird in diesem Fall als Besuchsknoten bezeichnet . Für den Besuch jedes Knotens müssen Sie für jeden Knotentyp festlegen, wie die Daten jedes Syntaxknotens ausgewertet werden sollen.
Hier ist ein weiteres Beispiel dafür in C, wo ich einfach den Inhalt jedes Knotens drucke:
Beachten Sie, wie die Funktion jeden Knoten rekursiv besucht, je nachdem, um welchen Knotentyp es sich handelt.
Fügen wir ein komplexeres Beispiel hinzu, ein
if
Anweisungskonstrukt! Denken Sie daran, dass if-Anweisungen auch eine optionale else-Klausel enthalten können. Fügen wir die if-else-Anweisung zu unserer ursprünglichen Knotenstruktur hinzu. Denken Sie daran, dass if-Anweisungen selbst auch if-Anweisungen haben können, so dass eine Art Rekursion innerhalb unseres Knotensystems auftreten kann. Andere Anweisungen sind optional, sodass daselsestmt
Feld NULL sein kann, was von der rekursiven Besucherfunktion ignoriert werden kann.Zurück in unserer aufgerufenen Node-Visitor-Print-Funktion
AST_PrintNode
können wir dieif
Anweisung AST-Konstrukt aufnehmen, indem wir diesen C-Code hinzufügen:So einfach ist das! Zusammenfassend ist der Syntaxbaum nicht viel mehr als ein Baum aus einer getaggten Vereinigung des Baums und seiner Daten selbst!
quelle