Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions src/common/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ impl Graph {

/// Builds a [`Graph`] from a slice of [`CfgNode`] implementors.
///
/// This method first collects all node labels into a name-to-index map,
/// then calls [`CfgNode::successors`] on each node to compute the full
/// successor adjacency list, and finally delegates to [`Graph::new`].
/// All node labels are collected into a name-to-index map before any
/// [`CfgNode::successors`] call runs, so a node may name a branch target
/// that appears later in `nodes`.
pub fn from_nodes<N: CfgNode>(nodes: &[N]) -> Self {
let n = nodes.len();
let label_map: HashMap<String, usize> = nodes
Expand Down
20 changes: 8 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! This binary ties together all compiler stages in order:
//! parsing → IR generation → optimization → assembly emission.
//! The output stage can be stopped early (via `--emit`) to inspect
//! The pipeline can be stopped early (via `--emit`) to inspect
//! the AST, IR, or final AArch64 assembly.

mod asm;
Expand All @@ -22,8 +22,8 @@ use std::{
path::{Path, PathBuf},
};

/// Controls which intermediate representation the compiler writes to the output.
/// The pipeline always runs up to (and including) the chosen stage, then exits.
/// Selects which pipeline stage's output is written.
/// The pipeline always runs up to (and including) that stage, then exits.
#[derive(Copy, Clone, Debug, PartialEq, ValueEnum)]
enum EmitTarget {
/// Stop after parsing and emit the Abstract Syntax Tree.
Expand All @@ -43,7 +43,7 @@ struct Cli {
#[clap(value_name = "FILE")]
input: String,

/// Which IR stage to emit as output (default: `asm`).
/// Which pipeline stage to emit as output (default: `asm`).
#[arg(long, value_enum, ignore_case = true, default_value = "asm")]
emit: EmitTarget,

Expand Down Expand Up @@ -108,7 +108,6 @@ fn run() -> Result<()> {
.generate()
.with_context(|| format!("failed to parse '{}'", cli.input))?;

// Early exit: the user only wants the AST dump.
if cli.emit == EmitTarget::Ast {
return parser
.output(&mut writer)
Expand All @@ -130,20 +129,17 @@ fn run() -> Result<()> {
let mut ir_gen = ir::IrGenerator::with_default_passes(ast, source_dir);
ir_gen.generate().context("failed to generate IR")?;

// Optimization stage: run the default function-pass pipeline over
// the freshly generated IR. `Optimizer::with_default_passes` takes
// `&mut ir_gen.module`, and the assembly generator below needs to
// reborrow the module immutably. Confining the optimizer to its own
// scope forces Rust to drop that mutable borrow before the asm stage
// starts.
// `Optimizer::with_default_passes` takes `&mut ir_gen.module`, and the
// assembly generator below reborrows the module immutably. Confining the
// optimizer to its own scope forces Rust to drop that mutable borrow
// before the asm stage starts.
{
let mut optimizer =
opt::Optimizer::with_default_passes(&mut ir_gen.module, &ir_gen.registry);
optimizer
.generate()
.context("failed to run optimization passes")?;

// Early exit: the user only wants the (optimized) IR dump.
if cli.emit == EmitTarget::Ir {
return optimizer
.output(&mut writer)
Expand Down
61 changes: 3 additions & 58 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ use regex::Regex;

static INIT: Once = Once::new();

// ---------------------------------------------------------------------------
// Platform detection
// ---------------------------------------------------------------------------

fn is_native_macos() -> bool {
cfg!(all(target_os = "macos", target_arch = "aarch64"))
}
Expand Down Expand Up @@ -94,10 +90,6 @@ fn ensure_cross_tools() {
}
}

// ---------------------------------------------------------------------------
// stdlib object (tests/std/std.o) build
// ---------------------------------------------------------------------------

/// Platform-specific path for the compiled stdlib object:
/// - macOS AArch64 → `tests/std/std-macos.o`
/// - Docker macOS / cross-compile Linux → `tests/std/std-linux.o`
Expand Down Expand Up @@ -206,10 +198,6 @@ fn ensure_std() {
});
}

// ---------------------------------------------------------------------------
// Compile / link / run primitives
// ---------------------------------------------------------------------------

/// Invokes `teac --emit asm` from `dir` to compile `input_file` into
/// `output_file`. `input_file` must be a bare filename so `teac`'s
/// `source_dir` resolves to `dir` and `use std;` finds `./std.teah`.
Expand Down Expand Up @@ -370,10 +358,6 @@ fn run_with_qemu(exe: &Path, input: Option<&Path>) -> io::Result<(i32, Vec<u8>,
run_with_optional_stdin(&mut cmd, input)
}

// ---------------------------------------------------------------------------
// Output comparison helpers
// ---------------------------------------------------------------------------

/// Whitespace-insensitive normalisation: collapses runs of whitespace
/// within each line to a single space, drops blank lines, appends a
/// trailing newline.
Expand Down Expand Up @@ -412,10 +396,6 @@ fn append_line<P: AsRef<Path>>(path: P, line: &str) {
writeln!(f, "{line}").expect("Failed to append line");
}

// ---------------------------------------------------------------------------
// Test drivers
// ---------------------------------------------------------------------------

/// Returns every function name declared in `source`, in source order.
/// Matches any line whose first non-whitespace token is the `fn`
/// keyword followed by an identifier and an opening parenthesis;
Expand All @@ -439,9 +419,8 @@ fn extract_fn_names(source: &str) -> Vec<String> {
/// receives the absolute source path, so `source_dir` resolves to the
/// test-case directory without a `current_dir` override.
//
// `#[allow(dead_code)]` because every in-tree caller is under a
// not-enabled-by-default `#[cfg(feature = ...)]` for a future language
// feature (float / for-loop / struct-method / multi-dim-array).
// `#[allow(dead_code)]`: every in-tree caller is behind a non-default
// `#[cfg(feature = ...)]` (float / for-loop / struct-method / multi-dim-array).
#[allow(dead_code)]
fn test_ast_parse(test_name: &str) {
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
Expand Down Expand Up @@ -526,7 +505,6 @@ fn test_ir(test_name: &str) {
let build_dir = case_dir.join("build");
fs::create_dir_all(&build_dir).expect("Failed to create build dir");

// Step 1: Generate IR
let tool = Path::new(env!("CARGO_BIN_EXE_teac"));
let ir_output = Command::new(tool)
.arg(&tea)
Expand All @@ -550,7 +528,6 @@ fn test_ir(test_name: &str) {
fs::write(&ll_path, &ir_output.stdout)
.unwrap_or_else(|e| panic!("Failed to write {}: {e}", ll_path.display()));

// Step 2: Compile IR + std.c → executable
let std_c = std_dir.join("std.c");
let exe = build_dir.join(format!("{test_name}_ir"));

Expand All @@ -570,7 +547,6 @@ fn test_ir(test_name: &str) {
clang_output.status.code().unwrap_or(-1)
);

// Step 3: Run the executable
let input = case_dir.join(format!("{test_name}.in"));
let input_path = if input.is_file() {
Some(input.as_path())
Expand All @@ -588,7 +564,6 @@ fn test_ir(test_name: &str) {
}
}

// Step 4: Compare output against golden .out file
let expected_out = case_dir.join(format!("{test_name}.out"));
let actual_out = build_dir.join(format!("{test_name}_ir.out"));

Expand Down Expand Up @@ -698,9 +673,6 @@ fn test_single(test_name: &str) {
tea.display()
);

// -----------------------------------------------------------------------
// Step 1: Compile TeaLang source to assembly
// -----------------------------------------------------------------------
let output_name = format!("{test_name}.s");
let output_path = out_dir.join(&output_name);
let output = launch(
Expand All @@ -725,9 +697,6 @@ fn test_single(test_name: &str) {
output_path.display()
);

// -----------------------------------------------------------------------
// Step 2: Locate the pre-built stdlib object file
// -----------------------------------------------------------------------
let stdlib = get_std_o_path();
assert!(
stdlib.is_file(),
Expand All @@ -745,9 +714,6 @@ fn test_single(test_name: &str) {
None
};

// -----------------------------------------------------------------------
// Step 3: Link assembly + stdlib → executable and run (platform-specific)
// -----------------------------------------------------------------------
let (run_code, run_stdout, run_stderr) = if is_native_macos() {
let exe = out_dir.join(test_name);
let (link_code, link_err) =
Expand Down Expand Up @@ -799,16 +765,10 @@ fn test_single(test_name: &str) {
}
}

// -----------------------------------------------------------------------
// Step 4: Write actual output (stdout + exit code) to file
// -----------------------------------------------------------------------
fs::write(&actual_out, &run_stdout)
.unwrap_or_else(|e| panic!("Failed to write {}: {e}", actual_out.display()));
append_line(&actual_out, &run_code.to_string());

// -----------------------------------------------------------------------
// Step 5: Compare actual output against the golden .out file
// -----------------------------------------------------------------------
match read_to_string_if_exists(&expected_out).expect("Failed to read expected output file") {
Some(exp) => {
let got = fs::read_to_string(&actual_out)
Expand All @@ -833,10 +793,6 @@ fn test_single(test_name: &str) {
}
}

// ---------------------------------------------------------------------------
// Test declaration macros
// ---------------------------------------------------------------------------

/// Declares a batch of `test_single` tests from test-case names.
macro_rules! full_tests {
($($name:ident),* $(,)?) => {
Expand Down Expand Up @@ -888,10 +844,6 @@ macro_rules! asmt_tests {
};
}

// -----------------------------------------------------------------------
// Full compile-link-run tests
// -----------------------------------------------------------------------

full_tests! {
dfs,
bfs,
Expand Down Expand Up @@ -925,7 +877,6 @@ full_tests! {
type_infer_basic,
}

// Return-type-inference tests (feature-gated)
// type_infer_1..5 exercise the return-type-inference pass (every `fn`
// omits its `-> T` clause). Without the feature the baseline treats
// omitted returns as `-> void`, so these tests would fail spuriously.
Expand All @@ -945,13 +896,7 @@ fn type_infer_5() {
test_compile_error("type_infer_5");
}

// Assignment tests
//
// Each test runs `asmt_tests!`, which always performs AST parsing and
// then dispatches to one deeper stage based on the `asmt-tests-*`
// feature: `ast` stops after AST, `ir` runs `test_ir`, and `asm` (the
// default when no `asmt-tests-*` is set) runs `test_single` end-to-end.
// Per-assignment feature flags select which batch is compiled:
// Run per assignment feature:
// cargo test --features float
// cargo test --features for-loop
// cargo test --features struct-method
Expand Down