O analisador semântico recebe o Program (AST) produzido pelo parser e verifica restrições que a gramática não captura: variáveis não declaradas, redeclarações, atribuição a const, incompatibilidades de tipo, acesso a campos inexistentes.
O ponto de entrada público é analyse(prog), que cria um SemanticAnalyser e retorna Vec<CompilerError>. Erros não interrompem a análise — todos são acumulados para relatório completo.
struct SemanticAnalyser {
sym: SymbolTable,
current_fn_ret: Option<QualifierType>, // tipo de retorno da função atual
diagnostics: Vec<CompilerError>,
warnings: Vec<CompilerWarning>,
loop_depth: usize, // para validar break/continue
switch_depth: usize, // para validar break em switch
}O fluxo é:
analyse_program(prog)
└── analyse_decl(decl)*
├── analyse_stmt(stmt)* (dentro de Function)
│ └── analyse_expr(expr)
└── analyse_expr(expr) (inicializadores)
A tabela de símbolos é uma pilha de escopos. Cada escopo é um HashMap<String, Symbol>.
struct Symbol {
name: String,
ty: QualifierType,
mutable: bool, // false se declarado com const
decl_span: Span,
}| Método | Comportamento |
|---|---|
enter_scope() |
Empilha um novo escopo vazio |
exit_scope() |
Desempilha o escopo atual |
declare(symbol) |
Insere no escopo corrente; erro se já existir no mesmo escopo |
lookup(name) |
Busca do escopo mais interno ao mais externo |
lookup_current_scope(name) |
Busca apenas no escopo corrente |
register_struct(name, fields) |
Armazena definição de struct (separado dos símbolos) |
lookup_struct(name) |
Recupera os campos de uma struct |
register_type_alias(name, qty) |
Armazena alias de typedef |
lookup_type_alias(name) |
Recupera o tipo subjacente de um alias |
analyse_program → enter_scope / exit_scope (escopo global)
analyse_decl::Function → enter_scope / exit_scope (escopo da função + params)
analyse_stmt::Block → enter_scope / exit_scope (bloco aninhado)
analyse_stmt::For → enter_scope / exit_scope (init pode declarar variável)
- Analisa o inicializador (se houver)
- Resolve aliases de typedef no tipo
- Chama
declare(symbol)— emiteRedeclarationse duplicado no mesmo escopo
enter_scope- Salva
current_fn_retcom o tipo de retorno resolvido - Declara cada parâmetro no novo escopo
- Analisa todos os statements do corpo
- Restaura
current_fn_ret;exit_scope - Verifica a heurística
body_always_returnsse a função não forvoid; emite avisoMissingReturnse faltar retorno.
Chama register_struct(name, fields) — armazena a definição para uso posterior em acesso a membro.
Cada variante é declarada como um símbolo const int. Variantes com valor explícito têm o inicializador analisado.
Resolve o tipo-base e chama register_type_alias(alias, resolved_type).
Antes de declarar um símbolo, o tipo passa por resolve_type(), que substitui Type::Alias(name) pelo tipo concreto registrado:
const unsigned myint_t x
→ resolve_type → const unsigned int x (se typedef myint_t = int)
A resolução é recursiva para ponteiros e arrays:
myint_t* p → int* p
myint_t[10] → int[10]
Aliases não encontrados são mantidos como Type::Alias (diagnóstico emitido mais tarde quando usados em contexto de tipo).
analyse_expr analisa recursivamente e retorna o tipo inferido da expressão. Tipos não resolvíveis retornam Type::Void como sentinela.
| Literal | Tipo inferido |
|---|---|
IntLiteral(v) |
int |
FloatLiteral(v) |
double |
CharLiteral(v) |
char |
StringLiteral(v) |
char* |
Busca na tabela de símbolos. Se não encontrado → UndefinedVariable(name).
- Verifica se o LHS é
const→AssignToConst(name) - Infere tipos de LHS e RHS
- Verifica compatibilidade via
types_compatible_for_assign→TypeMismatch - Retorna o tipo do LHS
binary_result_type(lhs_ty, op, rhs_ty) define as regras:
| Operador | Regra |
|---|---|
+, - |
numérico OP numérico → promoção; ponteiro ± inteiro → ponteiro |
*, / |
numérico OP numérico → promoção |
% |
inteiro OP inteiro (float proibido) |
&, |, ^, <<, >> |
inteiro OP inteiro |
==, !=, <, >, <=, >= |
num↔num ou ptr↔ptr → int |
&&, || |
escalar OP escalar → int |
Promoção numérica: Double > Float > Long > Int > Short/Char.
- Infere o tipo do objeto
- Para
.: esperaType::Struct(name)— caso contrárioTypeMismatch - Para
->: esperaType::Pointer(Struct(name))— caso contrárioTypeMismatch - Busca
nameemlookup_struct→UndefinedStructse ausente - Busca
field_namenos campos →FieldNotFoundse ausente - Retorna o tipo do campo
| Nó | Tipo retornado |
|---|---|
Unary(AddrOf) |
Pointer(T) onde T é o tipo do operando |
Unary(Deref) |
tipo base T de Pointer(T) ou Array(T) |
Unary(-, ~), Prefix, Postfix |
mesmo tipo do operando |
CompoundAssign |
tipo do LHS |
Cast(qty, _) |
qty resolvido |
Sizeof(_), SizeofType(_) |
unsigned int |
Call(callee, args) |
Tipo de retorno registrado na assinatura. Checa aridade e tipos de argumentos. |
Index(arr, idx) |
tipo do elemento (desreferencia Array(T) ou Pointer(T)). idx deve ser numérico inteiro. |
Ternary(cond, then, else) |
O tipo promovido/comum dos ramos then e else, após verificação de compatibilidade. |
Erros geram CompilerError::Semantic, avisos geram CompilerWarning::Semantic. A análise não é interrompida por diagnósticos.
| Kind | Causa |
|---|---|
Redeclaration(name) |
Nome já declarado no escopo atual |
UndefinedVariable(name) |
Identificador não encontrado em nenhum escopo |
UndefinedFunction(name) |
Chamada de função sem definição ou protótipo registrado |
AssignToConst(name) |
Atribuição a variável declarada com const |
TypeMismatch { expected, found } |
Tipos incompatíveis (atribuição, operação binária, retorno, ternário) |
UndefinedStruct(name) |
Acesso a membro de struct não registrada |
FieldNotFound { struct, field } |
Campo não existe na struct |
InvalidSwitchType |
Expressão de switch não é de tipo inteiro |
BreakOutsideLoop |
Uso de break fora de um bloco iterativo (for/while) ou switch |
ContinueOutsideLoop |
Uso de continue fora de um bloco iterativo (for/while) |
ArityMismatch { expected, found } |
Quantidade incorreta de argumentos na chamada de função |
NotIndexable |
Tentativa de usar operador de índice [] em tipo que não é array nem ponteiro |
InvalidIndexType |
Expressão usada no índice não é do tipo inteiro |
| Kind | Causa |
|---|---|
MissingReturn(name) |
Função declarada com retorno não-void sem garantia de return em todos os caminhos |
UnusedVariable(name) |
Variável local declarada mas não referenciada |
MayBeUninitialized(name) |
Variável lida sem antes ter garantido inicialização (por declaração ou atribuição) |
- Aritmética de ponteiro para
Sub(ponteiro − ponteiro →ptrdiff_t) - Constant folding robusto (hoje o compilador avalia as heurísticas baseado estaticamente na árvore do código, sem resolver valores em runtime na análise)