Pular para o conteúdo
cajulang

Especificação da linguagem

Como a Caju funciona: sintaxe, tipos, instruções, funções, erros e módulos. Os exemplos foram compilados com a versão 0.5.0 e mostram a saída real.

01Visão geral

Caju é uma linguagem de programação compilada, de tipagem estática com inferência. O compilador gera código de máquina x86-64: executáveis ELF para Linux e PE para Windows. Também gera módulos WebAssembly, que rodam no Node e no navegador. O compilador e o runtime são escritos na própria Caju.

A linguagem vem com tudo o que é preciso para trabalhar: compilador, executor de testes (caju test) e gerenciador de pacotes (caju pkg), num único comando caju.

Um programa é um ou mais arquivos .caju. As instruções do nível de cima do arquivo rodam em ordem, de cima para baixo: não é preciso declarar uma função main.

hello.caju
fn greet(name: str) -> str {
return "Hello, " + name + "!"
}
print(greet("world"))
$ caju run hello.caju
Hello, world!

02Elementos léxicos

Comentários. Começam com # ou // e vão até o fim da linha.

Fim de instrução. Uma quebra de linha ou um ; termina a instrução. Uma expressão não continua na linha seguinte depois de um operador: quebre-a em variáveis.

Identificadores. Letras (incluindo as acentuadas do latim, como é e ç), dígitos e _, sem começar por dígito.

Palavras reservadas. Não podem ser usadas como nomes:

letconstifelsewhilefortoandornottruefalseimportfromexportfnreturnexterndllexportstructinterfacenilswitchcasedefaultbreakcontinuefallthroughdefergoselectchanmaprange

Literais.

TipoExemplosObservações
Inteiro42 1_000 0xFF 0b1010 0o17_ separa dígitos
Ponto flutuante3.14 1.5e364 bits
Texto"Olá\n"UTF-8; escapes \n \t \r \0 \\ \" \', \xHH, \uHHHH, \UHHHHHHHH
Texto bruto`c:\pasta\n`Entre crases, sem escapes
Caractere'a'O código do caractere, como inteiro
Booleano e nulotrue false nil
literals.caju
a := 1_000; b := 0xFF; c := 0b1010; d := 0o17
print(a, b, c, d, 1.5e3)
print("tab\tquote\" \u00e9 \x41", `raw\n`, 'a')
$ caju run literals.caju
1000 255 10 15 1500
tab quote" é A raw\n 97

03Tipos

Os tipos são opcionais nas declarações: sem anotação, o compilador infere. Com anotação, um valor de outro tipo é erro de compilação.

TipoDescrição
intInteiro de 64 bits com sinal
int8 int16 int32 int64Inteiros com sinal de tamanho fixo
uint uint8 uint16 uint32 uint64Inteiros sem sinal
numNúmero de ponto flutuante de 64 bits. Aceita valores int
float32Ponto flutuante de 32 bits
strTexto em UTF-8. s[i] é um uint8 e len(s) conta bytes
booltrue ou false
anyQualquer valor. Use asserção ou switch de tipo para recuperar o tipo
errorInterface com o método Error() -> str

Apelidos. byte = uint8, rune = int32, string = str e float64 = num.

Tipo compostoFormaComportamento
Array[3]intTamanho fixo; copiado na atribuição
Slice[]intJanela sobre um array; cresce com append
Mapamap[str]intChave e valor; chaves de tipos básicos
Ponteiro*T&x dá o endereço, *p o valor
Funçãofn(int) -> intFunções são valores
Structstruct Point { x: int, y: int }Valor com campos nomeados
Interfaceinterface Shape { area() -> num }Conjunto de métodos

Valor zero. Uma variável declarada sem valor começa com o zero do tipo: 0, "", false, nil, ou uma struct com todos os campos zerados.

04Variáveis e constantes

FormaEfeito
x := 10Declara e infere o tipo do valor (aqui, int)
let x: int = 10Declara com o tipo explícito
let x: intDeclara com o valor zero do tipo
let x = 0Sem tipo, let com um número infere num, para aceitar decimais depois
const N = 3Constante; precisa de valor
As variáveis valem para a função inteira, não só para o bloco. Não declare o mesmo nome com tipos diferentes na mesma função.
types.caju
x := 10
x = "ten"
$ caju run types.caju
Type error in types.caju, line 2: "x" (line 1) is int, but got str.

05Expressões e operadores

GrupoOperadores
Aritméticos+ - * / % ** (potência)
Bits& | ^ &^ << >>
Comparação== != < <= > >=
Lógicos&& ou and, || ou or, ! ou not
Atribuição= := += -= *= /= %= &= |= ^= <<= >>= &^=
Incrementox++ x--
Ponteiros&x (endereço) e *p (valor apontado)

+ também concatena textos. Não existe ~: para inverter os bits, use x ^ -1.

Conversões se escrevem como chamadas: int(x), num(x), str(x), uint8(x), float32(x), byte(x), rune(x) e as demais.

ops.caju
print(2 ** 10, 7 % 3, 6 &^ 3, 1 << 4, -8 >> 1)
print(true and not false, !true && false)
print(str(3.5), int(3.9), num(2))
$ caju run ops.caju
1024 1 4 16 -4
true false
3.5 3 2

06Controle de fluxo

if aceita uma instrução antes da condição, separada por ;. Não há parênteses em volta da condição.

Laços. Há quatro formas de for e o while:

FormaExemplo
Clássicofor i := 0; i < n; i++ { }
Só condiçãofor i < n { } ou while i < n { }
Intervalofor i = 1 to 10 { } (inclui os dois limites)
rangefor i, v := range lista { } e for k, v := range mapa { }

break e continue aceitam um rótulo para sair ou seguir um laço de fora.

switch compara com cada case, que pode listar vários valores. Os casos não continuam no seguinte, a menos que terminem com fallthrough.

flow.caju
for j = 1 to 2 { print("j", j) }
outer: for p := 0; p < 3; p++ {
for q := 0; q < 3; q++ {
if q == 1 { continue outer }
if p == 2 { break outer }
print(p, q)
}
}
i := 3
switch i {
case 1, 2: print("small")
case 3:
print("three")
fallthrough
default: print("default")
}
$ caju run flow.caju
j 1
j 2
0 0
1 0
three
default

07Funções

Funções são declaradas com fn no nível de cima do arquivo. Cada parâmetro tem um tipo. O resultado vem depois de -> e, se for omitido, é inferido do return.

Uma função pode devolver vários valores, -> (int, error), e os resultados podem ter nome: um return sozinho devolve os valores atuais deles.

Dentro de outra função, crie funções como valores, com fn(...) { }. Elas capturam as variáveis de fora (closures).

functions.caju
fn split(n: int) -> (q: int, r: int) {
q = n / 10
r = n % 10
return
}
print(split(47))
add := fn(a: int, b: int) -> int { return a + b }
print(add(2, 3))
$ caju run functions.caju
4 7
5
Funções declaradas não aceitam um número variável de argumentos. O ... serve para espalhar uma lista numa chamada, como em append(a, b...).

08Structs, métodos e interfaces

Structs agrupam campos com tipo. O literal pode dar os valores em ordem, Point{1, 2}, ou por nome, Point{y: 2}. Os campos não informados ficam com o valor zero.

Métodos são funções com um receptor: fn (p: Point) dist() recebe uma cópia, e fn (p: *Point) move() recebe o original e pode alterá-lo.

Embutir uma struct em outra dá à de fora os campos e métodos da de dentro.

Interfaces são satisfeitas implicitamente: um tipo atende a uma interface se tiver os métodos dela. Uma interface pode embutir outra. x.(T) recupera o tipo guardado e, com v, ok := x.(T), não gera panic.

interfaces.caju
interface Named { name() -> str }
interface Greeter { Named; greet() -> str }
struct P { n: str }
fn (p: P) name() -> str { return p.n }
fn (p: P) greet() -> str { return "hi " + p.n }
gs := []Greeter{P{"ana"}}
print(gs[0].greet(), gs[0].name())
$ caju run interfaces.caju
hi ana ana

09Erros, defer e panic

Erros são valores do tipo error. Crie um com errors.New("mensagem") ou com qualquer tipo que tenha o método Error() -> str. Funções que podem falhar devolvem o erro como último resultado.

defer agenda uma chamada para quando a função terminar; as chamadas agendadas rodam da última para a primeira. panic interrompe a execução e desfaz as chamadas; recover, dentro de um defer, para o panic. Erros de execução, como um índice fora da lista, também viram panic.

errors.caju
struct NotFound { name: str }
fn (e: *NotFound) Error() -> str { return e.name + " not found" }
fn find(name: str) -> error {
if name != "caju" { return &NotFound{name} }
return nil
}
err := find("mango")
if err != nil { print(err.Error()) }
$ caju run errors.caju
mango not found

10Funções embutidas

FunçãoUso
print(a, b, ...)Imprime os valores separados por espaço e quebra a linha
len(x) e cap(x)Tamanho e capacidade de textos, arrays, slices e mapas
append(s, v...)Acrescenta valores a um slice e devolve o slice
copy(dst, src)Copia elementos e devolve quantos copiou
delete(m, k)Remove uma chave de um mapa
make(T, n, c)Cria slices e mapas
new(T)Aloca um valor zero e devolve um ponteiro
panic(v) e recover()Interrompe a execução e recupera dentro de um defer
errors.New(msg)Cria um erro com a mensagem

11Módulos e pacotes

Cada arquivo é um módulo. Só os nomes declarados com export podem ser usados por outros arquivos.

ImportaçãoEfeito
import "std/strings"Biblioteca padrão, usada como strings.split(...)
import filepath "std/path"Importa com outro nome
import { split, join } from "std/strings"Traz nomes para uso direto
import "./mods/geo"Outro arquivo do projeto, relativo ao atual
import "github.com/dono/pacote"Pacote de terceiros, instalado com caju pkg add
mods/geo.caju
# mods/geo.caju
export struct Point { x: int, y: int }
export fn origin() -> Point { return Point{} }
fn hidden() -> int { return 1 }
main.caju
import "./mods/geo"
print(geo.origin())
print(geo.hidden())
$ caju run main.caju
Link error in main.caju, line 4: "hidden" exists in ./mods/geo.caju,
but it is not exported. Write export before its declaration.

12Gerenciador de pacotes

A Caju vem com um gerenciador de pacotes, o caju pkg. Não há servidor central: um pacote é um repositório do GitHub com um arquivo caju.toml na raiz. O nome do pacote é o caminho do repositório, e é esse nome que os programas importam.

caju.toml
[package]
name = "github.com/acme/strutil"
version = "1.1.0"
description = "Text helpers"
license = "MIT"
[dependencies]
"github.com/acme/other" = "1.0.0"

Versões. Uma versão publicada é uma tag de git no formato vX.Y.Z, com o mesmo número do version do caju.toml. Se os dois não baterem, o gerenciador recusa o pacote.

Escolha de versões. Cada dependência indica a versão mínima de que precisa. Quando dois pacotes pedem versões diferentes do mesmo pacote, vale a maior das mínimas. Assim, o resultado só muda quando um caju.toml pede uma versão maior, nunca porque alguém publicou uma versão nova. Versões de MAJOR diferente, como 1.x e 2.x, não são compatíveis.

Trava de versões. O caju.lock guarda, para cada pacote usado direta ou indiretamente, a versão, o commit e um hash SHA-256 dos arquivos. Faça commit dele: assim todo mundo compila exatamente os mesmos arquivos.

Verificação. Antes de compilar, caju build e caju run conferem se o caju.lock está em dia com o caju.toml e se os arquivos de cada pacote batem com o hash travado. Qualquer diferença impede a compilação:

app.caju
import "github.com/acme/strutil"
print(strutil.shout("caju"))
$ caju run app.caju # depois de alterar um arquivo do pacote
Package error: checksum mismatch for github.com/acme/strutil@1.1.0
caju.lock: sha256:aa012c96e037f3527d565bae3fbcb84c459070d694f2276e8149582ced94ee9a
files: sha256:67e6d47f83afb261e6971681c489a422c30f146d8fac2adf0732a7dba5f62756
ComandoO que faz
caju pkg init [nome]Cria o caju.toml do projeto
caju pkg add nome[@versão]Adiciona uma dependência (padrão: a última versão publicada)
caju pkg remove nomeRemove uma dependência
caju pkg update [nome]Leva as dependências à última versão publicada
caju pkg installBaixa o que falta e confere tudo pelo caju.lock
caju pkg verifyConfere os pacotes do cache contra os hashes do lock
caju pkg treeMostra a árvore de dependências
caju pkg cache list|verify|cleanOs pacotes baixados na máquina, compartilhados por todos os projetos e conferidos pelo hash a cada reuso
caju pkg hash [dir]Calcula o hash de um diretório de pacote

Onde ficam. Os pacotes são baixados com git (a tag da versão) para o cache em ~/.caju/pkg. add, remove e update editam só a seção [dependencies] do caju.toml, sem mexer no resto do arquivo.

Dentro de um pacote, os arquivos se importam pelo caminho completo, como import "github.com/acme/strutil/extra", e um pacote só deve importar o que está no [dependencies] dele.

13Chamadas ao sistema no Windows

extern declara uma função de uma DLL do Windows, com a convenção stdcall (padrão) ou cdecl. Os parâmetros usam tipos de interface: i32, u32, i64, u64, f32, f64, ptr, bool, str e wstr, e o resultado pode ser void. Essas chamadas só existem em programas compilados com --target windows.

message.caju
extern "user32.dll" fn MessageBoxA(hwnd: ptr, text: str, title: str, flags: u32) -> i32
MessageBoxA(0, "Hello from Caju", "caju", 0)

14Compilação e ferramentas

ComandoO que faz
caju run arquivo.cajuCompila em memória e executa
caju build -o app arquivo.cajuGera um executável Linux (ELF)
caju build --target wasm -o app.wasm arquivo.cajuGera um módulo WebAssembly (roda no Node ou no navegador)
caju build --target windows arquivo.cajuGera um .exe para Windows (PE)
caju testRoda os arquivos _test.caju
caju pkg ...Gerenciador de pacotes (ver a seção anterior)
caju versionMostra a versão

15Em desenvolvimento

  • Goroutines, canais e select no compilador nativo. As palavras go, chan e select já são reservadas.
  • No Windows: alloc, peek, poke, readstr, dllexport e DLL nativa.
  • Mapas com chave struct, array ou any.