Pular para o conteúdo

Especificação §27

Tour de sintaxe (a linguagem numa página)

language-design.md §27 · 352 linhas · 9 min de leitura

A “parte 2”: um passeio por toda a sintaxe em código comentado, agrupado por tema, como referência rápida. Nada é novo, é o consolidado das seções 1 a 15, escrito como você escreveria. Cada bloco é um exemplo executável; os comentários apontam a decisão por trás.

x := 5 // declara imutável, runtime (default): atalho de 'let'
let y := 5 // imutável, forma explícita (idêntica a 'x := 5')
var z := 5 // mutável
const N := 64 // constante de compilação (seção 10)
nome: string = "ana" // tipo explícito ao estilo Odin; ':=' é ': =' com tipo omitido
z = z + 1 // '=' pelado só atribui, e só a um 'var'; o ':' é o que declara
// var w := compute(); return w // ERRO: 'w' é var mas nunca foi mutado. Use a forma imutável
// Um só tipo de ponteiro; 'mut' é write-back. Ambos exigem alvo 'var'.
p := &z // &lugar = endereço de um lugar
total := *p // *p = deref
fn bump(v: mut int) { v = v + 1 } // parâmetro write-back
bump(mut z) // muta z in-place; bump(mut x) seria ERRO (x é imutável)
loop { break } // sem cabeçalho → infinito
loop ativo { ... } // expressão booleana → while
loop item in lista { ... } // IDENT in iterável → for-each
loop i in 0..8 { ... } // range exclusivo (..= é inclusivo)
loop i := 0; i < 8; i = i+1 { ... } // C-style; ';' delimita as 3 partes (parênteses opcionais)
// match COM sujeito → casa padrões (o "switch")
match parse_port(input) {
Ok(p) => listen(p)
Err(error.Empty) => use_default()
Err(error.NotNumber) => abort()
Err(error.OutOfRange) => abort() // domínio fechado: todas as variantes, '_' seria erro
}
// match SEM sujeito → espera vários channels, reage ao primeiro pronto (o "select")
match {
msg := -> chan_a => handle_a(msg)
msg := -> chan_b => handle_b(msg)
} timeout(1s) {
give_up()
}
defer file.close() // cleanup determinístico; roda na saída do escopo, LIFO
// Modificadores: pub? comptime? unsafe? fn ('@generator' é decorador: acima ou inline)
pub fn add(a: int, b: int) -> int { return a + b }
// UFCS: x.f(y) é açúcar para f(x, y): pipeline com '.', e lambdas são 'fn(x) => expr'
errors := lines
.filter(fn(l) => l.contains("ERROR"))
.map(fn(l) => parse_entry(l))
// Generic com lógica uniforme:
fn show[T + Display](x: T) -> string { return x.to_string() }
// Dispatch por tipo, variações pequenas e juntas → 'match T' interno (domínio fechado, sem '_'):
fn encode[T: {int, bool}](x: T) -> []byte {
match T {
int => return x.to_bytes()
bool => return x.to_bytes()
}
}
// Variações grandes ou separadas → set externo (roteador por tipo, resolvido em compilação):
fn to_string = { int_to_string, bool_to_string }
decl User {
id: int // privado ao módulo por default (encapsulado)
name: string
pub email: string // campo exposto individualmente a outros módulos
}
// enum SEM payload = enumeração simples / state machine (sem iota; cada variante é um valor):
decl State { Idle; Running; Done }
// enum COM payload = sum type (payload é opcional por variante, e pode misturar):
decl Event { Tick; Click(int, int); Quit }
// match exaustivo sobre enum: cobre todas as variantes e '_' é erro de compilação
// (adicionar uma variante quebra a build até você tratá-la, o '_' anularia isso):
fn step(s: State) -> State {
match s {
Idle => return Running
Running => return Done
Done => return Done
}
}
// match com payload vincula os campos por posição; domínio fechado → todas as variantes (sem '_'):
fn describe(e: Event) -> string {
match e {
Tick => return "tick"
Click(x, y) => return "click em " + x.to_string()
Quit => return "quit"
}
}
// Método = função externa com receiver (estilo Go); a struct só guarda dados:
fn (u: User) display() -> string { return u.name + " <" + u.email + ">" }
// Interface = conjunto de métodos; implementar os métodos JÁ satisfaz, sem "implements":
decl Writeable {
fn write(data: []byte) -> error
}
// union = bytes sobrepostos, sempre unsafe (reinterpretação):
unsafe union Bits { f: f32; raw: u32 }
flag: Optional[int] // ausência explícita; inteiros i8..usize, byte = u8, floats IEEE
s := "Olá {{nome}}!" // string UTF-8; interpolação nomeada (via Display)
n := s.char_at(0) // um grapheme; NÃO há 's[i]' puro (ambíguo → erro)
view := s.chars[2..6] // VIEW por graphemes ('..'), mantém s vivo; .bytes/.codepoints idem
cp := s.chars_copy(2, 6) // método-irmão; ':' / '_copy' = cópia O(n)
raw := s.to[u16]() // conversão de borda, explícita → []byte
eq := a == b // '==' é igualdade de bytes; '.equals(_, .semantic)' p/ Unicode
var b := "x"; b.append("y") // mutável só com 'var'; crescimento por doubling
fn parse_port(s: string) -> Result[u16, error{Empty, NotNumber, OutOfRange}] {
if s == "" { return Err(error.Empty) }
// ...
}
// 'catch |e|' trata o erro como unidade e ESCAPA (return/panic): propaga ou devolve um fallback:
port := parse_port(input) catch |e| { return default_port() }
// 'match |e|' ramifica por variante (trailer; '|e|' é o erro), e como o catch cada arm escapa.
// Variante única → use catch. Recuperar COM valor (dar default ao cfg) é match sobre o Result.
cfg := load() match |e| {
error.NotFound => return e // propaga (açúcar → Err(e))
error.Corrupted => runtime.panic("config ilegível") // aborta
}
// Três formas, sem sobreposição: só-valores → tupla (string, string); só-erro → error-set sozinho;
// valor(es) + erro → Result. Não existe retorno posicional com erro (entrou erro, é Result):
fn split_pair(s: string) -> (string, string) { ... }
// Channel tipado pela perspectiva de quem segura o handle: <-T = "envio T", ->T = "recebo T"
c: Channel[<-int, ->string]
spawn worker(c) // o compilador verifica que os lados são complementares
fn worker(c: Channel[<-string, ->int]) {
val := -> c // recebe (bloqueia até chegar)
c <- val.to_string() // envia (async, fire-and-forget)
}
reply := server <-> request timeout(5s) // sync request-reply; timeout OBRIGATÓRIO (sem ele, erro)
spawn task(data) catch |e| { log(e) } // processo com handler de falha
spawn { // grupo de processos (a supervisão emerge da estrutura)
indexer(docs) catch |e| { ... }
notifier(subs) catch |e| { ... }
}
fn build_report() -> Report {
@mm(arena) // estratégia de alocação deste escopo: arena | gc | none
buf: HugeBuffer @mm(arena) = alloc_buffer() // (forma postfix fixa por-valor)
defer free_arena() // @mm(none)/manual libera com defer
// ...
}
@mm(arena) spawn producer(data @transfer) // @transfer invalida 'data' aqui; chega no destino com seu mm
@mm(gc) spawn consumer(chan) // Arena → GC via @transfer é válido
promoted := node @promote(gc, deep) // copia para outro MM (shallow é o default; 'deep' explícito)
// 'type' é valor de primeira classe; um generic é uma função que recebe/devolve 'type':
decl List[T] { items: *T, len: usize } // açúcar para a comptime fn abaixo
comptime fn List(T: type) -> type {
return decl { items: *T; len: usize }
}
const nums := List[int] // chamada em compilação → tipo concreto (monomorfizado)
buffer: [N]u8 // '[...]' aceita valores comptime, não só tipos
comptime { // bloco que roda no compilador
assert (N <= 1024)
}
const backend := use(if target.arch == x86 { return "backend_x86" } else { return "backend_wasm" })
@generator fn fibonacci() -> int {
var a := 0
var b := 1
loop {
yield a
next := a + b
a = b
b = next
}
}
loop n in fibonacci().take(10) { io.print(n) } // consumido como qualquer iterável
// Contrato de fronteira: pré-condição empilha; pós-condição tem duas formas equivalentes.
@requires(i < buf.len)
@requires(buf.len > 0)
@ensures(u8 == buf.bytes[i]) // forma diretiva (empilha, referencia o retorno por tipo)
fn at(buf: Buffer, i: usize) -> u8 { // ou tersa, no tipo de retorno: '-> u8 == buf.bytes[i]'
// a pré-condição contratada torna 'at' SEGURA de chamar (o compilador checa na chamada)
return unsafe raw_read(buf.ptr + i) assume "ptr válido: invariante de Buffer + bounds do @requires"
}
b := at(buffer, 3) // chamada safe: o compilador checa 3 < buf.len
// named return: obrigatório SÓ quando um tipo de retorno se repete e há @ensures sobre ele
@ensures(lo > 0)
@ensures(hi > 50)
fn split(...) -> (lo: u8, hi: u8) { return (a, b) } // nomes = rótulos do slot; return livre
// Abate de operação no corpo: forte (checa, panic) vs fraca (caso-B, razão não-vazia, auditada)
y := unsafe x.raw assume "reinterpretação intencional float→bits"
result := unsafe { // bloco = escopo-expressão
v := raw_read(p) assert (p_valid) // assert posterior enxerga o que veio antes ('p_valid')
return v * 2 // valor sai por return (expressão → vai pro 'result')
} assert (p_valid) // trailer compartilhado = pré-condição de entrada
const c := @cimport("stdio.h") // C entra como módulo (comptime) → c.stdio
@cimport("gcc") // tipos fundamentais → c.gcc.int, c.gcc.size_t (ABI do compilador)
buf: *c.gcc.char = c.stdio.malloc(n) // tudo qualificado sob 'c.'; tipo do runtime + fn do header
unsafe c.stdio.printf("%d", count) // chamar C é unsafe; variádico é caso-B (tipos por sua conta)
@repr(c) decl Point { x: c.gcc.int } // layout C-fiel na fronteira (structs internas são otimizadas)
@pin handle // objeto @mm(gc) cruzando pro C exige @pin (senão não compila)
mine := data @promote(gc) // memória do C (@mm(c)) repatriada pro meu GC
@callback fn cmp(a: *c.gcc.void, b: *c.gcc.void) -> c.gcc.int { ... } // fn chamável pelo C
@asm(x86, intel) // função-assembly: corpo é asm, retorna valor
unsafe fn add10(v: u32 @asm(in, rax)) -> u32 @asm(out, rbx) {
mov rbx, rax
add rbx, 10
}
var lo: u32; var hi: u32 // bloco inline: roda asm no meio de código normal
@asm(x86, intel)
@asm(out, rax) lo
@asm(out, rdx) hi
@asm(clobber, "memory") // clobbers EXPLÍCITOS no bloco (a função usa a ABI)
unsafe { rdtsc }
@asm(wasm, wasm) // WASM não tem registradores: liga por param/local
unsafe fn add(a: u32, b: u32) -> u32 { local.get a local.get b i32.add }
procs := runtime.processes() // snapshot barato: processos, memória, árvore, @mm por processo
p := runtime.process("db_writer") // por NOME registrado (restart-estável); runtime.group("h") p/ grupos
me := runtime.self() // a própria referência, de dentro de qualquer processo
@register("db_writer") // nomeia o processo (string, estável); @register("h", group) p/ grupos
spawn db_writer(conn) // 'spawn' solto não registra nada (fire-and-forget, custo zero)
ev := runtime.trace(p, [.send, .receive, .channel_block], sink: .channel, sample: 100)
loop e in ev { match e { ... } } // captura = ring buffer (barata); consumo via channel/callback/endpoint
@component
fn Counter(start: int) {
@state n := start // estado reativo (decorator, não keyword)
@derived label := "cliques: {{n}}" // recomputa quando 'n' muda
fn inc() { n = n + 1 } // handler nomeado
return { // markup no return block (sem implicit return)
<button class="btn" onclick={inc}>{{label}}</button> // handler: { } ; texto: {{ }}
<style>
.btn { padding: 8px 16px; } // CSS escopado: '.btn' → '.btn_a3f8' (hashing); typo = erro
</style>
}
}
@jsimport("dom", browser) // FFI de JS: tudo sob 'js.'; ambiente propaga @server/@client
@css("tailwind.css") // CSS externo: global, nomes preservados, entra no check
@server fn load(id: int) -> Post { return db.get(id) } // @server/@client = restrição; resto atravessa
// ilhas: só as partes interativas viram WASM; o resto é HTML estático
// fronteira servidor/cliente = channel de rede (net.connect[T], T + Serializable)
// onde WASM não roda: transpile do .mkoui pra JS via build --target=js (paridade total)
// arquivo dentro de um projeto com mk.project na raiz
use myapp/store // local: prefixo = nome do pacote (absoluto, nunca '../')
use json // stdlib: bare (vem com o compilador)
use github.com/acme/http as web // externo: forma URL + alias
use json.{parse, decode} // traz nomes específicos pro escopo
priv fn helper() { ... } // visível só neste ARQUIVO
fn internal() { ... } // visível no MÓDULO (default)
pub fn api() { ... } // visível a outros MÓDULOS
module quickscript // (alternativa) módulo single-file transportável, checksums embutidos