diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..2547d9d
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,26 @@
+---
+name: "miniC educational parser code"
+description: Coding standards for this repo
+---
+
+When working in this project, prioritize code that is:
+
+- clear and easy to read for educational purposes
+- self-documenting through explicit naming, structure, and straightforward control flow
+- idiomatic Rust, but not at the expense of readability
+- aligned with the existing miniC codebase style and parser-combinator design
+
+Prefer:
+
+- descriptive function, type, and variable names
+- simple parser structure and well-scoped helper functions
+- comments only when they explain why a design choice matters, not what obvious code does
+- preserving spec-driven behavior and making language rules understandable
+
+Avoid:
+
+- overly terse or clever code that reduces comprehension
+- large unrelated refactors when the task is focused on parser/AST/spec behavior
+- introducing new patterns that conflict with the established codebase style
+
+IMPORTANT! Always thoroughly review the relevant `docs/` documentation before starting a task and again whenever you encounter a roadblock.
diff --git a/docs/00-guide.md b/docs/00-guide.md
new file mode 100644
index 0000000..525ed2c
--- /dev/null
+++ b/docs/00-guide.md
@@ -0,0 +1,428 @@
+# MiniC + Nom
+
+Este guia introduz o projeto MiniC da disciplina **sem assumir experiência prévia com Rust** ou **com Nom**.
+
+Tem um objetivo: levar você de "sei o que são combinadores de parsing em teoria" até "consigo ler e estender essa implementação real de linguagem em Rust".
+
+Use este documento como seu ponto de partida e consulte as referências vinculadas quando precisar de ajuda.
+
+Referências rápidas:
+- Rust Book:
+- Documentação da crate Nom:
+- Guias do Nom:
+
+
+## O que é MiniC
+
+MiniC é uma pequena linguagem similar a C implementada em Rust para aprender construção de compiladores.
+
+Um programa MiniC é uma lista de declarações de funções:
+
+```c
+int factorial(int n) {
+ if n <= 1 { return 1; }
+ return n * factorial(n - 1);
+}
+
+void main() {
+ int result = factorial(10);
+ print(result);
+}
+```
+
+Pipeline do MiniC:
+1. Fazer parsing do código-fonte para uma AST.
+2. Verificar tipos da AST.
+3. Interpretar (executar) a AST verificada.
+
+Por que isso importa pedagogicamente:
+- Cada etapa tem uma responsabilidade clara.
+- Você pode testar cada etapa independentemente.
+- O trabalho de extensão é estruturado e previsível.
+
+Para mais informações sobre a linguagem e o pipeline, veja [docs/01-language.md](01-language.md) e [docs/02-pipeline.md](02-pipeline.md).
+
+## Parte A: Introdução Rápida a Rust (Apenas o Necessário)
+
+Você **não** precisa de todo o livro de Rust para trabalhar em MiniC. Você só precisa de um pequeno subconjunto.
+
+### 1) Variáveis e Funções
+
+Estrutura de função em Rust:
+
+```rust
+fn add(x: i64, y: i64) -> i64 {
+ x + y
+}
+```
+
+- `fn` começa uma função.
+- `x: i64` é o nome do parâmetro e tipo.
+- `-> i64` é o tipo de retorno.
+- A última expressão sem `;` é retornada.
+
+(Rust Book: )
+
+### 2) Structs (Dados com Campos Nomeados)
+
+```rust
+struct Example {
+ x: i64,
+ y: i64,
+}
+```
+
+(Rust Book: )
+
+### 3) Enums (Uma de Várias Variantes)
+
+Enums em Rust têm três estilos principais de variante:
+- Tuple-like (`Name(T1, T2)`): campos posicionais, útil quando a ordem importa (ex.: operandos).
+- Struct-like (`Name { f1: T1, f2: T2 }`): campos nomeados, mais explícito e resistente à reordenação.
+- Unit-like (`Name`): etiqueta sem dados, para estados/flags.
+
+```rust
+enum Example {
+ Tuple(i64, i64), // tuple-like (posicional)
+ Record { x: i64, y: i64 }, // struct-like (campos nomeados)
+ Unit, // unit-like (sem dados)
+}
+```
+
+(Rust Book: )
+
+### 4) match (Ramificação por Variante)
+
+Use `match` ou `if let` para desestruturar enums:
+
+```rust
+match some_enum {
+ Expr::Tuple(a, b) => println!("posicional: {} {}", a, b),
+ Expr::Record { x, .. } => println!("campo x = {}", x),
+ Expr::Unit => println!("unit"),
+}
+```
+
+O compilador garante que o `match` seja exaustivo sobre todas as possibilidades do enum. Para ter um caso "catch-all", podemos usar `_ => {}` como o último match.
+
+(Rust Book: )
+
+### 5) Box para Árvores Recursivas
+
+Tipos recursivos (como nós de expressão) precisam de `Box` para que o compilador saiba calcular seu tamanho:
+
+```rust
+enum Expr {
+ Int(i64),
+ Add(Box, Box),
+}
+
+// Construindo um nó Add:
+let left = Expr::Int(1);
+let right = Expr::Int(2);
+let add = Expr::Add(Box::new(left), Box::new(right));
+
+// Desestruturando com `match`:
+match expr_example {
+ Expr::Add(box Expr::Int(l), box Expr::Int(r)) => {
+ println!("Add node: {} + {} = {}", l, r, l + r);
+ }
+ _ => {}
+}
+```
+
+(Rust Book: )
+
+### 6) Result para Tratamento de Erros
+
+`Result` significa um de dois casos:
+- `Ok(T)` sucesso
+- `Err(E)` falha
+
+Ao chamar uma função que retorna `Result`, podemos:
+- Desempacotá-lo para lidar com ambos os casos, ou
+- Propagar o erro caso a função atual também retorne `Result`.
+
+```rust
+fn parse_to_number(input: &str) -> Result {
+ input.parse::()
+}
+
+// chamando e lidando com o resultado explicitamente
+fn try_parse_number() {
+ match parse_number("42") {
+ Ok(n) => println!("numero: {}", n),
+ Err(e) => eprintln!("erro ao parsear: {}", e),
+ }
+}
+
+// propagando erros com `?`
+fn try_double(s: &str) -> Result {
+ let n = parse_number(s)?;
+ Ok(n * 2)
+}
+```
+
+(Rust Book: )
+
+### 7) Generics
+
+Generics são parâmetros de tipo: permitem escrever uma definição uma única vez e instanciá-la com diferentes tipos.
+
+No MiniC, a [AST](../src/ir/ast.rs#L214) usa `Ty` como o tipo genérico nos nós. O parser produz `ExprD<()>` (sem tipos) e o type-checker produz `ExprD` (cada nó carrega seu `Type`).
+
+(Rust Book: )
+
+### 8) Macros `#[derive(...)]`
+
+Na [AST](../src/ir/ast.rs), muitas structs/enums usam `#[derive(...)]`. Derives geram código boilerplate automaticamente durante a compilação.
+
+- `Debug`: permite imprimir o nó para debugging/tests (`{:?}`).
+- `Clone`: gera uma implementação automática de `clone()` para copiar nós quando necessário.
+- `PartialEq`/`Eq`: permitem comparar nós (útil em testes e transformações).
+- `Hash`: permite usar o valor como chave em `HashMap`/`HashSet`.
+
+(Rust Book: )
+
+### 9) Ownership e Borrowing
+
+Breve resumo das regras essenciais do Rust aplicáveis ao projeto:
+
+- O borrow-checker é um verificador em tempo de compilação que evita dangling references e condições de corrida sem custo em tempo de execução.
+- Cada valor tem um dono. Quando o dono sai de escopo, o valor é liberado.
+- `&T` e `&mut T` são empréstimos (borrows). O *borrow-checker* garante que essas referências não ultrapassem o tempo de vida do dono e impede usos concorrentes inválidos.
+- Use `Box` para tipos recursivos (p.ex., nós de AST).
+- Prefira `&str` para views de string e faça `clone()` só quando necessário.
+
+(Rust Book: )
+
+## Parte B: Introdução Rápida a Nom (Apenas o Necessário)
+
+Nom é uma biblioteca de combinadores de parsers para Rust.
+
+Conforme a documentação do Nom, a forma central de um parsing é:
+
+```rust
+fn parser(input: I) -> IResult
+```
+
+Para MiniC, tipicamente:
+- tipo de entrada: `&str`
+- tipo de saída: fragmento de AST
+
+(Visão geral do Nom: , guia "fazendo um novo parser": )
+
+### 1) O que IResult Significa
+
+`IResult` é essencialmente:
+- `Ok((remaining_input, output_value))`
+- ou `Err(...)`
+
+Então um parser retorna ambos:
+1. O que foi parseado.
+2. O que restou sem parsear.
+
+(Referência: )
+
+Modelo de erro do Nom (importante ao debugar comportamento de parsing):
+- `Err::Error` é recuperável (então `alt` pode tentar outro branch)
+- `Err::Failure` é irrecuperável (branch confirmado)
+- `Err::Incomplete` significa que mais entrada é necessária em modo streaming (mas nunca ocorre em modo complete).
+
+O combinator `cut` muda erros recuperáveis para falhas quando você sabe que está no branch correto. Veja e .
+
+### 2) Complete vs Streaming
+
+Nom tem variantes `complete` e `streaming`.
+
+MiniC usa parsers `complete` porque arquivos de código estão totalmente disponíveis na memória.
+
+Conforme a documentação do Nom:
+- streaming pode retornar `Incomplete` para buffers parciais.
+- complete trata dados faltantes como um erro.
+
+Para parsing de linguagem baseado em arquivo, complete é o padrão certo.
+
+(Referência e exemplos: )
+
+### 3) Combinators Principais Usados em MiniC
+
+- `tag("if")`: correspondência exata de string.
+- `char('(')`: correspondência exata de caractere.
+- `alt((a, b, c))`: tenta parsers em ordem, aplica o primeiro que funcionar.
+- `tuple((a, b, c))`: aplica parsers em sequência.
+- `preceded(a, b)`: retorna `b` se for precedido por `a`. Descarta `a`. Usado para descartar whitespace.
+- `delimited(a, b, c)`: retorna `b` se estiver entre `a` e `c`. Descarta `a` e `c`. Usado para encontrar "{}", "()" e "[]".
+- `opt(p)`: pode ou não estar presente (p?).
+- `many0(p)`: se repete zero ou mais vezes (p*).
+- `many1(p)`: se repete um ou mais vezes (p+).
+- `separated_list0(sep, item)`: uma lista de `item` separada por `sep`. (Ex. parâmetros de função)
+- `verify(p, pred)`: verifica a condição `pred` sobre o resultado do parser `p`.
+- `map(p, f)`: aplica `f` sobre o resultado do parser `p`. Usado para transformar a saída dos parsers em nós da AST.
+
+Quando não tiver certeza qual combinator usar, consulte o guia de escolha: .
+
+### 4) Comportamentos Importantes do Nom
+
+#### Sucesso do parser não implica consumo completo
+
+Um parser em Nom retorna `Ok((rest, value))`. Mesmo quando `value` foi reconhecido com sucesso, parte da entrada pode sobrar em `rest`. Se você precisa garantir que o parser consuma toda a entrada (útil nos testes unitários), envolva-o com `all_consuming`:
+
+```rust
+let all = all_consuming(tag("str"));
+assert!(all("str").is_ok()); // consome tudo
+assert!(all("struct").is_err()); // sobra entrada -> erro
+```
+
+(Nom:`all_consuming` )
+
+#### alt é ordenado
+
+`alt((a, b, c))` tenta da esquerda para a direita e retorna o primeiro sucesso. A ordem dos branches afeta diretamente comportamento da linguagem.
+
+Exemplo onde a ordem importa (um branch é prefixo de outro):
+
+```rust
+let parser_wrong = alt((tag("str"), tag("struct")));
+assert_eq!(parser_wrong("struct"), Ok(("uct", "str")));
+// branch curto primeiro -> escolhe "str" e deixa "uct" sobrando
+
+let parser_right = alt((tag("struct"), tag("str")));
+assert_eq!(parser_right("struct"), Ok(("", "struct")));
+// branch longo primeiro -> escolhe "struct" como esperado
+```
+
+(Nom: `alt` )
+
+## Parte C: Arquitetura do Parser de MiniC
+
+Módulos do parser são divididos por categoria de gramática.
+
+Veja notas sobre arquitetura do parser em [docs/04-parser.md](04-parser.md), depois compare diretamente com a implementação.
+
+### 1) [Identificadores](../src/parser/identifiers.rs)
+
+Nomes de variáveis, funções, etc. Rejeita palavras-chave reservadas com `verify`.
+
+(Nom `verify`: )
+
+### 2) [Literais](../src/parser/literals.rs)
+
+Faz parsing de int, float, string, bool. Parser de string suporta escapes (`\\`, `\"`, `\n`, `\t`).
+
+(Nom: `escaped_transform` )
+
+### 3) [Expressões (Precedência + Associatividade)](../src/parser/expressions.rs)
+
+- ou lógico (precedência mais baixa)
+- e lógico
+- não
+- relacional
+- aditivo
+- multiplicativo
+- unário
+- primário
+- atômico (precedência mais alta)
+
+MiniC codifica precedência de operadores via camadas de função. Cada camada chama a camada anterior quando precisa de um operando. **Todos os operadores binários de MiniC são associativos à esquerda**, implementados com um loop de acumulador em cada nível: parseia o operando esquerdo, depois enquanto o operador esperado não falha, parseia o operador e o operando direito (que se torna o novo operando esquerdo).
+
+Exemplo: `1 - 2 - 3` se torna `(1 - 2) - 3` porque o primeiro `2` é consumido como direito, o resultado `(1 - 2)` vira o novo esquerdo, e `3` é consumido como novo direito.
+
+### 4) [Declarações](../src/parser/statements.rs)
+
+- bloco
+- if
+- while
+- return
+- declaração de variável
+- chamada de função
+- atribuição
+
+A ordem é deliberada, especialmente para formas com prefixos sobrepostos.
+
+### 5) [Funções e Tipos](../src/parser/functions.rs)
+
+Declaração de função e tipos.
+
+Parser de tipo inclui formas escalares e de array. Porque `alt` é ordenado, prefixos mais longos (como formas de array 2D) são listados listados antes dos mais curtos (formas 1D).
+
+### 6) [Parser de Programa](../src/parser/program.rs)
+
+Parser de declarações top-level. Usa repetição sobre declarações de função.
+
+## Parte D: AST, Verificação de Tipos e Interpretação
+
+### 1) Design da AST
+
+Um nó da AST é uma unidade da árvore que representa uma construção sintática (ex.: expressão, declaração) e agrupa os campos necessários para representá-la.
+
+Nós seguem o padrão `Object` + `ObjectD`:
+- `Object` descreve a forma de um objeto.
+- `ObjectD` agrupa o objeto com o tipo que ele carrega para execução.
+
+Na prática: o parser produz `ObjectD<()>` (sem tipos) e o type-checker produz `ObjectD` (com tipos). Isso reaproveita a mesma forma estrutural entre fases sem duplicação.
+
+Arquivos: [docs/03-ast.md](03-ast.md), [src/ir/ast.rs](../src/ir/ast.rs).
+
+### 2) Responsabilidades do Verificador de Tipos
+
+Verificação de tipos valida:
+- Assinatura `main` obrigatória
+- Tipos de declaração e atribuição
+- Contagem/tipos de argumento de chamada de função
+- Digitação de operador de expressão
+- Indexação de array e consistência de elementos
+- Correção de tipo de retorno
+
+Usa um ambiente mapeando nomes para tipos.
+
+Arquivos: [docs/05-type-checker.md](05-type-checker.md), [src/semantic/type_checker.rs](../src/semantic/type_checker.rs).
+
+### 3) Responsabilidades do Interpretador
+
+Interpretador executa AST verificada:
+- Avaliação de expressão em valores em tempo de execução
+- Execução de declaração (incluindo fluxo de controle)
+- Chamadas de função (definidas pelo usuário e nativas)
+- Erros em tempo de execução (ex., fora dos limites)
+
+Usa um ambiente mapeando nomes para valores em tempo de execução.
+
+Arquivos: [docs/06-interpreter.md](06-interpreter.md), [src/interpreter/eval_expr.rs](../src/interpreter/eval_expr.rs), [src/interpreter/exec_stmt.rs](../src/interpreter/exec_stmt.rs).
+
+## Parte E: Como Adicionar Funcionalidades
+
+Para cada nova funcionalidade de linguagem, o ideal é fazer nessa ordem:
+1. Estender AST.
+2. Estender parser.
+3. Adicionar testes de parser/programa.
+4. Estender type-checker.
+5. Adicionar testes de type-checker.
+6. Estender interpretador.
+7. Adicionar testes de interpretador.
+8. Verificar testes de stdlib e CLI
+8. Atualizar docs.
+
+Para adição de funcionalidades que impactam nas funções builtin em tempo de execução, consulte [docs/07-stdlib.md](07-stdlib.md) e [src/stdlib/mod.rs](../src/stdlib/mod.rs).
+
+## Parte F: Estratégia de Teste Que Você Deveria Seguir
+
+Camadas de teste de MiniC:
+- Testes do parser / programa
+- Testes do type-checker
+- Testes do interpretador
+- Testes CLI
+
+Regra prática:
+- em cada camada, pelo menos um teste unitário para cada regra de funcionamento da funcionalidade adicionada
+- um teste CLI para cada comportamento visível ao usuário
+
+Arquivos:
+- [tests/parser.rs](../tests/parser.rs)
+- [tests/program.rs](../tests/program.rs)
+- [tests/type_checker.rs](../tests/type_checker.rs)
+- [tests/interpreter.rs](../tests/interpreter.rs)
+- [tests/stdlib.rs](../tests/stdlib.rs)
+- [tests/cli](../tests/cli)
+
+Detalhes de estratégia de teste e convenções shelltest são documentados em [docs/08-testing.md](08-testing.md).
diff --git a/src/environment/env.rs b/src/environment/env.rs
index dd83ba1..5553b94 100644
--- a/src/environment/env.rs
+++ b/src/environment/env.rs
@@ -11,6 +11,8 @@
//! * [`set`](Environment::set) — update an existing binding.
//! * [`snapshot`](Environment::snapshot) / [`restore`](Environment::restore)
//! — save and restore the entire map (used for scoping).
+//! * [`aggregate_type`](Environment::aggregate_type) — look up an aggregate
+//! type declaration from the shared type-declaration table.
//!
//! Additionally, [`names`](Environment::names) and
//! [`remove_new`](Environment::remove_new) support block-exit cleanup.
@@ -54,20 +56,57 @@
//! acceptable at MiniC's scale.
use std::collections::{HashMap, HashSet};
+use std::rc::Rc;
+
+use crate::ir::ast::{AggregateTypeDecl, AgtTypeSpecifier};
+
+pub type TypeDeclKey = (AgtTypeSpecifier, String);
+pub type TypeDeclMap = HashMap;
+
+pub fn build_type_decl_map(decls: &[AggregateTypeDecl]) -> TypeDeclMap {
+ let mut type_map = TypeDeclMap::new();
+ for decl in decls {
+ let key = (decl.specifier.clone(), decl.identifier.clone());
+ type_map.insert(key, decl.clone());
+ }
+ type_map
+}
/// Unified parametric environment: maps names to values of type `V`.
/// Both variable bindings and function bindings are stored in the same map.
pub struct Environment {
bindings: HashMap,
+ type_decls: Rc,
}
impl Environment {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
+ type_decls: Rc::new(TypeDeclMap::new()),
+ }
+ }
+
+ pub fn with_type_decls(type_decls: TypeDeclMap) -> Self {
+ Self {
+ bindings: HashMap::new(),
+ type_decls: Rc::new(type_decls),
}
}
+ pub fn aggregate_type(
+ &self,
+ specifier: &AgtTypeSpecifier,
+ identifier: &str,
+ ) -> Option<&AggregateTypeDecl> {
+ self.type_decls
+ .get(&(specifier.clone(), identifier.to_string()))
+ }
+
+ pub fn has_aggregate_type(&self, specifier: &AgtTypeSpecifier, identifier: &str) -> bool {
+ self.aggregate_type(specifier, identifier).is_some()
+ }
+
/// Bind `name` to `value`, overwriting any existing binding.
pub fn declare(&mut self, name: impl Into, value: V) {
self.bindings.insert(name.into(), value);
diff --git a/src/environment/mod.rs b/src/environment/mod.rs
index ee49f46..b96bf91 100644
--- a/src/environment/mod.rs
+++ b/src/environment/mod.rs
@@ -21,4 +21,4 @@
pub mod env;
-pub use env::Environment;
\ No newline at end of file
+pub use env::{build_type_decl_map, Environment, TypeDeclKey, TypeDeclMap};
diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs
index 49fcbef..9f64518 100644
--- a/src/interpreter/eval_expr.rs
+++ b/src/interpreter/eval_expr.rs
@@ -40,7 +40,7 @@
//! for more detail on this mechanism.
use crate::environment::Environment;
-use crate::ir::ast::{CheckedExpr, Expr, Literal};
+use crate::ir::ast::{AgtTypeMember, AgtTypeSpecifier, CheckedExpr, Expr, Literal, Type};
use super::exec_stmt::exec_stmt;
use super::value::{FnValue, RuntimeError, Value};
@@ -64,15 +64,55 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result numeric_binop(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a + b, |a, b| a + b),
- Expr::Sub(l, r) => numeric_binop(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a - b, |a, b| a - b),
- Expr::Mul(l, r) => numeric_binop(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a * b, |a, b| a * b),
- Expr::Div(l, r) => numeric_binop(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a / b, |a, b| a / b),
+ Expr::Add(l, r) => numeric_binop(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a + b,
+ |a, b| a + b,
+ ),
+ Expr::Sub(l, r) => numeric_binop(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a - b,
+ |a, b| a - b,
+ ),
+ Expr::Mul(l, r) => numeric_binop(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a * b,
+ |a, b| a * b,
+ ),
+ Expr::Div(l, r) => numeric_binop(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a / b,
+ |a, b| a / b,
+ ),
- Expr::Lt(l, r) => numeric_cmp(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a < b, |a, b| a < b),
- Expr::Le(l, r) => numeric_cmp(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a <= b, |a, b| a <= b),
- Expr::Gt(l, r) => numeric_cmp(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a > b, |a, b| a > b),
- Expr::Ge(l, r) => numeric_cmp(eval_expr(l, env)?, eval_expr(r, env)?, |a, b| a >= b, |a, b| a >= b),
+ Expr::Lt(l, r) => numeric_cmp(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a < b,
+ |a, b| a < b,
+ ),
+ Expr::Le(l, r) => numeric_cmp(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a <= b,
+ |a, b| a <= b,
+ ),
+ Expr::Gt(l, r) => numeric_cmp(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a > b,
+ |a, b| a > b,
+ ),
+ Expr::Ge(l, r) => numeric_cmp(
+ eval_expr(l, env)?,
+ eval_expr(r, env)?,
+ |a, b| a >= b,
+ |a, b| a >= b,
+ ),
Expr::Eq(l, r) => {
let lv = eval_expr(l, env)?;
@@ -147,6 +187,58 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result {
+ let base_val = eval_expr(base, env)?;
+ match &base.ty {
+ Type::Aggregate {
+ specifier,
+ identifier,
+ } => match specifier {
+ AgtTypeSpecifier::Struct => match base_val {
+ Value::Struct { fields, .. } => {
+ fields.get(member).cloned().ok_or_else(|| {
+ RuntimeError::new(format!(
+ "missing struct member '{}.{}'",
+ identifier, member
+ ))
+ })
+ }
+ other => Err(RuntimeError::new(format!(
+ "expected struct runtime value for {}, got {}",
+ identifier, other
+ ))),
+ },
+ AgtTypeSpecifier::Union => match base_val {
+ Value::Union {
+ active_field,
+ value,
+ ..
+ } => {
+ if &active_field == member {
+ Ok(*value)
+ } else {
+ Err(RuntimeError::new(format!(
+ "union member '{}.{}' is inactive (active field: {})",
+ identifier, member, active_field
+ )))
+ }
+ }
+ other => Err(RuntimeError::new(format!(
+ "expected union runtime value for {}, got {}",
+ identifier, other
+ ))),
+ },
+ AgtTypeSpecifier::Enum => {
+ enum_member_value(identifier, member, env).map(Value::Int)
+ }
+ },
+ other => Err(RuntimeError::new(format!(
+ "member access requires aggregate base type, got {:?}",
+ other
+ ))),
+ }
+ }
}
}
@@ -168,8 +260,8 @@ pub fn eval_call(
)));
}
let snapshot = env.snapshot();
- for ((param_name, _), val) in decl.params.iter().zip(args.into_iter()) {
- env.declare(param_name.clone(), val);
+ for (param, val) in decl.params.iter().zip(args.into_iter()) {
+ env.declare(param.name.clone(), val);
}
let result = exec_stmt(&decl.body, env)?;
env.restore(snapshot);
@@ -180,8 +272,33 @@ pub fn eval_call(
}
}
-// --- Helpers ---
+fn enum_member_value(
+ agt_identifier: &str,
+ member: &str,
+ env: &Environment,
+) -> Result {
+ let decl = env
+ .aggregate_type(&AgtTypeSpecifier::Enum, agt_identifier)
+ .ok_or_else(|| RuntimeError::new(format!("unknown enum type '{}'", agt_identifier)))?;
+ let mut next_value: i64 = 0;
+ for entry in &decl.members {
+ if let AgtTypeMember::Enumerator { name, value } = entry {
+ let resolved = value.unwrap_or(next_value);
+ if name == member {
+ return Ok(resolved);
+ }
+ next_value = resolved + 1;
+ }
+ }
+
+ Err(RuntimeError::new(format!(
+ "unknown enumerator '{}.{}'",
+ agt_identifier, member
+ )))
+}
+
+// --- Helpers ---
fn eval_literal(lit: &Literal) -> Value {
match lit {
Literal::Int(n) => Value::Int(*n),
diff --git a/src/interpreter/exec_stmt.rs b/src/interpreter/exec_stmt.rs
index ceeda2c..638611b 100644
--- a/src/interpreter/exec_stmt.rs
+++ b/src/interpreter/exec_stmt.rs
@@ -34,11 +34,15 @@
//! This gives MiniC correct lexical block scoping without a scope stack.
use crate::environment::Environment;
-use crate::ir::ast::{CheckedExpr, CheckedStmt, Expr, Statement};
+use crate::ir::ast::{
+ AgtTypeMember, AgtTypeSpecifier, CheckedExpr, CheckedStmt, Expr, Statement, Type,
+};
use super::eval_expr::{eval_call, eval_expr};
use super::value::{RuntimeError, Value};
+use std::collections::HashMap;
+
/// `None` = normal fall-through; `Some(v)` = early return with value.
pub type ExecResult = Result