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/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..a888f71
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,20 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Debug MiniC --tac aggregate_types",
+ "type": "lldb",
+ "request": "launch",
+ "cargo": {
+ "args": ["build", "--bin", "mini_c"],
+ "filter": {
+ "name": "mini_c",
+ "kind": "bin"
+ }
+ },
+ "args": ["--tac", "tests/fixtures/aggregate_types.minic"],
+ "cwd": "${workspaceFolder}",
+ "stopOnEntry": false
+ }
+ ]
+}
\ No newline at end of file
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/codegen/tac_code_gen.rs b/src/codegen/tac_code_gen.rs
index 2f5e6ee..a9b9ed9 100644
--- a/src/codegen/tac_code_gen.rs
+++ b/src/codegen/tac_code_gen.rs
@@ -1,21 +1,29 @@
-use crate::ir::ast::{CheckedProgram, CheckedFunDecl, CheckedStmt, Statement, Expr, CheckedExpr, Literal, Type};
-use crate::ir::tac::{TACProgram, Instruction, Address, Operator};
-
+use crate::ir::ast::{
+ AggregateTypeDecl, AgtTypeMember, AgtTypeSpecifier, CheckedExpr, CheckedFunDecl,
+ CheckedProgram, CheckedStmt, Expr, ExprD, Literal, Statement, Type,
+};
+use crate::ir::tac::{Address, Instruction, Operator, TACProgram};
#[derive(Clone)]
pub struct Environment {
- current_label : usize,
- current_temporary: usize
+ current_label: usize,
+ current_temporary: usize,
+ type_declarations: Vec,
}
impl Environment {
pub fn new() -> Self {
Self {
current_label: 0,
- current_temporary: 0
+ current_temporary: 0,
+ type_declarations: Vec::new(),
}
}
+ fn register_type_declarations(&mut self, type_declarations: Vec) {
+ self.type_declarations = type_declarations;
+ }
+
fn new_label(&mut self) -> String {
self.current_label += 1;
format!("Label{}:", self.current_label)
@@ -25,24 +33,47 @@ impl Environment {
self.current_temporary += 1;
format!("temp{}", self.current_temporary)
}
+
+ fn enum_member_value(&self, identifier: &str, member: &str) -> i64 {
+ let decl = self
+ .type_declarations
+ .iter()
+ .find(|decl| decl.specifier == AgtTypeSpecifier::Enum && decl.identifier == identifier)
+ .unwrap_or_else(|| unreachable!("checked enum type must be declared"));
+
+ let mut next_value = 0;
+ for entry in &decl.members {
+ if let AgtTypeMember::Enumerator { name, value } = entry {
+ let resolved = value.unwrap_or(next_value);
+ if name == member {
+ return resolved;
+ }
+ next_value = resolved + 1;
+ }
+ }
+
+ unreachable!("checked enum member must exist")
+ }
}
-fn translate_program(program: CheckedProgram, env: &mut Environment) -> TACProgram {
+pub fn translate_program(program: CheckedProgram, env: &mut Environment) -> TACProgram {
+ env.register_type_declarations(program.type_declarations.clone());
let main_fn = program.main_function();
match main_fn {
None => unreachable!("[Impossible] program must have a main function"),
Some(f) => translate_function(f.clone(), env),
}
-
}
fn translate_function(function: CheckedFunDecl, env: &mut Environment) -> TACProgram {
- let mut instructions =
- if let Statement::Block { seq : stmts } = function.body.stmt {
- stmts.into_iter().flat_map(|stmt| translate_statement(stmt, env)).collect::>()
- } else {
- translate_statement(*(function.body), env)
- };
+ let mut instructions = if let Statement::Block { seq: stmts } = function.body.stmt {
+ stmts
+ .into_iter()
+ .flat_map(|stmt| translate_statement(stmt, env))
+ .collect::>()
+ } else {
+ translate_statement(*(function.body), env)
+ };
instructions.insert(0, Instruction::Label(function.name.clone()));
instructions
}
@@ -51,60 +82,163 @@ pub fn translate_statement(statement: CheckedStmt, env: &mut Environment) -> Vec
let mut res: Vec = Vec::new();
match statement.stmt {
- Statement::Block{seq} => {
- seq.into_iter().flat_map(|s| translate_statement(s, env)).collect::>()
- },
+ Statement::Block { seq } => seq
+ .into_iter()
+ .flat_map(|s| translate_statement(s, env))
+ .collect::>(),
+ Statement::Decl { name, ty, init } => {
+ let (expression_address, instructions) = translate_expression(*init, env);
+ res.extend(instructions);
+ res.push(Instruction::CopyAssignment(
+ Address::Variable(name, ty),
+ expression_address,
+ ));
+ res
+ }
Statement::Assign { target, value } => {
- if let Expr::Ident(name) = &target.exp {
- let var_type = target.ty.clone();
- let var_address = Address::Variable(name.to_string(), var_type);
- let (expression_address, instructions) = translate_expression(*value, env);
- res.extend(instructions);
- res.push(Instruction::CopyAssignment(var_address, expression_address));
- res
- }
- else {
- todo!()
- }
- },
- Statement::Call{name, args} => {
+ let var_address = translate_lvalue(*target, env);
+ let (expression_address, instructions) = translate_expression(*value, env);
+ res.extend(instructions);
+ res.push(Instruction::CopyAssignment(var_address, expression_address));
+ res
+ }
+ Statement::Call { name, args } => {
// addresses_and_instructions :: [(Address, [Instruction])]
- let addresses_and_instructions = args.into_iter().map(|expr| translate_expression(expr, env)).collect::>();
- let mut instructions = addresses_and_instructions.iter().fold(vec![], |mut acc, (_, inst)| {acc.extend(inst.clone()); acc});
+ let addresses_and_instructions = args
+ .into_iter()
+ .map(|expr| translate_expression(expr, env))
+ .collect::>();
+ let mut instructions =
+ addresses_and_instructions
+ .iter()
+ .fold(vec![], |mut acc, (_, inst)| {
+ acc.extend(inst.clone());
+ acc
+ });
// includes a 'param' instruction to the
// every addresses built from the arguments.
for (addr, _) in &addresses_and_instructions {
instructions.push(Instruction::Param(addr.clone()));
}
- instructions.push(Instruction::Call(None, name, addresses_and_instructions.len()));
+ instructions.push(Instruction::Call(
+ None,
+ name,
+ addresses_and_instructions.len(),
+ ));
instructions
}
- Statement::If{cond, then_branch: then_body, else_branch: Some(else_body)} => {
- let label_then = env.new_label();
+ Statement::If {
+ cond,
+ then_branch: then_body,
+ else_branch: Some(else_body),
+ } => {
let label_else = env.new_label();
let label_end_if = env.new_label();
- let mut instructions = translate_conditional(*cond, env, label_then.clone(), label_else.clone());
- instructions.push(Instruction::Label(label_then));
+ let mut instructions = translate_conditional_false(*cond, env, label_else.clone());
instructions.extend(translate_statement(*then_body, env));
instructions.push(Instruction::JMP(label_end_if.clone()));
instructions.push(Instruction::Label(label_else));
instructions.extend(translate_statement(*else_body, env));
instructions.push(Instruction::Label(label_end_if));
instructions
- },
- _ => todo!()
+ }
+ _ => todo!(),
+ }
+}
+
+fn translate_lvalue(target: CheckedExpr, env: &mut Environment) -> Address {
+ match target.exp {
+ Expr::Ident(name) => Address::Variable(name, target.ty),
+ Expr::Member { base, member } => translate_member_address(*base, member, target.ty, env),
+ _ => todo!(),
+ }
+}
+
+fn translate_member_address(
+ base: CheckedExpr,
+ member: String,
+ member_ty: Type,
+ env: &mut Environment,
+) -> Address {
+ match &base.ty {
+ Type::Aggregate {
+ specifier: AgtTypeSpecifier::Enum,
+ identifier,
+ } => Address::Constant(
+ Literal::Int(env.enum_member_value(identifier, &member)),
+ Type::Int,
+ ),
+ _ => Address::Variable(format!("{}.{}", member_base_name(base), member), member_ty),
}
}
-fn translate_expression(expression: CheckedExpr, env: &mut Environment) -> (Address, Vec) {
+fn member_base_name(base: CheckedExpr) -> String {
+ match base.exp {
+ Expr::Ident(name) => name,
+ Expr::Member {
+ base: nested_base,
+ member,
+ } => format!("{}.{}", member_base_name(*nested_base), member),
+ _ => todo!(),
+ }
+}
+
+fn translate_conditional_false(
+ expression: CheckedExpr,
+ env: &mut Environment,
+ false_label: String,
+) -> Vec {
match expression.exp {
- Expr::Literal(value) => {
- (Address::Constant(value, expression.ty), vec![])
- },
+ Expr::Literal(Literal::Bool(true)) => vec![],
+ Expr::Literal(Literal::Bool(false)) => vec![Instruction::JMP(false_label)],
Expr::Ident(name) => {
- (Address::Variable(name.to_string(), expression.ty), vec![])
- },
+ let addr = Address::Variable(name.to_string(), expression.ty);
+ vec![Instruction::ConditionalJMPFalse(addr, false_label)]
+ }
+ Expr::Lt(left, right) => {
+ translate_relational_false(*left, *right, Operator::GTE, false_label, env)
+ }
+ Expr::Le(left, right) => {
+ translate_relational_false(*left, *right, Operator::GT, false_label, env)
+ }
+ Expr::Gt(left, right) => {
+ translate_relational_false(*left, *right, Operator::LTE, false_label, env)
+ }
+ Expr::Ge(left, right) => {
+ translate_relational_false(*left, *right, Operator::LT, false_label, env)
+ }
+ Expr::Eq(left, right) => {
+ translate_relational_false(*left, *right, Operator::NE, false_label, env)
+ }
+ Expr::Ne(left, right) => {
+ translate_relational_false(*left, *right, Operator::EQ, false_label, env)
+ }
+ _ => {
+ let (addr, mut instructions) = translate_expression(
+ ExprD {
+ exp: expression.exp,
+ ty: expression.ty,
+ },
+ env,
+ );
+ instructions.push(Instruction::ConditionalJMPFalse(addr, false_label));
+ instructions
+ }
+ }
+}
+
+fn translate_expression(
+ expression: CheckedExpr,
+ env: &mut Environment,
+) -> (Address, Vec) {
+ match expression.exp {
+ Expr::Literal(value) => (Address::Constant(value, expression.ty), vec![]),
+ Expr::Ident(name) => (Address::Variable(name.to_string(), expression.ty), vec![]),
+ Expr::Member { base, member } => (
+ translate_member_address(*base, member, expression.ty, env),
+ vec![],
+ ),
// Boolean Expressions. 'and' and 'or' implement a short circuit semantics.
Expr::Not(exp) => {
let (addr, mut instructions) = translate_expression(*exp, env);
@@ -112,10 +246,16 @@ fn translate_expression(expression: CheckedExpr, env: &mut Environment) -> (Addr
let label_exit = env.new_label();
let temp = Address::Temporary(env.new_temporary(), Type::Bool);
instructions.push(Instruction::ConditionalJMPFalse(addr, label_false.clone()));
- instructions.push(Instruction::CopyAssignment(temp.clone(), Address::Constant(Literal::Bool(false), Type::Bool)));
+ instructions.push(Instruction::CopyAssignment(
+ temp.clone(),
+ Address::Constant(Literal::Bool(false), Type::Bool),
+ ));
instructions.push(Instruction::JMP(label_exit.clone()));
instructions.push(Instruction::Label(label_false));
- instructions.push(Instruction::CopyAssignment(temp.clone(), Address::Constant(Literal::Bool(true), Type::Bool)));
+ instructions.push(Instruction::CopyAssignment(
+ temp.clone(),
+ Address::Constant(Literal::Bool(true), Type::Bool),
+ ));
instructions.push(Instruction::Label(label_exit));
(temp, instructions)
}
@@ -127,18 +267,27 @@ fn translate_expression(expression: CheckedExpr, env: &mut Environment) -> (Addr
let label_exit = env.new_label();
let temp = Address::Temporary(env.new_temporary(), Type::Bool);
let mut instructions = l_instructions;
- instructions.push(Instruction::ConditionalJMPFalse(l_addr, label_false.clone()));
+ instructions.push(Instruction::ConditionalJMPFalse(
+ l_addr,
+ label_false.clone(),
+ ));
instructions.push(Instruction::JMP(label_true.clone()));
instructions.push(Instruction::Label(label_false));
instructions.extend(r_instructions);
instructions.push(Instruction::ConditionalJMP(r_addr, label_true.clone()));
- instructions.push(Instruction::CopyAssignment(temp.clone(), Address::Constant(Literal::Bool(false), Type::Bool)));
+ instructions.push(Instruction::CopyAssignment(
+ temp.clone(),
+ Address::Constant(Literal::Bool(false), Type::Bool),
+ ));
instructions.push(Instruction::JMP(label_exit.clone()));
instructions.push(Instruction::Label(label_true));
- instructions.push(Instruction::CopyAssignment(temp.clone(), Address::Constant(Literal::Bool(true), Type::Bool)));
+ instructions.push(Instruction::CopyAssignment(
+ temp.clone(),
+ Address::Constant(Literal::Bool(true), Type::Bool),
+ ));
instructions.push(Instruction::Label(label_exit));
(temp, instructions)
- },
+ }
Expr::And(left, right) => {
let (l_addr, l_instructions) = translate_expression(*left, env);
let (r_addr, r_instructions) = translate_expression(*right, env);
@@ -146,73 +295,62 @@ fn translate_expression(expression: CheckedExpr, env: &mut Environment) -> (Addr
let label_exit = env.new_label();
let temp = Address::Temporary(env.new_temporary(), Type::Bool);
let mut instructions = l_instructions;
- instructions.push(Instruction::ConditionalJMPFalse(l_addr, label_false.clone()));
+ instructions.push(Instruction::ConditionalJMPFalse(
+ l_addr,
+ label_false.clone(),
+ ));
instructions.extend(r_instructions);
- instructions.push(Instruction::ConditionalJMPFalse(r_addr, label_false.clone()));
- instructions.push(Instruction::CopyAssignment(temp.clone(), Address::Constant(Literal::Bool(true), Type::Bool)));
+ instructions.push(Instruction::ConditionalJMPFalse(
+ r_addr,
+ label_false.clone(),
+ ));
+ instructions.push(Instruction::CopyAssignment(
+ temp.clone(),
+ Address::Constant(Literal::Bool(true), Type::Bool),
+ ));
instructions.push(Instruction::JMP(label_exit.clone()));
instructions.push(Instruction::Label(label_false));
- instructions.push(Instruction::CopyAssignment(temp.clone(), Address::Constant(Literal::Bool(false), Type::Bool)));
+ instructions.push(Instruction::CopyAssignment(
+ temp.clone(),
+ Address::Constant(Literal::Bool(false), Type::Bool),
+ ));
instructions.push(Instruction::Label(label_exit));
(temp, instructions)
- },
+ }
// Arithmetic Expressions
Expr::Add(left, right) => {
let (l_addr, l_instructions) = translate_expression(*left, env);
let (r_addr, r_instructions) = translate_expression(*right, env);
let mut instructions = [l_instructions, r_instructions].concat();
let temp = Address::Temporary(env.new_temporary(), expression.ty);
- instructions.push(Instruction::BinaryAssignment(Operator::Add, temp.clone(), l_addr, r_addr));
+ instructions.push(Instruction::BinaryAssignment(
+ Operator::Add,
+ temp.clone(),
+ l_addr,
+ r_addr,
+ ));
(temp, instructions)
}
- _ => todo!()
- }
-}
-
-fn translate_conditional(expression: CheckedExpr, env: &mut Environment, true_label: String, false_label: String) -> Vec {
- match expression.exp {
- Expr::Literal(Literal::Bool(true)) => vec![Instruction::JMP(true_label)],
- Expr::Literal(Literal::Bool(false)) => vec![Instruction::JMP(false_label)],
- Expr::Ident(name) => {
- let addr = Address::Variable(name.to_string(), expression.ty);
- vec![Instruction::ConditionalJMP(addr, true_label), Instruction::JMP(false_label)]
- },
- Expr::And(left, right) => {
- let label_right = env.new_label();
- let mut instructions = translate_conditional(*left, env, label_right.clone(), false_label.clone());
- instructions.push(Instruction::Label(label_right));
- instructions.extend(translate_conditional(*right, env, true_label, false_label));
- instructions
- },
- Expr::Or(left, right) => {
- let label_right = env.new_label();
- let mut instructions = translate_conditional(*left, env, true_label.clone(), label_right.clone());
- instructions.push(Instruction::Label(label_right));
- instructions.extend(translate_conditional(*right, env, true_label, false_label));
- instructions
- },
- Expr::Not(expr) => translate_conditional(*expr, env, false_label, true_label),
- Expr::Lt(left, right) => translate_relational(*left, *right, Operator::LT, true_label, false_label, env),
- Expr::Le(left, right) => translate_relational(*left, *right, Operator::LTE, true_label, false_label, env),
- Expr::Gt(left, right) => translate_relational(*left, *right, Operator::GT, true_label, false_label, env),
- Expr::Ge(left, right) => translate_relational(*left, *right, Operator::GTE, true_label, false_label, env),
- Expr::Eq(left, right) => translate_relational(*left, *right, Operator::EQ, true_label, false_label, env),
- Expr::Ne(left, right) => translate_relational(*left, *right, Operator::NE, true_label, false_label, env),
- _ => {
- let (addr, mut instructions) = translate_expression(expression, env);
- instructions.push(Instruction::ConditionalJMP(addr, true_label));
- instructions.push(Instruction::JMP(false_label));
- instructions
- }
+ _ => todo!(),
}
}
-fn translate_relational(left: CheckedExpr, right: CheckedExpr, op: Operator, true_label: String, false_label: String, env: &mut Environment) -> Vec {
+fn translate_relational_false(
+ left: CheckedExpr,
+ right: CheckedExpr,
+ op: Operator,
+ false_label: String,
+ env: &mut Environment,
+) -> Vec {
let (l_addr, l_instructions) = translate_expression(left, env);
let (r_addr, r_instructions) = translate_expression(right, env);
let mut instructions = l_instructions;
instructions.extend(r_instructions);
- instructions.push(Instruction::ConditionalJMPRelational(op, l_addr, r_addr, true_label));
- instructions.push(Instruction::JMP(false_label));
+ instructions.push(Instruction::ConditionalJMPRelational(
+ op,
+ l_addr,
+ r_addr,
+ false_label,
+ ));
instructions
}
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