Pular para o conteúdo
cajulang

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.

02

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.

O programa de exemplo · main.caju
fn add(a: int, b: int) -> int {
return a + b
}
print(add(2, 3))
O que esta etapa produz: tokens
  1. fnKEYWORD 1:1
  2. addNAME 1:4
  3. (OP 1:7
  4. aNAME 1:8
  5. :OP 1:9
  6. intNAME 1:11
  7. ,OP 1:14
  8. bNAME 1:16
  9. :OP 1:17
  10. intNAME 1:19
  11. )OP 1:22
  12. ->OP 1:24
  13. intNAME 1:27
  14. {OP 1:31
  15. ↵NEWLINE 1:32
  16. returnKEYWORD 2:3
  17. aNAME 2:10
  18. +OP 2:12
  19. bNAME 2:14
  20. ↵NEWLINE 2:15
  21. }OP 3:1
  22. ↵NEWLINE 3:2
  23. printNAME 5:1
  24. (OP 5:6
  25. addNAME 5:7
  26. (OP 5:10
  27. 2NUMBER 5:11
  28. ,OP 5:12
  29. 3NUMBER 5:14
  30. )OP 5:15
  31. )OP 5:16
  32. ↵NEWLINE 5:17
  33. ∎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. 1

    Estágio 0

    bootstrap/*.js: o compilador original, em JavaScript, executado pelo Node.

  2. 2

    Estágio 1

    O estágio 0 compila compiler/main.caju: um caju nativo, feito pelo compilador em JavaScript.

  3. 3

    Estágio 2

    O estágio 1 compila o mesmo código-fonte: agora um compilador feito por um compilador Caju.

  4. 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. 5

    caju.wasm

    Com --target wasm, o compilador compila a si mesmo para WebAssembly; depois, o caju.wasm também compila a si mesmo e gera os mesmos bytes.

cmp build/caju2 build/caju3 && echo okstage 2 == stage 3

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"
s
●
str
len
4
byte
c
byte
a
byte
j
byte
u
NUL
0

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.

RaízesHeapstack: liststack: userglobal: cache[]intarray*User"Ana"mapmap"old"structclosure
  • 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.

↑ quem chama
locais
sum
list ●
argumentos
a = 2
b = 3
frame ($fp) →
↓ chamada
locais
x
y ●
temporários
t ●
ponteiro de pilha ($sp) →

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. 1

    A release

    Cada release no GitHub traz caju-wasm.tar.gz: o compilador como caju.wasm, a biblioteca padrão e o runtime como texto, e um pequeno host em JavaScript.

  2. 2

    O worker

    A página inicia um Web Worker que carrega o caju.wasm e um sistema de arquivos em memória com a biblioteca.

  3. 3

    Compilar

    Seus arquivos vão para esse sistema de arquivos e caju build --target wasm roda dentro do caju.wasm, como rodaria no Linux: o host responde às chamadas de sistema dele (open, read, write...).

  4. 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.

Playground →

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.