Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3af9027
feat: generate ctags for the project
EGmux Apr 6, 2026
ccd8699
Merge branch 'rbonifacio:main' into main
EGmux Apr 7, 2026
7b23263
feat: add tags
EGmux Apr 9, 2026
6ce4fc2
feat: add proptest lib for property based testing
EGmux Apr 9, 2026
6317537
fix: path to proptest
EGmux Apr 12, 2026
cef52f4
feat: add skeleton test for property based tests
Apr 13, 2026
36bb7c1
Unit tests for constant declaration <avll>
oruns Apr 18, 2026
08694b0
feat: add support for local constant declarations
Apr 19, 2026
e8bf2b6
feat: WIP Arbitrary implementation for const statements <egb2>
EGmux Apr 19, 2026
e6885bb
feat: Arbitrary implmentation for ConstStrategy <egb2>
EGmux Apr 21, 2026
86184ac
feat: Property based tests for Const Statements <egb2>
EGmux Apr 21, 2026
fc75bdb
implemet parser for statments with const
RodrigoSantosB Apr 21, 2026
244cbfe
Merge branch 'main' of https://github.com/EGmux/MiniC
RodrigoSantosB Apr 21, 2026
0123d17
Immutability and prohibition of assignment to const
Juliana-serafim Apr 25, 2026
1e5e62d
Tests First Milestone
oruns May 10, 2026
355d6ee
Merge branch 'main' of https://github.com/EGmux/MiniC
oruns May 10, 2026
ad7cd4a
Tests Second Milestone
oruns May 10, 2026
30654fb
fixed path second milestone
oruns May 10, 2026
18d2f49
Fixed path first milestone
oruns May 10, 2026
e88366c
feat: minor changes to make the code compile
Jun 14, 2026
9807502
feat: minor fixes so major tests compile and pass
Jun 14, 2026
748ee36
Merge branch 'second-milestone'
Jun 15, 2026
bbb6cd3
Merge branch 'rbonifacio:main' into main
EGmux Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"githubPullRequests.ignoredPullRequestBranches": [
"main"
]
}
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ nom = "7"

[dev-dependencies]
nom = "7"
proptest = "1.10.0"

[[test]]
name = "property"
path = "tests/property/mod.rs"
1,080 changes: 1,080 additions & 0 deletions nom.tags

Large diffs are not rendered by default.

1,659 changes: 1,659 additions & 0 deletions proptest.tags

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions setupctags.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
## Setup ctags for nom, rust-std crates and project
set -e # Exit on error

## check existence of stdlib
if [ ! -d rust ]; then
echo "Cloning Rust source..."
git clone --depth 1 https://github.com/rust-lang/rust.git
fi

## create stdlib tags
if [ -d rust ]; then
echo "Generating stdlib tags..."
(cd rust && ctags -R --languages=Rust -f ~/rust-stdlib.tags library/)
echo "✅ Created ~/rust-stdlib.tags"
fi

## create project tags
echo "Generating project tags..."
ctags -R --languages=Rust --exclude=target -f tags .
echo "✅ Created ./tags"

## find and tag nom (if exists)
NOM_PATH=$(find ~/.cargo/registry/src -type d -name "nom-*" 2>/dev/null | head -1)
if [ -n "$NOM_PATH" ]; then
echo "Generating nom tags..."
ctags -R --languages=Rust -f nom.tags "$NOM_PATH/src"
echo "✅ Created nom.tags"
else
echo "⚠️ nom not found in cargo cache (build your project first?)"
fi

## find and tag proptest (if exists)
PROPTEST_PATH=$(find ~/.cargo/registry/src/index.crates.io-* -type d -name "proptest-*" 2>/dev/null | head -1)
if [ -n "$PROPTEST_PATH" ]; then
echo "Generating proptest tags..."
ctags -R --languages=Rust -f proptest.tags "$PROPTEST_PATH/src"
echo "✅ Created proptest.tags"
else
echo "⚠️ proptest not found in cargo cache (build your project first?)"
fi

echo ""
echo "🎉 Done! Add to Vim: :set tags=./tags,~/rust-stdlib.tags"
7 changes: 7 additions & 0 deletions src/interpreter/exec_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment<Value>) -> ExecResult
Ok(None)
}

// --- Constant declaration ---
Statement::ConstDecl { name, init, .. } => {
let val = eval_expr(init, env)?;
env.declare(name.clone(), val);
Ok(None)
}

// --- Assignment ---
Statement::Assign { target, value } => {
let val = eval_expr(value, env)?;
Expand Down
10 changes: 10 additions & 0 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,21 @@ use crate::stdlib::NativeRegistry;

use eval_expr::eval_call;
use value::{FnValue, RuntimeError, Value};
use crate::ir::ast::Statement;
use crate::interpreter::eval_expr::eval_expr;

/// Interpret a type-checked MiniC program, starting execution at `main`.
pub fn interpret(program: &CheckedProgram) -> Result<(), RuntimeError> {
let mut env = Environment::<Value>::new();

for constant in &program.constants {
if let Statement::ConstDecl { name, init, .. } = &constant.stmt {
// Evaluate the initializer expression to a value
let value = eval_expr(init, &mut env)?;
env.declare(name.clone(), value);
}
}

// Register native stdlib functions as Value::Fn(FnValue::Native) bindings.
let registry = NativeRegistry::default();
for (name, entry) in registry.iter() {
Expand Down
7 changes: 7 additions & 0 deletions src/ir/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ pub enum Statement<Ty> {
ty: Type,
init: Box<ExprD<Ty>>,
},
/// Constant declaration with initialization: `const int x = expr`.
ConstDecl {
name: String,
ty: Type,
init: Box<ExprD<Ty>>,
},
Assign {
target: Box<ExprD<Ty>>,
value: Box<ExprD<Ty>>,
Expand Down Expand Up @@ -169,6 +175,7 @@ pub struct FunDecl<Ty> {
#[derive(Debug, Clone, PartialEq)]
pub struct Program<Ty> {
pub functions: Vec<FunDecl<Ty>>,
pub constants: Vec<StatementD<Ty>>,
}

// Type synonyms for checked and unchecked phases.
Expand Down
2 changes: 1 addition & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ pub use functions::fun_decl;
pub use identifiers::identifier;
pub use literals::{literal, Literal};
pub use program::program;
pub use statements::{assignment, statement};
pub use statements::{assignment, const_statement, statement};
106 changes: 101 additions & 5 deletions src/parser/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
//! function declaration causes the parse to stop. The type checker then
//! verifies that a `main` function exists.
//!
//! Local `const T name = expr ;` is recognised inside function bodies via
//! [`crate::parser::statements::const_statement`] (wired from [`crate::parser::statements::statement`]).
//! Extending this module with `alt((const_statement, fun_decl))` plus a rich enough
//! [`crate::ir::ast::Program`] is the usual next step for top-level constants.
//!
//! # Design Decisions
//!
//! ## `many0` as the top-level combinator
Expand All @@ -26,12 +31,103 @@
//! `main` is a semantic constraint checked in the next pipeline stage, not
//! a syntactic one enforced here.

use crate::ir::ast::{Program, UncheckedProgram};
// use crate::ir::ast::{Program, UncheckedProgram};
// use crate::parser::functions::fun_decl;
// use nom::{combinator::map, multi::many0, IResult};
//
// /// Parse a complete MiniC program: zero or more function declarations.
// /// Execution starts at the `main` function (validated by the type checker).
// pub fn program(input: &str) -> IResult<&str, UncheckedProgram> {
// map(many0(fun_decl), |functions| Program {
// functions,
// constants: vec![]
// })(input)
// }
// use crate::ir::ast::{Program, UncheckedProgram, FunDecl, StatementD};
// use crate::parser::functions::fun_decl;
// // Import the constant statement parser mentioned in your docs
// use crate::parser::statements::const_statement;
//
// use nom::{
// branch::alt,
// combinator::map,
// multi::many0,
// IResult,
// };
//
// /// An internal enum used to differentiate top-level elements during parsing.
// enum GlobalItem {
// Function(FunDecl<()>),
// Constant(StatementD<()>),
// }
//
// /// Parse a complete MiniC program: zero or more global constant declarations
// /// and function declarations in any order.
// pub fn program(input: &str) -> IResult<&str, UncheckedProgram> {
// // 1. Repeatedly parse either a function declaration OR a global constant
// map(
// many0(alt((
// map(fun_decl, GlobalItem::Function),
// map(const_statement, GlobalItem::Constant),
// ))),
// |items| {
// let mut functions = Vec::new();
// let mut constants = Vec::new();
//
// // 2. Separate them into their respective vectors for the Program AST
// for item in items {
// match item {
// GlobalItem::Function(f) => functions.push(f),
// GlobalItem::Constant(c) => constants.push(c),
// }
// }
//
// Program { functions, constants }
// },
// )(input)
// }
use crate::ir::ast::{Program, UncheckedProgram, FunDecl, StatementD};
use crate::parser::functions::fun_decl;
use nom::{combinator::map, multi::many0, IResult};
use crate::parser::statements::const_statement;

use nom::{
branch::alt,
combinator::map,
multi::many0,
character::complete::multispace0,
sequence::delimited,
IResult,
};

enum GlobalItem {
Function(FunDecl<()>),
Constant(StatementD<()>),
}

/// Parse a complete MiniC program: zero or more function declarations.
/// Execution starts at the `main` function (validated by the type checker).
pub fn program(input: &str) -> IResult<&str, UncheckedProgram> {
map(many0(fun_decl), |functions| Program { functions })(input)
// Wrap the top level elements so that trailing or leading whitespace/newlines
// do not prematurely break `many0`
map(
many0(delimited(
multispace0,
alt((
map(fun_decl, GlobalItem::Function),
map(const_statement, GlobalItem::Constant),
)),
multispace0,
)),
|items| {
let mut functions = Vec::new();
let mut constants = Vec::new();

for item in items {
match item {
GlobalItem::Function(f) => functions.push(f),
GlobalItem::Constant(c) => constants.push(c),
}
}

Program { functions, constants }
},
)(input)
}
88 changes: 84 additions & 4 deletions src/parser/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
//! Exposes two public functions:
//!
//! * [`statement`] — the top-level entry point; tries each statement form in
//! order: `return`, `if`, `while`, call-statement, block, declaration,
//! assignment.
//! order: `return`, `if`, `while`, call-statement, block, `const` declaration,
//! declaration, assignment.
//! * [`const_statement`] — `const T name = expr ;`, producing
//! [`Statement::ConstDecl`].
//! * [`assignment`] — parses `lvalue = expression ;`; exported separately
//! because the test suite uses it directly.
//!
Expand Down Expand Up @@ -47,7 +49,7 @@ use crate::parser::identifiers::identifier;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{char, multispace0},
character::complete::{char, multispace0, multispace1},
combinator::{map, opt},
multi::many0,
sequence::{delimited, preceded, tuple},
Expand All @@ -58,7 +60,7 @@ fn wrap(s: Statement<()>) -> UncheckedStmt {
StatementD { stmt: s, ty: () }
}

/// Parse any statement: block | if | while | return | decl | call | assignment.
/// Parse any statement: block | if | while | return | const | decl | call | assignment.
pub fn statement(input: &str) -> IResult<&str, UncheckedStmt> {
preceded(
multispace0,
Expand All @@ -67,6 +69,7 @@ pub fn statement(input: &str) -> IResult<&str, UncheckedStmt> {
if_statement,
while_statement,
return_statement,
const_statement,
decl_statement,
call_statement,
assignment,
Expand All @@ -82,6 +85,30 @@ fn return_statement(input: &str) -> IResult<&str, UncheckedStmt> {
Ok((rest, wrap(Statement::Return(expr.map(Box::new)))))
}

/// Parse `const T name = expr ;`.
///
/// Produces [`Statement::ConstDecl`] so later phases can preserve immutability
/// information.
pub fn const_statement(input: &str) -> IResult<&str, UncheckedStmt> {
map(
tuple((
preceded(multispace0, tag("const")),
preceded(multispace1, type_name),
preceded(multispace1, identifier),
preceded(multispace0, tag("=")),
preceded(multispace0, expression),
preceded(multispace0, char(';')),
)),
|(_, ty, name, _, init, _)| {
wrap(Statement::ConstDecl {
name: name.to_string(),
ty,
init: Box::new(init),
})
},
)(input)
}

/// Parse a variable declaration: `Type ident = expr ;`. Must come before assignment.
fn decl_statement(input: &str) -> IResult<&str, UncheckedStmt> {
map(
Expand Down Expand Up @@ -207,3 +234,56 @@ pub fn assignment(input: &str) -> IResult<&str, UncheckedStmt> {
},
)(input)
}

#[cfg(test)]
mod tests {
use super::statement;
use crate::ir::ast::{Expr, Literal, Statement, Type};
use nom::combinator::all_consuming;

#[test]
fn parses_const_declaration_as_const_decl() {
let (_, stmt) = all_consuming(statement)("const int MAX_SIZE = 100;").expect("should parse");

match stmt.stmt {
Statement::ConstDecl { name, ty, init } => {
assert_eq!(name, "MAX_SIZE");
assert_eq!(ty, Type::Int);
assert_eq!(init.exp, Expr::Literal(Literal::Int(100)));
}
other => panic!("expected ConstDecl, got {:?}", other),
}
}

#[test]
fn parses_const_expression_initializer() {
let (_, stmt) = all_consuming(statement)("const int DOUBLE = 2 * 21;").expect("should parse");

match stmt.stmt {
Statement::ConstDecl { name, ty, .. } => {
assert_eq!(name, "DOUBLE");
assert_eq!(ty, Type::Int);
}
other => panic!("expected ConstDecl, got {:?}", other),
}
}

#[test]
fn keeps_mutable_declaration_as_decl() {
let (_, stmt) = all_consuming(statement)("int x = 5;").expect("should parse");

match stmt.stmt {
Statement::Decl { .. } => {}
Statement::ConstDecl { .. } => panic!("plain Decl must not produce ConstDecl"),
other => panic!("unexpected statement: {:?}", other),
}
}

#[test]
fn rejects_const_without_initializer() {
assert!(
all_consuming(statement)("const int X;").is_err(),
"const without initializer should fail to parse"
);
}
}
Loading