Sei que esta pergunta tem mais de 4 anos, mas acho que devo adicionar uma resposta mais detalhada.
Resumo Sintaxe Árvores não são criadas de maneira diferente de outras árvores; a afirmação mais verdadeira nesse caso é que os nós da árvore de sintaxe têm uma quantidade variável de nós, conforme necessário.
Um exemplo são expressões binárias como 1 + 2
Uma expressão simples como essa criaria um único nó raiz contendo um nó direito e esquerdo que contém os dados sobre os números. Na linguagem C, seria algo 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;
};
Sua pergunta também foi como atravessar? Atravessar neste caso é chamado de nós visitantes . Visitar cada nó requer que você use cada tipo de nó para determinar como avaliar os dados de cada nó da sintaxe.
Aqui está outro exemplo disso em C, onde simplesmente imprimo o conteúdo de cada nó:
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 como a função visita recursivamente cada nó de acordo com o tipo de nó com o qual estamos lidando.
Vamos adicionar um exemplo mais complexo, uma if
construção de instrução! Lembre-se de que as instruções if também podem ter uma cláusula else opcional. Vamos adicionar a instrução if-else à nossa estrutura de nós original. Lembre-se de que as próprias declarações if também podem ter if, para que ocorra um tipo de recursão dentro do nosso sistema de nós. As demais instruções são opcionais para que o elsestmt
campo possa ser NULL, que a função recursiva do visitante pode 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 volta à função de impressão do visitante do nó chamada AST_PrintNode
, podemos acomodar a if
construção AST da instrução adicionando 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;
Tão simples como isso! Em conclusão, a Árvore de Sintaxe não é muito mais que uma árvore de uma união marcada da árvore e seus próprios dados!