Pular para o conteúdo

Especificação

Gramática formal

A gramática, token a token, sincronizada com o documento de design.

language-grammar.md · 452 linhas · 22 min de leitura

Notação. ::= define uma regra; ? opcional; * zero-ou-mais; + um-ou-mais; | alternativa; ( ) agrupa; "…" é um terminal literal. Como a linguagem usa { } [ ] ( ) como tokens reais, eles aparecem sempre entre aspas ("{", "[") pra não colidir com o metassintaxe. Newline é uma quebra de linha significativa (ver ASI, §1).

Separadores (regra central, do design doc). O separador segue o tipo do conteúdo, não o bracket. Um grupo-de-valor (lista posicional e homogênea de valores) separa por ,: argumentos de função, elementos de tupla, elementos de literal de array/slice/coleção, e os membros group-ish de set (error{A, B}, Event{Send, Recv}, type args Map[K, V]). Um corpo de membros distintos (cada um um campo nomeado, um caso, ou um statement) separa por ; ou Newline (combináveis para legibilidade): campos de struct (numa declaração e num struct literal), variantes de enum, braços de match, statements de bloco. Então um struct literal é campos distintos, Point{1; 2}, enquanto um array literal é grupo-de-valor, []int{1, 2, 3}, mesmo os dois sendo delimitados por { }: o conteúdo decide, não a chave.

Fidelidade. As regras espelham o design doc seção por seção, sincronizadas com ele. [decisão] marca os poucos pontos onde a gramática precisou escolher por silêncio do doc (ex.: a desambiguação do {} em §6).


Program ::= ModuleDecl? Item* (* ModuleDecl, se houver, é a 1ª coisa do arquivo *)
Ident ::= (Letter | "_") (Letter | Digit | "_")*
Letter ::= "a"…"z" | "A"…"Z"
Digit ::= "0"…"9"
HexDigit ::= Digit | "a"…"f" | "A"…"F"
OctDigit ::= "0"…"7"
BinDigit ::= "0" | "1"
(* Keywords reservadas, nunca um Ident. As 32 do §27: *)
Keyword ::= "fn" | "let" | "var" | "mut" | "const" | "decl" | "alias" | "union" | "module" | "use"
| "if" | "else" | "match" | "loop" | "in" | "break" | "continue" | "return" | "defer" | "catch" | "yield"
| "spawn" | "timeout"
| "comptime" | "unsafe" | "assert" | "assume"
| "pub" | "priv"
| "as"
| "true" | "false"
(* --- literais --- *)
Literal ::= IntLit | FloatLit | DurationLit | SizeLit | StringLit | CharLit | BoolLit
IntLit ::= DecInt | HexInt | OctInt | BinInt (* 4 bases + separador '_' *)
DecInt ::= Digit (Digit | "_")*
HexInt ::= "0x" HexDigit (HexDigit | "_")*
OctInt ::= "0o" OctDigit (OctDigit | "_")*
BinInt ::= "0b" BinDigit (BinDigit | "_")*
FloatLit ::= DecInt "." DecInt (("e" | "E") ("+" | "-")? DecInt)?
DurationLit ::= DecInt DurationUnit (* lexer converte → Duration, sem import *)
DurationUnit ::= "ns" | "ms" | "s" | "m" | "h" | "d" (* §14, ver nota *)
SizeLit ::= DecInt SizeUnit (* lexer converte → usize em BYTES, sem import (§14) *)
SizeUnit ::= "b" | "kb" | "kib" | "mb" | "mib" | "gb" | "gib" | "tb" | "tib" (* b=byte; 'i'=base-2, só com prefixo *)
StringLit ::= '"' (StringChar | Interpolation)* '"'
Interpolation ::= "{{" Expr "}}" (* §16, interpolação via Display *)
CharLit ::= "'" Grapheme "'" (* um grapheme cluster; resolve p/ codepoint/byte/grapheme por contexto *)
BoolLit ::= "true" | "false"
BuiltinValue ::= "none" | … (* 'none' = Optional vazio E estratégia de @mm; é VALOR builtin, não keyword (§5) *)
Comment ::= "//" (até Newline)
(* --- operadores (tokens) --- *)
ArithOp ::= "+" | "-" | "*" | "/" | "%"
| "+%" | "-%" | "*%" (* soma/sub/mul com wrap: +/-/* trapam em overflow, as formas com %-sufixo dão wrap (§4) *)
BitwiseOp ::= "&" | "|" | "^" | "~" | "<<" | ">>" (* ~ é unário (complemento); resto binário *)
CompareOp ::= "==" | "!=" | "<" | ">" | "<=" | ">="
LogicalOp ::= "&&" | "||" | "!"
CompoundAssign ::= "+=" | "-=" | "*=" | "/=" | "%=" (* aritméticos com '=' *)
| "&=" | "|=" | "^=" | "<<=" | ">>=" (* bitwise com '=' *)
ChannelArrow ::= "<-" | "->" | "<->" (* tipo de channel E operadores send/receive (§4) *)

Unidades de duração. São 6 (ns ms s m h d), como o §14 do design doc (5s, 300ms, 10ns, 6m, 7h, 18d).

ASI (só Newline). O separador de statement é a quebra de linha (estilo Go); o lexer fecha o statement no fim da linha. O ; explícito existe para os casos de uma-linha (cláusulas do loop C-style; braços/statements inline como unsafe { a; b }), mas a inserção automática é disparada apenas por Newline.


Item ::= Decorator* ItemBody
ItemBody ::= FnDecl | MethodDecl | TypeDecl | AliasDecl | UnionDecl | Binding | UseDecl
(* --- função --- *)
FnDecl ::= Visibility? "comptime"? "unsafe"? "fn"
Ident GenericParams? "(" ParamList? ")" ("->" ReturnType)? Block
Visibility ::= "pub" | "priv"
ParamList ::= Param ("," Param)* (* grupo em '()' → vírgula *)
Param ::= Decorator* Ident ":" ParamType ("=" Expr)? (* "= Expr" = valor default *)
ParamType ::= "mut"? Type (* 'mut' = passagem write-back (§6) *)
| "..." Type (* variádico, '...' é PREFIXO (§7/§13) *)
ReturnType ::= Type "==" Expr (* pós-condição tersa: -> u8 == cond (§5) *)
| "(" NamedReturn ("," NamedReturn)+ ")" (* tupla nomeada: -> (lo: u8, hi: u8): SÓ retorno; rótulos p/ @ensures (§5/§14) *)
| Type (* inclui tupla (a,b), Result[…], error{…} *)
NamedReturn ::= Ident ":" Type
MethodDecl ::= "fn" "(" (Ident ":")? Type ")" Ident "(" ParamList? ")" ("->" ReturnType)? Block (* binding presente = método; ausente = função associada (§2) *)
(* --- decl: struct | enum | interface (o corpo desambigua; §2) --- *)
TypeDecl ::= "decl" Ident GenericParams? ("->" Kind)? "{" DeclBody "}" (* '-> Kind' opcional, default '-> type'; '-> prop' = família de proposição (§21, D2) *)
DeclBody ::= FieldList (* campos 'nome: tipo' → struct *)
| VariantList (* variantes 'Nome'/'Nome(…)' → enum *)
| SigList (* assinaturas 'fn …' → interface *)
FieldList ::= Field (MemberSep Field)* MemberSep?
Field ::= Decorator* Visibility? Ident ":" Type Decorator* (* @embeds após o tipo (§2) *)
VariantList ::= Variant (MemberSep Variant)* MemberSep?
Variant ::= Ident ("(" TypeList ")")? (* Nome OU Nome(payload) *)
| Ident ":" ResultVariant (* variante que refina índice, SÓ sob um head dependente (§21, D3) *)
ResultVariant ::= Type (* resultado do construtor nulário: Nil: Vec[T, 0] *)
| "fn" GenericParams? "(" ParamList? ")" "->" Type (* Cons: fn[m: usize](head: T, tail: Vec[T, m]) -> Vec[T, m + 1] *)
SigList ::= MethodSig (MemberSep MethodSig)* MemberSep?
MethodSig ::= "fn" Ident "(" ParamList? ")" ("->" ReturnType)?
Kind ::= "type" | "prop" (* o universo que um decl produz: 'type' (default, o kind da §10) | 'prop' (uma proposição, §21) *)
(* --- alias / union --- *)
AliasDecl ::= "alias" Ident GenericParams? "=" RefinedType (* sinônimo transparente; parametrizado com GenericParams (§10, ex. alias StringTrie[V] = Trie[byte, V]); RefinedType permite um refinamento @where no fim (§21); leva '=' (§2) *)
RefinedType ::= Type Decorator* (* referência-por-tipo: i64 @where(i64 >= 0) — o valor é nomeado pelo tipo, como @ensures(u8 > 0) (§21) *)
| "(" Ident ":" Type ")" Decorator* (* slot nomeado: (xs: List[T]) @where(xs.len > 0) — liga o nome do valor, como um retorno nomeado -> (lo: u8) (§21) *)
UnionDecl ::= "unsafe"? "union" Ident "{" FieldList "}" (* union é sempre unsafe (§2/§5) *)
MemberSep ::= ";" | Newline (* corpo em '{}' → ';' ou Newline; NUNCA ',' *)

decl State { Idle; Running; Done } · decl Point { x: int; y: int } · decl Shape { Circle(int); Square }: membros separados por ; (ou Newline em multilinha). Contraste com fn f(a: int, b: int), em que os argumentos em () separam por ,.


Binding ::= BindKw? BindTarget BindRhs
BindKw ::= "let" | "var" | "const" (* ausente = atalho imutável (':=') *)
BindTarget ::= BindNames (* a, b (sem parênteses) *)
| "(" BindNames ")" (* (a, b) (com parênteses) *)
BindNames ::= BindName ("," BindName)* (* nomes = grupo-ish → vírgula *)
BindName ::= Ident | "_" (* '_' descarta (§14) *)
BindRhs ::= ":=" ExprList (* inferido: x := v | a, b := 0, 1 *)
| ":" Type "=" Expr (* tipado (estilo Odin): x: T = v (§6/§14) *)
ExprList ::= Expr ("," Expr)*

Semântica (anotada): := (ou : T =) declara; = pelado só atribui a um var. _ := Expr é a única forma de descartar. const x := v ≡ comptime let. a, b := f() ≡ (a, b) := f() (casam por posição).


Type ::= PointerType | ManyPtrType | SliceType | ArrayType | TupleType | FunctionType
| ChannelType | ErrorSetType | VariantSetType | GenericType
| QualifiedType | NamedType
PointerType ::= "*" Type (* um só ponteiro; sem *const/*mut (§6) *)
ManyPtrType ::= "[" "*" "]" Type (* [*]T, ponteiro-de-muitos: N contíguos, sem comprimento; indexar é unsafe (§6) *)
SliceType ::= "[" "]" Type (* []T = [*]T + comprimento (§6) *)
ArrayType ::= "[" (Expr | "_") "]" Type (* [N]T (N comptime) | [_]T (tamanho inferido do literal, §6) *)
TupleType ::= "(" Type ("," Type)+ ")" (* (a, b): grupo em '()' → vírgula (§7) *)
FunctionType ::= "fn" "(" TypeList? ")" ("->" Type)? (* fn(A) -> B (§9/§10) *)
ChannelType ::= "Channel" "[" ChannelDir ("," ChannelDir)* "]"
ChannelDir ::= "<-" Type | "->" Type | Type (* <-T envio, ->T recebo (§4) *)
ErrorSetType ::= "error" ("{" SetMembers "}")? (* 'error' = ABERTO; 'error{…}' = FECHADO (§7) *)
VariantSetType ::= NamedType "{" SetMembers "}" (* Event{Send, Receive}, em POSIÇÃO DE TIPO (§7) *)
SetMembers ::= (SetMember ("," SetMember)*)? (* grupo-ish → vírgula; VAZIO = conjunto fechado vazio = desabitado: error{} = "não erra" (§7) *)
SetMember ::= Ident (* uma variante / tag *)
| Ident "..." (* spread, '…' é POSTFIX: 'nome...' (§7) *)
GenericType ::= (QualifiedType | NamedType) "[" TypeArg ("," TypeArg)* "]" (* List[int], Result[(int,int), E], box.Box[int] — base qualificada por módulo também (§15) *)
TypeArg ::= Type | Expr (* tipo OU valor comptime (ex.: [N]) *)
QualifiedType ::= Ident ("." Ident)+ (* c.gcc.int, modulo.Tipo; instancie uma genérica qualificada como QualifiedType "[" … "]" *)
NamedType ::= Ident | PrimitiveType
TypeList ::= Type ("," Type)*
PrimitiveType ::= IntType | FloatType
| "bool" | "string"
| "byte" | "bit" | "codepoint" | "grapheme" | "char" (* byte=u8, bit=u1, char=grapheme: apelidos builtin (§14/§16) *)
| "int" | "uint" | "isize" | "usize"
| "type" (* o kind, valor de compilação (§10) *)
| "noreturn" (* tipo bottom, divergência; encaixa em qualquer contexto (§14) *)
IntType ::= ("i" | "u") DecInt (* iN/uN, N ∈ 1..65535 (§14) *)
FloatType ::= "f16" | "bf16" | "f32" | "f64" | "f128" | "f256" | "float"

Desambiguação Nome{…} (§7). Em posição de tipo, Nome{…} é VariantSetType (Event{Send}). Em posição de valor, é StructLit (Vec2{1; 2}). A posição separa; o kind reforça. error{…} é sempre ErrorSetType (error é keyword contextual: nomeia o domínio de erro builtin em posição de tipo e na construção error.Tag, mas não é reservada fora disso). Note que as formas de set são group-ish de posição-de-tipo e separam membros por vírgula (error{A, B}, Event{Send, Recv}), como um array literal []int{1, 2, 3} (grupo-de-valor), e diferente de um struct literal Vec2{1; 2} (campos distintos, ;): o conteúdo decide o separador, não a chave (§1).

Builtin que não são keywords (moram aqui, §27): Result[T,E], Optional[T], Ptr, RawPtr, Generator[T], Duration.


Block ::= "{" (Stmt MemberSep)* Stmt? "}"
Stmt ::= Binding
| ComptimeStmt
| Assignment
| LoopStmt | IfStmt | MatchStmt
| ReturnStmt | BreakStmt | ContinueStmt | YieldStmt
| DeferStmt | SpawnStmt
| UnsafeStmt
| StandaloneDecorator
| FnDecl (* função ANINHADA: só escopo, sem captura (§14) *)
| ExprStmt
ComptimeStmt ::= "comptime" (Binding | Block | IfStmt | LoopStmt | MatchStmt) (* comptime ortogonal sobre binding/bloco/if/loop/match; comptime fn está no FnDecl (§10) *)
(* --- atribuição (≠ binding: '=' pelado, só p/ var) --- *)
Assignment ::= LValue AssignOp Expr
| LValueList "=" ExprList (* a, b = b, a: swap/paralelo (§14) *)
AssignOp ::= "=" | CompoundAssign (* = | += -= *= /= %= &= |= ^= <<= >>= *)
LValue ::= Ident | LValue "." Ident | LValue "[" Expr "]" | "*" LValue
LValueList ::= LValue ("," LValue)*
ReturnStmt ::= "return" Expr?
| "return" MatchExpr (* 'return' distribuído (§14); `if` é statement-only, use o ternário para valor *)
BreakStmt ::= "break" Ident? (* LABEL de LOOP opcional, nunca valor (§14); um loop entrega via return *)
ContinueStmt ::= "continue" Ident? (* label de loop opcional *)
YieldStmt ::= "yield" Expr (* generators (§11) *)
DeferStmt ::= "defer" Expr (* cleanup LIFO (§14) *)
(* --- loop unificado (§14) --- *)
LoopStmt ::= (Ident ":")? "loop" LoopHeader? Block (* label Go-style opcional; também usável em posição de valor (Primary), entregando via return *)
LoopHeader ::= Expr (* condição booleana → while *)
| Ident "in" Expr (* IDENT in iterável/range → for-each *)
| Binding ";" Expr ";" SimpleStmt (* C-style; ';' explícito delimita as 3 partes *)
SimpleStmt ::= Assignment | ExprStmt
RangeExpr ::= Expr ".." Expr | Expr "..=" Expr (* '..' exclusivo, '..=' inclusivo *)
IfStmt ::= IfExpr
IfExpr ::= "if" Expr Block ("else" (IfExpr | Block))?
SpawnStmt ::= "spawn" (CallExpr | Block) ErrorTrailer? (* spawn f(x) catch|e|{} | spawn { … } *)
UnsafeStmt ::= "unsafe" Block (AssertTrailer | AssumeTrailer)? (* 'unsafe expr' aparece como Unary em Expr *)
StandaloneDecorator ::= Decorator (Binding | Block)? (* @mm(arena), @state x := v, @effect { … } *)
ExprStmt ::= Expr
MatchStmt ::= MatchExpr

Precedência do mais alto (liga mais forte) ao mais baixo. Adotada a do Go, a mais comum entre Zig/Go/Odin/Erlang/Gleam (Odin e Erlang concordam no agrupamento bitwise; só o Zig separa). Propriedade universal nas 5: comparação fica abaixo de bitwise.

Nível Operadores
1 postfix/acesso x.y x.f() x() x[i] x as T x@decorator
2 unário (prefixo) -x !x ~x *x(deref) &x(addr)
3 multiplicativo * / % << >> &
4 aditivo + - | ^
5 comparação == != < > <= >=
6 E lógico &&
7 OU lógico ||
8 ternário (mais baixo) ? :
(atribuição é statement, fora da escada de expressão)
Expr ::= Ternary
Ternary ::= LogicalOr ("?" Expr ":" Expr)? (* cond ? a : b (§14) *)
LogicalOr ::= LogicalAnd ("||" LogicalAnd)*
LogicalAnd ::= Comparison ("&&" Comparison)*
Comparison ::= Additive (CompareOp Additive)* (* string usa estes byte-a-byte; semântica Unicode por método (§16) *)
Additive ::= Multiplicative (("+" | "-" | "|" | "^") Multiplicative)* (* '+' concatena string *)
Multiplicative ::= Unary (("*" | "/" | "%" | "<<" | ">>" | "&") Unary)*
Unary ::= UnaryPrefix* Postfix
UnaryPrefix ::= "-" | "!" | "~" | "*" | "&" (* *deref; &addr *)
Postfix ::= Primary PostfixOp*
PostfixOp ::= "." Ident (* campo / método sem args *)
| "." Ident "(" ArgList? ")" (* método via UFCS: x.f(y) ≡ f(x, y) (§14) *)
| "(" ArgList? ")" (* chamada *)
| "[" Expr "]" (* indexação *)
| "[" Expr? (".." | "..=") Expr? "]" (* slice VIEW, aliasa o backing; bounds opcionais (§6) *)
| "[" Expr? ":" Expr? "]" (* slice COPY, independente O(n); bounds opcionais (§6) *)
| "[" TypeArg ("," TypeArg)* "]" (* args comptime: collect[List]() (§10) *)
| "as" Type (* conversão (§2/§16) *)
| ValueDecorator (* result @transfer (§5) *)
| ErrorTrailer (* catch|e| / match|e| / assert / assume (§5/§7) *)
| ChannelTrailer (* <- / <-> com timeout (§4) *)
Primary ::= Literal | BuiltinValue | Ident
| EnumShorthandLit (* .Variante: enum inferido do tipo esperado (§7, §16) *)
| "(" Expr ")" (* agrupamento *)
| TupleLit | StructLit | ArrayLit
| MatchExpr | LoopStmt | RangeExpr (* match e loop são formas-valor (entregam via return); `if` NÃO está aqui: é statement-só, sua forma-valor é o ternário `c ? a : b` (§14). `Lambda` também NÃO está aqui: só é alcançável por `Arg`, e é isso que faz "só como argumento direto" (§6/§14) ser um fato da GRAMÁTICA, não uma regra do checker *)
| ReceiveExpr
EnumShorthandLit ::= "." Ident (* literal de enum ponto-líder: `.Red`, `.semantic`; enum inferido do contexto (estilo Zig). Só no início de expr; `x.campo` é um PostfixOp. Não-inferível = erro. *)
TupleLit ::= "(" Expr ("," Expr)+ ")" (* (a, b): grupo em '()' → vírgula *)
StructLit ::= StructHead "{" (FieldInits | FieldVals)? "}" (* Vec2{x: 1; y: 2} | Vec2{1; 2}: campos distintos → MemberSep (§1) *)
StructHead ::= (QualifiedType | NamedType) ("[" TypeArg ("," TypeArg)* "]")? (* Vec2 | Box[int] | box.Point | box.Box[int]: local ou qualificada por módulo, simples ou genérica (§15) *)
FieldInits ::= FieldInit (MemberSep FieldInit)*
FieldVals ::= Expr (MemberSep Expr)* (* struct literal posicional: campos distintos, não grupo de valores *)
FieldInit ::= Ident ":" Expr
ArrayLit ::= (SliceType | ArrayType) "{" (Expr ("," Expr)*)? "}" (* []int{1, 2, 3} | [3]int{1, 2, 3} | [_]int{1, 2, 3}: elementos são GRUPO-DE-VALOR → vírgula (§1) *)
ArgList ::= Arg ("," Arg)*
Arg ::= "mut"? Expr | Lambda (* 'mut' no call site (§6); lambda como arg *)
Lambda ::= "fn" "(" ParamList? ")" ("=>" Expr | Block) (* SÓ como argumento direto (§14) *)
(* --- channels (§4) --- *)
ReceiveExpr ::= "->" Expr (* receive: data := -> chan (prefixo, posição de valor) *)
ChannelTrailer ::= "<-" Expr (* send: chan <- data *)
| "<->" Expr "timeout" "(" Expr ")" (* sync req-reply; timeout OBRIGATÓRIO *)
ValueDecorator ::= "@" ("transfer" | "pin" | "unpin")
| "@" "promote" "(" DecoratorArgs ")" (* node @promote(gc, deep) *)

[decisão: resolvida] Separador por conteúdo, não por bracket. Um struct literal é um corpo de campos distintos, então separa por ; ou Newline (Vec2{1; 2} ou a forma multi-linha). Um array/slice literal é um grupo-de-valor de elementos homogêneos, então separa por , ([]int{1, 2, 3}), igual aos outros grupos-de-valor (argumentos, tuplas, membros de set). Os dois são delimitados por { }, mas o conteúdo decide o separador, não a chave: membros distintos (campos, variantes, braços, statements) levam ;/Newline; um grupo-de-valor posicional leva ,. (Isso refina um fraseado anterior “todo corpo { } usa ;”, que era grosso demais: elementos de array não são campos distintos.)


MatchExpr ::= "match" Expr "{" MatchArms "}" (* COM sujeito → switch/pattern (§14) *)
| "match" "{" SelectArms "}" TimeoutTrailer? (* SEM sujeito → select (§4) *)
ErrorTrailer ::= "match" "|" Ident "|" "{" MatchArms "}" (* op match |e| { }, ramifica (§7) *)
| "catch" "|" Ident "|" Block (* op catch |e| { }, erro como unidade (§7) *)
| AssertTrailer
| AssumeTrailer
AssertTrailer ::= "assert" "(" Expr ")" (* checa: runtime verifica, panic se falsa (§5) *)
AssumeTrailer ::= "assume" StringLit (* confia: afirma sem check e sem licença p/ UB (§5) *)
MatchArms ::= MatchArm (MemberSep MatchArm)* MemberSep?
MatchArm ::= Pattern "=>" ArmBody
ArmBody ::= Block | ReturnStmt | BreakStmt | ContinueStmt | ExprStmt
SelectArms ::= SelectArm (MemberSep SelectArm)* MemberSep?
SelectArm ::= (BindName ":=")? ReceiveExpr "=>" ArmBody (* data := -> chan_a => handle(data) *)
TimeoutTrailer ::= "timeout" "(" Expr ")" Block (* } timeout(5s) { … }; ausente=infinito; timeout(0)=poll *)
Pattern ::= SubPattern ("|" SubPattern)* (* or-pattern 'A | B' (§14) *)
SubPattern ::= "_" (* wildcard, SÓ domínio aberto (§7/§14) *)
| Literal
| "Ok" "(" Pattern ")" | "Err" "(" Pattern ")"
| VariantPattern
| Ident (* binding, ou variante sem payload *)
VariantPattern ::= QualName ("(" PatternList ")")? (* error.NotFound | Send(m) | Click(x, y) *)
QualName ::= Ident ("." Ident)*
PatternList ::= Pattern ("," Pattern)*

Os braços de match/select usam MemberSep (; ou Newline), a mesma regra de {} das declarações. Nunca vírgula. Exaustividade: domínio fechado (enum, error{…}, conjunto de variantes, whitelist) exige todas as variantes que o tipo admite e proíbe _; domínio aberto (int, string, error pelado) exige _.


Decorator ::= "@" Ident ("(" DecoratorArgs ")")? (* com args → '()'; tag pura → sem (§14) *)
DecoratorArgs ::= DecoratorArg ("," DecoratorArg)* (* args = grupo em '()' → vírgula *)
DecoratorArg ::= Ident ":" Expr (* nomeado: strategy: rest_for_one *)
| Expr (* posicional: arena, gc, none, 16mb, x86 *)

Posições (do §5/§14/§20). Prefixo de declaração (@repr(c) decl …, @component fn …, @test fn …, @supervisor(cfg) spawn …); postfix de valor (result @transfer, node @promote(gc, deep)); statement solto / binding reativo (@mm(arena), @state x := v, @effect { … }); em parâmetro/campo (value: *Record @mm(c), motor: Motor @embeds(methods)).

@generator é decorador comum. Pode vir acima da função (linha própria) ou inline antes do fn, como qualquer outro decorador, capturado por Decorator* em Item. Não há slot fixo no header de FnDecl (a ordem pub? comptime? @generator? unsafe? fn do doc é só convenção de escrita).


ModuleDecl ::= "module" ModulePath (* no TOPO do arquivo, estilo 'package' do Go/Odin *)
UseDecl ::= "use" ModulePath (* use json → json.parse(…) *)
| "use" ModulePath "." "{" Ident ("," Ident)* "}" (* use json.{parse, decode} *)
| "use" ModulePath "as" Ident (* use json as j *)
| "use" ModulePath "." Ident "(" ArgList? ")" (* inline: use json.parse(data) *)
ModulePath ::= Ident ("." Ident)*
GenericParams ::= "[" GenericParam ("," GenericParam)* "]"
GenericParam ::= TypeParam | ValueParam (* um parâmetro de tipo OU um parâmetro de valor comptime *)
TypeParam ::= Ident HKTParams? ConstraintSpec? (* T | T: {…} | T + I | C[_] | C[_: {…}]: {…} + I *)
ValueParam ::= Ident ":" Type (* um valor comptime de QUALQUER tipo: [n: usize], [flag: bool], [p: Point] (§10).
O Type pode nomear um type-param ANTERIOR → value-param dependente, ex. [A, x: A] (§21, D1).
Desambiguação: ':' seguido de '{' é whitelist num TypeParam; ':' seguido de um Type é um ValueParam. *)
HKTParams ::= "[" "_" ConstraintSpec? "]" (* C[_] não-aplicado; ConstraintSpec aqui = constraint do ELEMENTO (§10) *)
ConstraintSpec ::= Whitelist ("+" Interface)* (* régua estrita §10: ':' SÓ whitelist (pertencimento), *)
| ("+" Interface)+ (* '+' SÓ interface (implementação): nome não diz qual, daí 2 símbolos *)
Whitelist ::= ":" "{" TypeList "}" (* ': {List, Set}': pertence ao conjunto *)
Interface ::= NamedType (* '+ Writeable', implementa a interface *)

module path declara, no topo do arquivo, a que módulo o arquivo pertence (igual package do Go). O sistema de resolução (árvore, paths, visibilidade) está no §15 do design doc.


Resolvidas nesta rodada (todas confirmadas por você):

  • Literais numéricos: 4 bases (0b/0o/0x/decimal) + separador _.
  • Separadores (por conteúdo, não por bracket): grupos-de-valor , (args, tuplas, membros de set, literais de array/slice []int{1, 2, 3}); corpos de membros distintos ;/Newline (corpos de decl, variantes de enum, braços de match, blocos, literais de struct Vec2{1; 2}). Os dois literais usam { }; o conteúdo decide (§1).
  • Operadores booleanos: && || ! (grafia de símbolo).
  • Precedência: a do Go (a mais comum).
  • Bitwise + compostos: & | ^ ~ << >>; compostos += -= *= /= %= &= |= ^= <<= >>=.
  • none: valor builtin (Optional vazio + estratégia de @mm), não keyword.
  • @generator: decorador comum (acima ou inline), sem slot fixo.
  • module: declaração no topo do arquivo (estilo package do Go).
  • ASI: disparada só por Newline; ; explícito para uma-linha.
  • -> receive vs -> tipo de retorno: mesmo glifo, desambiguado por posição (expressão de channel vs assinatura).

Resolvidas também nesta rodada:

  • ++ / --: removidos. Incremento/decremento não entram na linguagem (usa += 1 / -= 1), alinhado com as 5 referências e com a explicitude do doc.
  • Separadores no design doc: sincronizados. Os corpos de decl (e o union) passaram a ;, e a nota do §14 foi ajustada; gramática e doc batem de novo.
  • @generator no doc: nota de decorador adicionada (acima ou inline, sem slot fixo).
  • Unidades de duração: confirmadas as 6 (ns ms s m h d).

Resolvidas nesta rodada (revisão pós-feedback):

  • Régua estrita de constraint: : introduz só whitelist {…} (pertencimento); + introduz só interface (implementação). T: Display virou T + Display. HKT: constraint do elemento dentro de [_ …], constraint do container depois de ]; todas as formas derivam de GenericParam ::= Ident HKTParams? ConstraintSpec?. Motivo: um nome não distingue “é o tipo Foo” de “implementa Foo”, então dois símbolos.
  • assume (nova keyword, 32 no total): assert agora é só a forma checável assert (cond) (checa, panic se falsa); a forma fraca (afirmar sem checar, sem licença para UB) virou assume "razão". Dois trailers distintos (AssertTrailer / AssumeTrailer).
  • Operadores em string: liberados byte-a-byte: ==/!= (igualdade de bytes), </>/<=/>= (ordem lexicográfica). Só s[i] continua banido (unidade ambígua); semântica Unicode (normalização, collation) por método.
  • Tupla nomeada no retorno: -> (lo: u8, hi: u8) vale só em posição de retorno (rótulos de slot para @ensures), não como tipo-tupla nomeado geral.
  • Sintaxe de extensão fica fora desta gramática: esta gramática descreve só o core (.mko). Uma extensão de compilador (seção 20 do design doc) pode trazer sua própria gramática para os tipos de arquivo que registra (ex.: o markup do .mkoui) e definir decoradores próprios; nada disso entra aqui. O core é fechado; o que cada extensão adiciona é especificado por ela.

As construções de verificação do core. A maior parte da camada é decorador comum, capturado por Decorator* sem produção nova: @must_consume/@consume_once (posse nível-de-tipo num decl), @pure/@total (cercas), @proof (a cerca de soundness sobre uma família dependente ou função). O que adiciona gramática é triplo, e tudo reusa formas existentes: refinamentos (RefinedType, §2), os deltas de tipos dependentes (ValueParam em GenericParam, o Kind no cabeçalho do decl, e a Variant/ResultVariant que refina índice, todos §2), e protocolos de sessão (abaixo).

(* --- protocolos de sessão: o decorador @protocol seleciona um corpo-de-passos para o decl (§21) --- *)
ProtocolDecl ::= "@protocol" "decl" Ident GenericParams? "{" ProtoBody "}" (* @protocol faz o '{}' ser um ProtoBody, não um DeclBody *)
ProtoBody ::= ProtoStep (MemberSep ProtoStep)* MemberSep?
ProtoStep ::= Type ("@sends" | "@receives") (* postfix: Credentials @sends, Token @receives (prefixo '@sends Type' também aceito) *)
| ("@sends" | "@receives") "match" "{" ProtoArms "}" (* @sends match = eu escolho (escolha interna); @receives match = o peer escolhe (externa) *)
| "loop" "{" ProtoBody "}" (* recursão *)
| "continue" (* volta ao loop envolvente *)
| "_" (* fim do protocolo *)
ProtoArms ::= ProtoArm (MemberSep ProtoArm)* MemberSep?
ProtoArm ::= Ident "=>" (("{" ProtoBody "}") | ProtoStep | "_")
(* --- dual: o endpoint espelhado; um operador-de-tipo comptime, junta-se às formas de Type --- *)
DualType ::= "dual" Type (* Channel[dual Auth]: troca todo @sends ↔ @receives, inclusive @sends match ↔ @receives match (§21) *)

DualType é uma alternativa adicional de Type (um operador-de-tipo prefixo, como um builtin sobre tipos), então dual Auth é válido onde quer que um tipo seja esperado — mais utilmente Channel[dual Auth], onde o contrato-no-spawn (§4 do design) checa que os dois endpoints são complementos.

Nenhuma keyword cresce. A camada toda reusa match, loop, continue, _ e fn, mais o namespace do @ para seus decoradores; o conjunto de 32 keywords (§7 acima) não muda. type ganha o universo-irmão prop no Kind (ambos builtins minúsculos, como int/bool), não uma keyword nova.

@by é um delta de core diferido, ainda não gramaticalizado aqui. O bloco de tática @by { … } (§21) é um bloco com goal-threading portado por decorador — semântica de bloco nova, logo um delta de core, mas realizado só na era self-hosting (quando use compiler expõe o AST como tipo de library). É nomeado aqui para não ser esquecido; sua produção é adicionada quando ele chegar.

Model checking é uma extensão, então sua gramática fica fora. @spec e a library temporal spec.* (§21) pertencem à extensão de model-checking, e pela nota de fechamento do §10 acima, a gramática de uma extensão não faz parte do core e não aparece neste documento.

Resolvido nesta rodada (a camada de verificação, §21 do design):

  • Posse @must_consume / @consume_once: decoradores nível-de-tipo no decl, sem produção nova.
  • Refinamento @where: RefinedType no RHS de um alias (e via o Decorator* no fim/início de um campo/param) — referência-por-tipo ou um slot nomeado (x: T), reusando o device de retorno-nomeado do §14. Sem binder mágico.
  • Value-params, generalizados (D1): GenericParam ::= TypeParam | ValueParam; um ValueParam é um valor comptime de qualquer tipo, cujo tipo pode nomear um type-param anterior (dependente). Isso também fecha uma lacuna anterior — value-params eram usados no design doc mas não estavam na produção.
  • Kind no cabeçalho do decl (D2): ("->" Kind)? no TypeDecl; Kind ::= "type" | "prop". -> reusa “produz”; : continua significando binding de campo/param só.
  • Variante que refina índice (D3): Variant ganha Ident ":" ResultVariant (tipo-resultado nulário, ou um construtor fn[…](…) -> …), válido só sob um head dependente.
  • Sessões: @protocol decl com um ProtoBody de passos @sends/@receives, match para escolha (direção = quem escolhe), loop/continue/_; dual como operador-de-tipo. Só um Channel tipado por um protocolo é percorrido como sessão.
  • Definitional equality (D4) e Σ/Π (D5) são poderes de checker/tipagem, não gramática de superfície; não adicionam produções.