So che questa domanda ha più di 4 anni, ma sento che dovrei aggiungere una risposta più dettagliata.
Gli alberi di sintassi astratti non sono creati diversamente dagli altri alberi; l'affermazione più vera in questo caso è che i nodi dell'albero di sintassi hanno una quantità variabile di nodi COME NECESSARIO.
Un esempio sono le espressioni binarie come 1 + 2
Una semplice espressione del genere creerebbe un singolo nodo radice contenente un nodo destro e sinistro che contiene i dati sui numeri. In linguaggio C, sarebbe simile
struct ASTNode;
union SyntaxNode {
int64_t llVal;
uint64_t ullVal;
struct {
struct ASTNode *left, *right;
} BinaryExpr;
};
enum SyntaxNodeType {
AST_IntVal, AST_Add, AST_Sub, AST_Mul, AST_Div, AST_Mod,
};
struct ASTNode {
union SyntaxNode *Data;
enum SyntaxNodeType Type;
};
La tua domanda era anche come attraversare? La traversata in questo caso si chiama Visiting Nodes . Per visitare ciascun nodo è necessario utilizzare ciascun tipo di nodo per determinare come valutare i dati di ciascun nodo di sintassi.
Ecco un altro esempio di quello in C in cui stampo semplicemente il contenuto di ciascun nodo:
void AST_PrintNode(const ASTNode *node)
{
if( !node )
return;
char *opername = NULL;
switch( node->Type ) {
case AST_IntVal:
printf("AST Integer Literal - %lli\n", node->Data->llVal);
break;
case AST_Add:
if( !opername )
opername = "+";
case AST_Sub:
if( !opername )
opername = "-";
case AST_Mul:
if( !opername )
opername = "*";
case AST_Div:
if( !opername )
opername = "/";
case AST_Mod:
if( !opername )
opername = "%";
printf("AST Binary Expr - Oper: \'%s\' Left:\'%p\' | Right:\'%p\'\n", opername, node->Data->BinaryExpr.left, node->Data->BinaryExpr.right);
AST_PrintNode(node->Data->BinaryExpr.left); // NOTE: Recursively Visit each node.
AST_PrintNode(node->Data->BinaryExpr.right);
break;
}
}
Nota come la funzione visita ricorsivamente ogni nodo in base al tipo di nodo con cui abbiamo a che fare.
Aggiungiamo un esempio più complesso, un if
costrutto di istruzione! Ricordiamo che se le istruzioni possono anche avere una clausola else facoltativa. Aggiungiamo l'istruzione if-else alla nostra struttura di nodi originale. Ricorda che se le istruzioni stesse possono avere anche istruzioni if, può verificarsi una sorta di ricorsione all'interno del nostro sistema di nodi. Le altre dichiarazioni sono facoltative, quindi il elsestmt
campo può essere NULL che la funzione visitatore ricorsivo può ignorare.
struct ASTNode;
union SyntaxNode {
int64_t llVal;
uint64_t ullVal;
struct {
struct ASTNode *left, *right;
} BinaryExpr;
struct {
struct ASTNode *expr, *stmt, *elsestmt;
} IfStmt;
};
enum SyntaxNodeType {
AST_IntVal, AST_Add, AST_Sub, AST_Mul, AST_Div, AST_Mod, AST_IfStmt, AST_ElseStmt, AST_Stmt
};
struct ASTNode {
union SyntaxNode *Data;
enum SyntaxNodeType Type;
};
tornando alla nostra funzione di stampa del visitatore del nodo chiamata AST_PrintNode
, possiamo accogliere l' if
istruzione AST build aggiungendo questo codice C:
case AST_IfStmt:
puts("AST If Statement\n");
AST_PrintNode(node->Data->IfStmt.expr);
AST_PrintNode(node->Data->IfStmt.stmt);
AST_PrintNode(node->Data->IfStmt.elsestmt);
break;
Così semplice! In conclusione, l'albero della sintassi non è molto più di un albero di un'unione etichettata dell'albero e dei suoi stessi dati!