Construyendo el compilador Parte VIII: Codigo intermedio de Expresiones y precedencia operadores
Codigo intermedio de expresiones
Hola!
Esta semana he estado trabajando en la "carnita" del proyecto
Primero que nada, algunas novedades interesantes. Empecemos
Precedencia de operadores
Edité mi gramática para que ahora acepte precedencia de operadores.
LASTIMOSAMENTE, la gramática de mi analizador semántico estaba basada en eso, por tanto, no puedo usarla para ambos programas (semantico e intermedio). Razon por la cual ahora voy a usar la gramática original para el semántico, y la nueva para el analisis de generación de código intermedio.
acá estan los cambios:
statement:
location assign_op expr SEMICOLON? # statement_assign
| method_call # statement_methodcall
| IF LROUND expr RROUND block (ELSE block)? # statement_if
| WHILE LROUND expr RROUND block # statement_while
| RETURN expr SEMICOLON # statement_return
| FOR var_id (EQUAL_OP int_literal)? COMMA (
(var_id (EQUAL_OP int_literal)?)
| int_literal
) block # statement_for
| BREAK SEMICOLON # statement_break;
method_call:
method_name LROUND (expr (COMMA expr)*)? RROUND (SEMICOLON)?;
expr:
literal # expr_literal
| location # expr_location
| method_call # expr_methodCall
| expr ('*' | '/' | '%') expr # expr_PrecedenciaMax
| expr ('+' | '-') expr # expr_PrecedenciaMenor
| expr (arith_op | rel_op | eq_op | cond_op) expr # expr_normal
| SUB expr # expr_menos
| NOT expr # expr_negacion
| LROUND expr RROUND # expr_parentesis;
Acá estan los cambios más notorios. Y son en la parte de EXPR. Para tener la precedencia, ahora usamos la parte de los # para poder acceder a las condiciones del nodo de forma más rápida y práctica.
Con eso en mente, y recordando que usamos listener, debemos de hacer los enter y exit para cada regla semántica que definimos en las producciones de nuestra gramática.
Traduciendo expresiones a código intermedio
Para esto, necesitamos una guia. Usaremos la siguiente:
Como vemos, cada produccion esta asociada a una regla semántica. Estas reglas, proveen la base o fundación de cada parte del código intermedio. La produccion más alta (S) provee de la concatenación del codigo de una expresion de la forma id=E.
Para esto, se usa un lenguaje de "nodos", donde cada produccion tiene un .codigo y un .direccion. La producción S, su código .codigo se genera a partir de la concatenación del E.codigo (su hijo del lado derecho del simbolo '=') y la generacion anterior de un código intermedio aunado al valor '=' y la direccion de E.direccion.
Este proceso es similar en todas las demás producciones. De forma que cuando tenemos expresiones como a = a + b, a = -b, c = (a) o a, las reglas generan código intermedio que al final termina por tener sentido.
Esta "carnita" se logra recorriendo el arbol mediante el listener que ANTLR4 provee, y guardano en un diccionario global, el contexto de cada nodo para luego usarlo más arriba.
Adjunto, algunos ejemplos de la generacion.
Primero un código base.
class Program{
void main(void)
{
int a;
int b;
int c;
c = b - a;
}
}
Ahora, esa resta queda como
t0 = MENOS fp[0]
fp[4] = t0
Otro ejemplo
class Program{
void main(void)
{
int a;
int b;
int c;
c = b + a;
}
}
t0 = fp[4] + fp[0]
fp[8] = t0
Ahora un ejemplo de cada caso
class Program{
void main(void)
{
int a;
int b;
int c;
c = b + a;
c = -b;
a = (b);
b = a * b;
b = a / b;
c = a % b;
}
}
Y el codigo generado es:
t0 = fp[4] + fp[0]
fp[8] = t0
t1 = MENOS fp[4]
fp[8] = t1
fp[0] = fp[4]
t2 = fp[0] * fp[4]
fp[4] = t2
t3 = fp[0] / fp[4]
fp[4] = t3
t4 = fp[0] % fp[4]
fp[8] = t4
Ahora, luego de esto ¿qué sigue?
Ahora falta linkear y hacer lo mismo, pero para métodos, ifs, whiles, estructuras, etc.
En el próximo blog, métodos o quizas if!
GRACIAS!
Comentarios
Publicar un comentario