Nota: Quando usei "complexo" no título, quero dizer que a expressão possui muitos operadores e operandos. Não que a expressão em si seja complexa.
Recentemente, estive trabalhando em um compilador simples para montagem de x86-64. Eu terminei o front end principal do compilador - o lexer e o analisador - e agora sou capaz de gerar uma representação da Árvore de Sintaxe Abstrata do meu programa. E como meu idioma será digitado estaticamente, agora estou fazendo a próxima fase: verificar o código-fonte. No entanto, cheguei a um problema e não fui capaz de resolvê-lo razoavelmente.
Considere o seguinte exemplo:
O analisador do meu compilador leu esta linha de código:
int a = 1 + 2 - 3 * 4 - 5
E o converteu no seguinte AST:
=
/ \
a(int) \
-
/ \
- 5
/ \
+ *
/ \ / \
1 2 3 4
Agora ele deve digitar check the AST. começa pelo primeiro tipo, verificando o =
operador. Primeiro, verifica o lado esquerdo do operador. Ele vê que a variável a
é declarada como um número inteiro. Portanto, agora deve verificar se a expressão do lado direito é avaliada como um número inteiro.
Entendo como isso poderia ser feito se a expressão fosse apenas um único valor, como 1
ou 'a'
. Mas como isso seria feito para expressões com vários valores e operandos - uma expressão complexa - como a acima? Para determinar corretamente o valor da expressão, parece que o verificador de tipos precisaria executar a própria expressão e registrar o resultado. Mas isso obviamente parece derrotar o objetivo de separar as fases de compilação e execução.
A única outra maneira que imagino que isso possa ser feito é verificar recursivamente a folha de cada subexpressão no AST e verificar se todos os tipos de folha correspondem ao tipo de operador esperado. Então, começando com o =
operador, o verificador de tipos examinaria todos os AST do lado esquerdo e verificaria se as folhas são inteiras. Em seguida, repetiria isso para cada operador na subexpressão.
Eu tentei pesquisar o tópico na minha cópia de "O Livro do Dragão" , mas ele não parece entrar em muitos detalhes e simplesmente reitera o que eu já sei.
Qual é o método usual usado quando um compilador é do tipo que verifica expressões com muitos operadores e operandos? Algum dos métodos mencionados acima foi usado? Caso contrário, quais são os métodos e como exatamente eles funcionam?
double a = 7/2
tentaria interpretar o lado direito como duplo; portanto, tentaria interpretar numerador e denominador como duplo e convertê-los se necessário; como resultado a = 3.5
. O bottom-up executaria divisão inteira e converteria somente na última etapa (atribuição), portanto a = 3.0
.
int a = 1 + 2 - 3 * 4 - 5
, mas paraint a = 5 - ((4*3) - (1+2))