Como a Caju funciona
Do arquivo .caju ao programa rodando: as etapas do compilador, como os valores ficam na memória, o coletor de lixo, chamadas e panics, e como o compilador acaba rodando no seu navegador. Tudo o que aparece aqui vem do compilador de verdade.
Do código-fonte ao executável
O compilador é uma sequência de etapas, cada uma escrita em Caju (compiler/*.caju). Acompanhe um programa pequeno por elas: escolha uma etapa para ver o que ela produz.
Analisador léxico
O lexer.caju corta o texto em tokens: palavras-chave, nomes, operadores, números, textos. Cada token guarda sua linha e coluna, e é assim que todo erro depois aponta para o lugar certo. Quebras de linha viram tokens NEWLINE: elas encerram os comandos.
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
Um compilador que compila a si mesmo
O compilador da Caju é escrito em Caju. Para obter o primeiro, usa-se uma vez um pequeno compilador escrito em JavaScript; daí em diante, o compilador compila o próprio código-fonte.
- 1
Estágio 0
bootstrap/*.js: o compilador original, em JavaScript, executado pelo Node. - 2
Estágio 1
O estágio 0 compila
compiler/main.caju: umcajunativo, feito pelo compilador em JavaScript. - 3
Estágio 2
O estágio 1 compila o mesmo código-fonte: agora um compilador feito por um compilador Caju.
- 4
Estágio 3
O estágio 2 compila tudo de novo. Os estágios 2 e 3 precisam ser idênticos byte a byte: o compilador reproduz a si mesmo.
- 5
caju.wasm
Com
--target wasm, o compilador compila a si mesmo para WebAssembly; depois, ocaju.wasmtambém compila a si mesmo e gera os mesmos bytes.
Toda mudança no compilador é verificada assim, além da suíte de testes: se os estágios 2 e 3 forem diferentes, algo no compilador mudou o significado do próprio compilador.
Valores na memória
Todo valor cabe numa palavra de 64 bits. Números e booleanos são a própria palavra; todo o resto é um ponteiro para um objeto criado pelo runtime.
s := "caju"Um ponteiro para [tamanho][bytes UTF-8][0]. Textos nunca mudam: + cria um novo, e o mesmo literal é compartilhado pelo programa inteiro.
O coletor de lixo
O runtime aloca objetos em páginas de 64 KB e os recolhe com marcação e varredura (mark and sweep), escrito em Caju (rt/gc.caju). Experimente: as raízes são a pilha e as variáveis globais do programa.
Cada objeto é alcançável a partir de uma raiz, ou não. Clique em Marcar.
- ainda não visitado
- marcado (alcançável)
- lixo
- liberado
O coletor é conservador: qualquer palavra na pilha ou num objeto que pareça um ponteiro para um objeto o mantém vivo. Por isso um ponteiro nunca pode ficar só num registrador durante uma chamada, e por isso a versão WebAssembly guarda numa pilha em memória todo valor que precisa sobreviver a uma chamada. Uma coleta começa quando a memória alocada desde a última passa do que sobreviveu a ela (no mínimo 8 MB).
Chamadas, defer e panic
Cada chamada empilha seus argumentos e cada função guarda suas variáveis locais num frame. defer, panic e recover fazem parte do runtime.
Uma chamada
Quem chama empilha os argumentos (e a closure, no caso de um valor de função) e faz a chamada; a função chamada acha o argumento i de n num deslocamento fixo e reserva espaço para suas variáveis locais. Os resultados 2, 3... vão em g.ret.
O limite de profundidade
Um contador de chamadas restantes começa em 10.000 e cada chamada gasta uma: a 10.001ª chamada aninhada para com "Too much recursion" em vez de derrubar o programa.
defer
Uma função com defer encadeia um registro numa lista (rt_dtop); cada defer acrescenta um nó com a função e seus argumentos já avaliados. Eles rodam do último para o primeiro quando a função retorna.
panic e recover
O panic executa as chamadas adiadas do registro mais novo para o mais antigo. Se uma delas chamar recover(), a execução volta para a função que a adiou, que retorna normalmente. No WebAssembly, esse salto é uma exceção wasm.
Caju no seu navegador
O playground roda o compilador de verdade, compilado para WebAssembly, sem servidor.
- 1
A release
Cada release no GitHub traz
caju-wasm.tar.gz: o compilador comocaju.wasm, a biblioteca padrão e o runtime como texto, e um pequeno host em JavaScript. - 2
O worker
A página inicia um Web Worker que carrega o
caju.wasme um sistema de arquivos em memória com a biblioteca. - 3
Compilar
Seus arquivos vão para esse sistema de arquivos e
caju build --target wasmroda dentro docaju.wasm, como rodaria no Linux: o host responde às chamadas de sistema dele (open, read, write...). - 4
Executar
O novo módulo roda no mesmo worker; o que ele escreve volta para a página à medida que é escrito. Parar encerra o worker.
Em volta do compilador
caju test
Encontra os arquivos *_test.caju e os executa: describe, it e expect de std/testing.
caju pkg
Pacotes são repositórios do GitHub; seleção de versão mínima e um caju.lock com hashes SHA-256.
caju mcp
Um servidor Model Context Protocol: assistentes de IA verificam, executam e testam código Caju, com resultados estruturados.