Semântica — Modelo de Execução
Status: Stable (exceto onde rotulado) · Evidência: aura/parser/to_ast.py (parse_module_decl, parse de import), aura/transpiler/transformers/statements.py (transform_Module, transform_ImportStmt, transform_IfStmt, transform_TryStmt), aura/transpiler/rules.py (RuleChecker), aura/transpiler/semantics.py (MutabilityChecker), aura/cli.py (cmd_run, _prepare_entrypoint).
Este documento define o significado de um programa Aura — o que acontece quando ele roda. Aura é transpilada para Python e executada no CPython, então parte do comportamento observável vem do host e não da linguagem; esses pontos são rotulados Target-specific ou UNSPECIFIED em vez de escondidos.
1. Início e fim da execução
- Um programa é um arquivo de entrada, executado com
aura run. O runtime parseia, checa, transpila para Python e fazexecdo resultado no CPython (cmd_run,cli.py:480-552). - O arquivo de entrada deve declarar um
mainde nível superior; o runtime o chama. O programador nunca escreve uma chamadamain()à direita (_prepare_entrypoint,cli.py:388-468). mainpode sersyncouasync; umasync def mainé aguardado dentro de um event loop (cmd_run,cli.py:526-543).- Os statements de
mainexecutam sequencialmente, na ordem da fonte. - Se
mainretorna umint, ele vira o código de saída do processo; caso contrário o programa sai com 0 (_prepare_entrypoint,cli.py:463-467). - Um
guard cond else { return }de nível superior reescreve oreturnnu pararaise SystemExit(...), quecmd_runtrata como uma saída bem-sucedida (statements.py:1137-1160,cli.py:554-562).
def main() -> int {
return 3 // process exit code 3
}
Evidência: test_main_entrypoint.py::test_main_return_value_is_exit_code, ::test_runtime_invokes_main, ::test_async_main_is_awaited.
1.1 Regras de assinatura
| Ponto de entrada | Resultado |
|---|---|
def main() { } | OK |
async def main() { } | OK |
def main(args: [string]) { } | OK; args recebe os argumentos da CLI |
main ausente no arquivo de entrada | E310 |
main com qualquer outra assinatura | E311 |
def main dentro de um corpo de module | E312 |
Evidência: rules.py:_check_main §162-205; test_main_entrypoint.py::test_missing_main_is_rejected, ::test_main_with_two_params_is_rejected, ::test_main_with_variadic_is_rejected; test_module_facade.py::test_main_inside_a_module_reports_e312.
mainpode receber nenhum parâmetro, ou um único parâmetroargs; um parâmetro variádico ou que não sejaargsé E311 (rules.py:191-205).aura testroda arquivos de teste comrequire_main=False, já que um arquivo de teste se autogoverna.
2. Ordem de avaliação
- Operandos binários são avaliados da esquerda para a direita.
- Argumentos de chamada são avaliados em ordem, esquerda→direita.
and/ortêm curto-circuito: o lado direito não é avaliado quando o esquerdo decide o resultado.- Atribuição: o lado direito é avaliado antes da escrita no lado esquerdo.
- Encadeamento postfix (
a.b().c()[d]) é avaliado da esquerda para a direita, receptor antes do membro.
let r = mark(1) + mark(2) // prints 1 then 2
let s = false and side() // side() never runs
Evidência: probe (1\n2\n, e nenhuma saída de side()); expressions.md.
3. Escopo e tempo de vida
- Um bloco
{ … }abre um escopo; declarações vivem até o fim do bloco (_visit_body,rules.py:1249-1268;MutabilityChecker._with_scope,semantics.py:492-497). - Um corpo de função é seu próprio escopo; parâmetros são bindings locais e são tratados como mutáveis (
_visit_function,semantics.py:287-317). - Um corpo de módulo é seu próprio escopo (
rules.py:623-631; §1.2 de modules.md).
3.1 let / const / let mut
| Declaração | Reatribuível |
|---|---|
let x = 1 | não |
let mut x = 1 | sim |
const K = 1 | não (nunca) |
| parâmetro de função | sim (um binding local) |
self.x = … / obj.field = … | não afetado (muta um objeto) |
variável de for, binding de with | tratados como mutáveis |
Evidência: docstring do módulo semantics.py §1-17; _visit_var_decl/_visit_const_decl §275-285; test_semantics_deep.py, test_language_rules.py.
let x = 1
x = 2 // E303: reassign an immutable binding
let mut total = 0
total += 1 // ok
const PI = 3.14
PI = 3 // E303
3.2 Uso antes da declaração
Ler um nome que a mesma função declara depois em seu próprio corpo é E319 — o caso que de outra forma seria um UnboundLocalError do Python (_check_use_before_declaration, semantics.py:237-262). Nomes de um escopo envolvente, do escopo de módulo ou de um import não são afetados (_locals_declared_in, §319-335).
def f() {
print(x) // E319: 'x' is used before it is declared
let x = 1
}
Evidência: test_rule_gaps.py (E319), language-reference/ §3.
3.3 Tempo de vida e memória
Bindings locais let/const têm tempo de vida de bloco. O tempo de vida de um objeto é gerenciado pelo host (contagem de referências + GC cíclico do CPython). A linguagem não especifica quando um objeto é coletado — UNSPECIFIED (intencional; é o GC do target).
3.4 Closures
Uma lambda que lê um local envolvente o captura; uma lambda de bloco que atribui a um local envolvente emite nonlocal <name> para que a escrita seja visível fora (_nonlocal_declaration, expressions.py:711). Um def aninhado que muta um local envolvente também emite nonlocal.
def main() {
let mut n = 0
let inc = () => { n = n + 1 }
inc(); inc()
print(n) // 2
}
Evidência: probe (2); test_main_entrypoint.py::test_nested_function_mutates_captured_local; closures.md §2.
UNSPECIFIED: a captura é por referência através do protocolo de closure do CPython, não um snapshot por iteração. Uma lambda criada em um loop observa o valor final da variável do loop. Probe:
for i in range(3) { fs.add((x) => x + i) }dáfs[0](0) == 2. Vincule o valor como um parâmetro default para captura por iteração (closures.md §2.3).
4. Semântica de valor vs referência
| Tipo | Modelo | == |
|---|---|---|
Números, bool | valor | igualdade numérica |
string | valor por conteúdo (imutável) | conteúdo |
| instância de classe | referência (identidade) | identidade por padrão |
| list / dict / set | referência (objeto mutável) | identidade por padrão |
| valor struct / em forma de dict | conteúdo | por elemento |
A passagem para uma função é por valor; para uma referência esse valor é a referência, então mutar o objeto é visível ao chamador enquanto reatribuir o parâmetro não é.
def mutate(xs) { xs.add(9) }
def reassign(x) { x = 9 }
def main() {
let a = [1]
mutate(a)
print(a) // [1, 9]
let n = 1
reassign(n)
print(n) // 1
}
Evidência: probe ([1, 9], 1); expressions.md, types.md.
5. Exceções
Aura throwa um valor; no CPython o valor é levantado (transform_ThrowStmt, statements.py:1293-1303).
| Forma | Emite |
|---|---|
throw "msg" | raise Exception("msg") (uma string nua é embrulhada) |
throw ValueError("bad") | raise ValueError('bad') |
catch { } | except Exception: |
catch Type { } | except Type: |
catch Type as e { } | except Type as e: |
catch as e { } | except Exception as e: |
finally sempre roda. Uma exceção não capturada aborta o programa com uma mensagem e um código de saída não zero (cmd_run, cli.py:563-573).
Evidência: test_syntax_complete.py::test_throw_string_wrapped, ::test_try_catch_finally, test_custom_errors.py.
TARGET-SPECIFIC: uma exceção é um objeto CPython. Cláusulas
catchsão emitidas na ordem da fonte e o filtro de tipo é o que o CPython resolver para o nome; Aura não reordena subclasses antes dos pais. UNSPECIFIED:throwde um valor que não é string nem exceção (throw 5) é emitido comoraise 5e falha em runtime; não é rejeitado em tempo de check (statements.md §8).
6. Semântica específica do target (CPython)
Como o target é CPython, estes comportamentos vêm do host:
| Comportamento | Regra |
|---|---|
| Tipo inteiro | int do Aura mapeia para o int de precisão arbitrária do Python. |
| Float | IEEE-754 binary64, como o CPython. |
% com operando negativo | floor-mod do Python: probe -7 % 3 == 2. |
/ | Divisão verdadeira (float), como o CPython. |
| Iteração | Protocolo de iterador do CPython; um for sobre range/list/dict o segue. |
| Ordenação de dict | Ordem de inserção (CPython 3.7+). |
| GC / tempo de vida de objeto | Contagem de referências + GC cíclico do CPython. Unspecified quando coletado. |
| Erros de runtime | Aparecem como a exceção Python subjacente (AttributeError, TypeError, …) reportada por cmd_run. |
Evidência: probe (-7 % 3 → 2); cmd_run reporta o tipo e a mensagem da exceção (cli.py:566-573).
7. Regras de linguagem aplicadas por aura check / aura run
aura check, aura run e o REPL rodam os mesmos checkers antes da geração de código (_mutability_diagnostics, cli.py:18-40; _rule_diagnostics, cli.py:43-62). As regras são:
| Regra | Código | Aplicada em |
|---|---|---|
Reatribuir um binding let/const | E303 | semantics.py:403-430 |
| Atribuir estado de módulo de fora (incl. um membro exportado) | E303 | rules.py:1308-1316 |
Atribuir um const de nível de classe através de um membro | E303 | rules.py:1317-1325 |
| Declaração duplicada no mesmo escopo | E301 | _declare, rules.py:581-592 |
| Nome de parâmetro duplicado | E301 | rules.py:1215-1228 |
return fora de uma função (exceto um guard de nível superior) | E004 | rules.py:706-714 |
break / continue fora de um loop | E004 | rules.py:715-723 |
break/continue nomeando um label não envolvente | E318 | rules.py:724-734 |
await fora de uma função async (ou top level) | E004 | rules.py:746-757 |
yield fora de uma função | E004 | rules.py:758-771 |
self fora de um método de classe | E004 | rules.py:792-799 |
self/cls dentro de um método static | E317 | rules.py:800-809 |
Código inalcançável após return/throw/break/continue | E302 | rules.py:1249-1268 |
| Alvo de atribuição inválido | E004 | rules.py:1272-1284 |
| Ler um local de função antes de sua declaração | E319 | semantics.py:237-262 |
main ausente / inválido / dentro de um módulo | E310 / E311 / E312 | rules.py:162-265 |
| Re-export de módulo não resolvido | E313 | rules.py:439-467 |
let count = 0
count = 1 // E303: reassign an immutable binding
let mut total = 0
total += 1 // ok
guard total > 0 else { return } // exits the program if the guard fails
Evidência: test_language_rules.py, test_extreme_rules.py (E302), test_rule_gaps.py (E318, E319), test_main_entrypoint.py, test_module_facade.py (E313).
7.1 Regras de posicionamento (detalhe)
returné válido dentro de qualquer função. No nível superior é um erro a menos que fique diretamente em um blocoguard … else { return }, que é o idioma para “sair do programa” (rules.py:706-714;_in_guard_else_depth).break/continuesão válidos apenas dentro de um loop. Um simples aponta para o loop mais interno; um rotulado deve nomear um loop envolvente (caso contrário E318).awaité válido dentro de umasync def, ou no nível superior, porquecmd_runexecuta um programa async dentro de uma corrotina (rules.py:746-757;cli.py:526-543).selfé válido apenas dentro de um método de um corpo de classe;self/clsdentro de um métodostaticé E317 (rules.py:792-809).
7.2 Código inalcançável (E302)
Um statement que segue return, throw, break ou continue (um terminator, _TERMINATORS, rules.py:74) na mesma lista de statements é E302. O checker reporta uma vez por região e reseta (_visit_body, rules.py:1249-1268).
def f() {
throw "boom";
print("never") // E302
}
Pegadinha: statements são separados pela gramática, e um
return/breakseguido na linha seguinte por uma expressão sem um;é parseado como o valor do terminator, então nenhum código morto é visto. Probe:return\n print(1)parseia comoreturn print(1)(sem E302), enquantoreturn;\n print(1)reporta E302. Use um ponto e vírgula ou um corpo de bloco para desambiguar (statements.md §6.7, §9).
7.3 Alvos de atribuição válidos
Um alvo de atribuição deve ser uma variável, um acesso a membro, um índice ou um alvo de desestruturação (tupla/lista, possivelmente com um padrão rest) (_is_assignable, rules.py:1332-1339). Qualquer outra coisa é E004.
def main() {
1 = 2 // E004: invalid assignment target
}
Evidência: rules.py:1272-1284; probe (E004).
8. Modelo de erro e diagnóstico
Diagnósticos são registros estruturados com um código, uma severidade, uma mensagem, uma localização de fonte opcional, uma dica opcional e localizações relacionadas (AuraError, errors.py:108-144). Os códigos são agrupados por namespace (ErrorCode, errors.py:36-107):
| Prefixo | Significado |
|---|---|
E0xx | léxico / sintaxe |
E1xx | tipo |
E3xx | semântico / estrutural |
E9xx | fatal / interno |
W0xx | warnings de estilo |
W1xx | warnings de não usado / redundante |
Um diagnóstico não é um código estruturado quando vem do parser: o parser levanta um SyntaxError do Python carregando line/column em vez disso (errors.py:55-59; to_ast.py usa self.error).
8.1 Coleta e severidade
ErrorCollectorreúne diagnósticos e para após 10 erros, anexando um resumo fatalE999(errors.py:146-171).- Um warning deve usar um código
W;add_warningrejeita um códigoE(errors.py:173-184), então um diagnósticoWnunca pode se disfarçar de erro. has_errors()é verdadeiro apenas paraERROR/FATAL; warnings não falham um build (errors.py:186-188).
8.2 Exibição e status de saída
O diagnóstico formatado é (errors.py:118-141):
<file>:<line>:<col>: ERROR [E303]
Cannot reassign immutable binding 'x'; ...
hint: write 'let mut x' at its declaration
aura checkimprime todos os diagnósticos; uma falha de regra/mutabilidade/tipo retorna exit 1 (cmd_check,cli.py:200-239).aura runreporta os mesmos diagnósticos e retorna exit 2 antes de executar (cmd_run,cli.py:503-508).- Um erro de runtime é reportado como
Runtime error: <Type>: <message>e retorna exit 1 (cli.py:566-573). - Um
intretornado pormainé o código de saída; uma saída deguardde nível superior nua é 0 (cli.py:554-562).
Evidência: test_diagnostics.py (códigos permanecem em sincronia com docs/ERRORS.md), test_cli_diagnostics.py.
docs/ERRORS.mdé a única fonte de verdade para códigos de diagnóstico; um teste afirma queErrorCodee esse documento permanecem em sincronia (errors.py:39-41,tests/test_diagnostics.py).
9. O que a linguagem NÃO define (resumo)
| Ponto | Estado |
|---|---|
| Quando um objeto é coletado | Unspecified (GC do CPython) |
| Captura por iteração em um loop | Unspecified (a captura é por referência) |
throw de um valor que não é exceção | Unspecified (não rejeitado em tempo de check) |
Ordenação de cláusulas catch | Ordem da fonte; o host resolve os tipos (Target-specific) |
| Resolução de import ambígua | Unspecified (o último binding vence no namespace do target) |
Sinal de %, divisão, largura de inteiro | Target-specific (CPython) |
| Texto de erro de runtime | Target-specific (a exceção Python subjacente) |
10. Referências cruzadas
- statements.md — semântica de statements e a tabela do checker.
- functions.md §9 — o ponto de entrada.
- closures.md — lambdas, captura e
nonlocal. - modules.md — módulos, imports, re-exports, resolução de nomes.
- python-interop.md — a superfície do target CPython.
- ../ERRORS.md — a referência canônica de códigos de diagnóstico.