Generador de Código de Tres Direcciones

Fase 4
Generador C3D

Escribe código en el editor y pulsa Analizar o Ejecutar para ver el C3D generado aquí.

¿Qué es el Código de Tres Direcciones?

El Código de Tres Direcciones (C3D) es una representación intermedia de un programa que vive entre el AST y el código máquina. Se llama "tres direcciones" porque cada instrucción tiene como máximo tres operandos: un resultado y dos fuentes.

Su rol en el pipeline de compilación es servir de capa de abstracción independiente de la arquitectura: no sabe nada de registros de CPU ni instrucciones específicas de x86 o ARM. Esto facilita la optimización y la generación posterior de código máquina real.

Código fuente
c = a + b * 5;
Código C3D
t0 = b * 5
t1 = a + t0
c = t1

Los Temporales

Los temporales (t0, t1, t2...) son variables sintéticas que el generador crea para guardar resultados intermedios de subexpresiones. No tienen nombre en el código fuente — son una invención del compilador.

No necesita temporal

Un Literal (5, "hola", true) o un Identificador (variable) se usan directamente — ya tienen un valor o una ubicación conocida.

// a y 5 se usan directo
t0 = a + 5
Sí necesita temporal

El resultado de cualquier Binario o Unario necesita un lugar donde guardarse antes de ser usado por la instrucción padre.

t0 = b * 5    // resultado binario
t1 = a + t0   // usa t0

Las Etiquetas

Las etiquetas (L0, L1, L2...) son puntos de destino para los saltos (goto). En C3D, las estructuras de control como if y while se implementan completamente con etiquetas y gotos — no existen como instrucciones nativas.

Patrón if/else
    if a < b goto L0
    goto L1
L0:
    // bloque then
    goto L2
L1:
    // bloque else
L2:
    // continúa
Patrón while
L0:
    if a < b goto L1
    goto L2
L1:
    // cuerpo
    goto L0
L2:
    // continúa

Reglas de Traducción por Nodo

El generador recorre el AST con el mismo patrón que el intérprete: un switch sobre el tipo de nodo. Cada tipo tiene su regla de traducción:

Literal

Se devuelve el valor directamente como string. No se crea temporal ni se emite instrucción — el valor se usa como operando directo en la instrucción padre.

// "42" se usa directo, no hay instrucción
t0 = x + 42
Identificador

Se devuelve el nombre de la variable directamente. No necesita temporal — ya tiene una ubicación en memoria.

// "x" e "y" se usan directo
t0 = x + y
Binario

Se genera código para el hijo izquierdo, luego el derecho. Se crea un nuevo temporal y se emite: temporal = izq OP der.

// Para: a + b * 5
t0 = b * 5     // hijo derecho primero
t1 = a + t0    // luego el padre
Unario

Se genera código para el operando. Se crea un temporal y se emite: temporal = OP operando.

t0 = -x
DeclVariable / Asignacion

Se genera código para la expresión del valor. Se emite una copia desde el lugar resultado hacia la variable.

t0 = a + b
x = t0
If (sin else)

Se crean 2 etiquetas (L_true, L_false). Se emite goto condicional a L_true, goto incondicional a L_false, L_true: cuerpo, L_false: fin.

If (con else)

Se crean 3 etiquetas (L_true, L_false, L_fin). La rama then termina con goto L_fin para saltar el else.

While

Se crean 3 etiquetas (L_inicio, L_cuerpo, L_fin). Al final del cuerpo se emite goto L_inicio para el loop-back.

For

Equivale a un while con inicializador y actualización explícita. Se descompone en: copia inicial + patrón while + copia actualización antes del loop-back.

Llamada

Se emite "param arg" por cada argumento en orden, luego se emite "call nombre, N". Si se necesita el valor de retorno, se captura en un temporal.

param x
param 5
t0 = call suma, 2
Return

Se genera código para la expresión de retorno y se emite "return lugar". Sin valor, se emite "return" solo.

t0 = a + b
return t0
DeclFuncion

Se emite "func nombre:" (prólogo), luego el cuerpo de la función, luego "end_func nombre" (epílogo).

func suma:
    // cuerpo
end_func suma

Eficiencia y Precedencia

Una ventaja clave del generador C3D sobre generar código manualmente es que la precedencia de operadores ya está garantizada por el AST.

Cuando el parser construyó el AST de a + b * 5, colocó el nodo *más profundo (como hijo del nodo +). Al recorrer el árbol de forma recursiva bottom-up, el generador procesa b * 5 primero — obteniendo t0 = b * 5 — y solo después procesa el + usando t0. No hay que preocuparse por paréntesis ni jerarquías explícitas.

Árbol AST
      +
     / \
    a   *
       / \
      b   5
Traversal bottom-up
// Procesa * primero (más profundo)
t0 = b * 5
// Luego procesa + con t0
t1 = a + t0