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.
fn greet(name: str) -> str { return "Hello, " + name + "!"} print(greet("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:
letconstifelsewhilefortoandornottruefalseimportfromexportfnreturnexterndllexportstructinterfacenilswitchcasedefaultbreakcontinuefallthroughdefergoselectchanmaprangeLiterais.
a := 1_000; b := 0xFF; c := 0b1010; d := 0o17print(a, b, c, d, 1.5e3)print("tab\tquote\" \u00e9 \x41", `raw\n`, 'a')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.
Apelidos. byte = uint8, rune = int32, string = str e float64 = num.
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
x := 10x = "ten"05Expressões e operadores
+ 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.
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))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:
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.
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 := 3switch i {case 1, 2: print("small")case 3: print("three") fallthroughdefault: print("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).
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))... 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.
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())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.
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()) }10Funções embutidas
11Módulos e pacotes
Cada arquivo é um módulo. Só os nomes declarados com export podem ser usados por outros arquivos.
# mods/geo.cajuexport struct Point { x: int, y: int }export fn origin() -> Point { return Point{} }fn hidden() -> int { return 1 }import "./mods/geo" print(geo.origin())print(geo.hidden())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.
[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:
import "github.com/acme/strutil" print(strutil.shout("caju"))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.
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.
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
15Em desenvolvimento
- Goroutines, canais e
selectno compilador nativo. As palavrasgo,chaneselectjá são reservadas. - No Windows:
alloc,peek,poke,readstr,dllexporte DLL nativa. - Mapas com chave struct, array ou
any.