Cómo funciona Caju
De un archivo .caju a un programa en ejecución: las etapas del compilador, cómo se guardan los valores en memoria, el recolector de basura, las llamadas y los panics, y cómo el compilador acaba ejecutándose en tu navegador. Todo lo que se muestra aquí sale del compilador real.
Del código fuente al ejecutable
El compilador es una cadena de etapas, cada una escrita en Caju (compiler/*.caju). Sigue un programa pequeño a través de ellas: elige una etapa para ver lo que produce.
Analizador léxico
lexer.caju corta el texto en tokens: palabras clave, nombres, operadores, números, textos. Cada token guarda su línea y su columna, y así cada error posterior señala el lugar correcto. Los saltos de línea se convierten en tokens NEWLINE: terminan las sentencias.
fn add(a: int, b: int) -> int { return a + b} print(add(2, 3))- fnKEYWORD 1:1
- addNAME 1:4
- (OP 1:7
- aNAME 1:8
- :OP 1:9
- intNAME 1:11
- ,OP 1:14
- bNAME 1:16
- :OP 1:17
- intNAME 1:19
- )OP 1:22
- ->OP 1:24
- intNAME 1:27
- {OP 1:31
- ↵NEWLINE 1:32
- returnKEYWORD 2:3
- aNAME 2:10
- +OP 2:12
- bNAME 2:14
- ↵NEWLINE 2:15
- }OP 3:1
- ↵NEWLINE 3:2
- printNAME 5:1
- (OP 5:6
- addNAME 5:7
- (OP 5:10
- 2NUMBER 5:11
- ,OP 5:12
- 3NUMBER 5:14
- )OP 5:15
- )OP 5:16
- ↵NEWLINE 5:17
- ∎EOF 6:1
Un compilador que se compila a sí mismo
El compilador de Caju está escrito en Caju. Para obtener el primero, se usa una vez un pequeño compilador escrito en JavaScript; a partir de ahí, el compilador compila su propio código fuente.
- 1
Etapa 0
bootstrap/*.js: el compilador original, en JavaScript, ejecutado con Node. - 2
Etapa 1
La etapa 0 compila
compiler/main.caju: uncajunativo, hecho por el compilador de JavaScript. - 3
Etapa 2
La etapa 1 compila el mismo código fuente: ahora es un compilador hecho por un compilador de Caju.
- 4
Etapa 3
La etapa 2 lo compila otra vez. Las etapas 2 y 3 deben ser idénticas byte a byte: el compilador se reproduce a sí mismo.
- 5
caju.wasm
Con
--target wasm, el compilador se compila a sí mismo a WebAssembly; después,caju.wasmtambién se compila a sí mismo a los mismos bytes.
Cada cambio en el compilador se comprueba así, además de con la batería de pruebas: si las etapas 2 y 3 difieren, algo en el compilador cambió el significado del propio compilador.
Valores en memoria
Cada valor cabe en una palabra de 64 bits. Los números y los booleanos son la propia palabra; todo lo demás es un puntero a un objeto creado por el runtime.
s := "caju"Un puntero a [longitud][bytes UTF-8][0]. Los textos nunca cambian: + crea uno nuevo, y todo el programa comparte el mismo literal.
El recolector de basura
El runtime asigna los objetos en páginas de 64 KB y los recolecta con marcado y barrido, escrito en Caju (rt/gc.caju). Pruébalo: las raíces son la pila y las variables globales del programa.
Cada objeto es alcanzable desde una raíz o no lo es. Pulsa Marcar.
- aún no visitado
- marcado (alcanzable)
- basura
- liberado
El recolector es conservador: cualquier palabra en la pila o en un objeto que parezca un puntero a un objeto lo mantiene vivo. Por eso un puntero nunca debe estar solo en un registro durante una llamada, y por eso la versión WebAssembly guarda en una pila en memoria cada valor que debe sobrevivir a una llamada. Una recolección empieza cuando la memoria asignada desde la anterior supera lo que sobrevivió a ella (como mínimo 8 MB).
Llamadas, defer y panic
Cada llamada apila sus argumentos y cada función guarda sus variables locales en un marco. defer, panic y recover forman parte del runtime.
Una llamada
Quien llama apila los argumentos (y la closure, si es un valor función) y hace la llamada; la función llamada encuentra el argumento i de n en un desplazamiento fijo y reserva espacio para sus variables locales. Los resultados 2, 3... viajan en g.ret.
El límite de profundidad
Un contador de llamadas restantes empieza en 10.000 y cada llamada gasta una: la llamada anidada número 10.001 se detiene con "Too much recursion" en lugar de bloquear el programa.
defer
Una función con defer enlaza un registro en una lista (rt_dtop); cada defer añade un nodo con la función y sus argumentos ya evaluados. Se ejecutan del último al primero cuando la función retorna.
panic y recover
panic ejecuta las llamadas diferidas del registro más reciente al más antiguo. Si una de ellas llama a recover(), la ejecución salta de vuelta a la función que la difirió, que retorna con normalidad. En WebAssembly, ese salto es una excepción de wasm.
Caju en tu navegador
El playground ejecuta el compilador real, compilado a WebAssembly, sin servidor.
- 1
La versión
Cada versión en GitHub incluye
caju-wasm.tar.gz: el compilador comocaju.wasm, la biblioteca estándar y el runtime como texto, y un pequeño host en JavaScript. - 2
El worker
La página inicia un Web Worker que carga
caju.wasmy un sistema de archivos en memoria con la biblioteca. - 3
Compilar
Tus archivos van a ese sistema de archivos y
caju build --target wasmse ejecuta dentro decaju.wasm, igual que en Linux: el host responde a sus llamadas al sistema (open, read, write...). - 4
Ejecutar
El nuevo módulo se ejecuta en el mismo worker; lo que escribe llega a la página a medida que se escribe. Detener termina el worker.
Alrededor del compilador
caju test
Busca los archivos *_test.caju y los ejecuta: describe, it y expect de std/testing.
caju pkg
Los paquetes son repositorios de GitHub; selección de versión mínima y un caju.lock con hashes SHA-256.
caju mcp
Un servidor de Model Context Protocol: los asistentes de IA comprueban, ejecutan y prueban código Caju con resultados estructurados.