Sé que esta pregunta tiene más de 4 años, pero creo que debería agregar una respuesta más detallada.
Los árboles de sintaxis abstracta se crean de manera diferente a otros árboles; La afirmación más cierta en este caso es que los nodos del árbol de sintaxis tienen una cantidad variable de nodos COMO SE NECESITA.
Un ejemplo son expresiones binarias como 1 + 2
Una expresión simple como esa crearía un nodo raíz único que contiene un nodo derecho e izquierdo que contiene los datos sobre los números.
En lenguaje C, se vería algo así como
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;
};
Tu pregunta también fue cómo atravesar? El desplazamiento en este caso se denomina Nodos visitantes . La visita a cada nodo requiere que use cada tipo de nodo para determinar cómo evaluar los datos de cada nodo de Sintaxis.
Aquí hay otro ejemplo de eso en C, donde simplemente imprimo el contenido de cada 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;
}
}
Observe cómo la función visita recursivamente cada nodo según el tipo de nodo con el que estamos tratando.
Agreguemos un ejemplo más complejo, una construcción de declaración if
! Recuerde que si las declaraciones también pueden tener una cláusula else opcional. Agreguemos la sentencia if-else a nuestra estructura de nodo original. Recuerde que si las declaraciones también pueden tener declaraciones if, entonces puede ocurrir un tipo de recursión dentro de nuestro sistema de nodos. Las demás instrucciones son opcionales, por lo que el campo elsestmt
puede ser NULL, que la función de visitante recursivo puede ignorar.
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;
};
de vuelta en nuestra función de impresión de visitantes de nodo llamada AST_PrintNode
, podemos acomodar la construcción AST de la declaración if
agregando este código 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;
¡Tan simple como eso! En conclusión, el árbol de sintaxis no es mucho más que un árbol de una unión etiquetada del árbol y sus propios datos.