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 verdadera en este caso es que los nodos del árbol de sintaxis tienen una cantidad variada de nodos según sea necesario.
Un ejemplo son las expresiones binarias como 1 + 2
Una expresión simple como esa crearía un solo nodo raíz 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? Recorrer en este caso se llama Nodos de visita . Visitar 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 de acuerdo con el tipo de nodo con el que estamos tratando.
¡Agreguemos un ejemplo más complejo, una if
construcción de declaración! Recuerde que las declaraciones if también pueden tener una cláusula else opcional. Agreguemos la instrucción if-else a nuestra estructura de nodo original. Recuerde que las declaraciones if también pueden tener declaraciones if, por lo que puede ocurrir una especie de recursión dentro de nuestro sistema de nodos. Las otras declaraciones son opcionales, por lo que el elsestmt
campo puede ser NULL, lo que la función recursiva del visitante 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 nuevo en nuestra función de impresión de visitante de nodo llamada AST_PrintNode
, podemos acomodar la if
construcción AST de la declaración 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 sencillo 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 datos.