From 62d340e507ca57de359c5c2de29ad872a0abf104 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Tue, 7 Nov 2023 21:59:05 -0600 Subject: [PATCH] build_boot for tests --- Cargo.lock | 1 + forge_core/src/compiler.rs | 81 ++++++++++++++++++++++++++++++++++ forge_core/src/forge_parser.rs | 2 +- forge_core/src/lib.rs | 2 +- vtest/Cargo.toml | 3 +- vtest/src/forge_tests.rs | 45 +++++++++++++++++++ vtest/src/lib.rs | 1 + 7 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 vtest/src/forge_tests.rs diff --git a/Cargo.lock b/Cargo.lock index bca5a85..43e9e05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1504,6 +1504,7 @@ dependencies = [ name = "vtest" version = "0.1.0" dependencies = [ + "forge_core", "vasm_core", "vcore", ] diff --git a/forge_core/src/compiler.rs b/forge_core/src/compiler.rs index 1f57c65..85a2ed3 100644 --- a/forge_core/src/compiler.rs +++ b/forge_core/src/compiler.rs @@ -3,10 +3,17 @@ use crate::ast::*; use std::collections::btree_map::Entry::Vacant; use std::collections::{BTreeMap}; use std::fmt::{Display, Formatter}; +use crate::forge_parser::{ParseError, parse}; #[derive(Eq, Clone, PartialEq, Debug)] pub struct CompileError(usize, usize, String); +impl From for CompileError { + fn from(value: ParseError) -> Self { + Self(value.0, value.1, value.2) + } +} + impl Display for CompileError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "({}:{}) {}", self.0, self.1, self.2) @@ -775,6 +782,50 @@ pub fn eval_const(expr: Expr, scope: &Scope) -> Result { } } +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Turn a `Program` into a list of assembly lines that can be assembled to run it. +/// There are various ways to build a program, this one is the simplest: it builds it as a +/// complete ROM that will place a jmp to main() at 0x400, so a Vulcan can boot from it. +pub fn build_boot(src: &str) -> Result, CompileError> { + let mut state = State::default(); + let ast = parse(src).map_err(CompileError::from)?; + ast.process(&mut state, None, (0, 0).into())?; + + if let Some(Variable::DirectLabel(label)) = state.global_scope.get("main") { + // Let's make a vec for the final listing + let mut asm: Vec = Vec::new(); + + // Now we start piling stuff into the vec, starting with an org: + asm.push(".org 0x400".into()); + + // jmp into main: + asm.push(format!("call {}", label)); + + // When main returns, just hlt: + asm.push("hlt".into()); + + // Now start dumping compiled objects into there: + for (label, val) in state.strings.iter() { + asm.push(format!("{}: .db \"{}\\0\"", label, val)) + } + for (_, val) in state.functions.iter_mut() { + asm.push(format!("{}:", val.label)); + asm.append(val.body.as_mut()); + } + + // Final thing is to place a label for the stack: + // (this is just a cell with the address of the following word) + asm.push("frame: .db $+1".into()); + asm.push(".db 0".into()); + Ok(asm) + } else { + Err(CompileError(0, 0, "Function main not defined (or not a function)".into())) + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + #[cfg(test)] mod test { use super::*; @@ -1226,4 +1277,34 @@ mod test { ].join("\n") ); } + + #[test] + fn test_build_boot() { + // Very basic test of one main() + let asm = build_boot("fn main() { return 5; }".into()).unwrap(); + assert_eq!(asm.join("\n"), vec![ + ".org 0x400", + "call _forge_gensym_1", + "hlt", + "_forge_gensym_1:", + "push 5", + "ret", + "frame: .db $+1", + ".db 0" + ].join("\n")); + + // Slightly more complicated, with a global str + let asm = build_boot("const str = \"blah\"; fn main() { return str; }".into()).unwrap(); + assert_eq!(asm.join("\n"), vec![ + ".org 0x400", + "call _forge_gensym_2", + "hlt", + "_forge_gensym_1: .db \"blah\\0\"", + "_forge_gensym_2:", + "push _forge_gensym_1", + "ret", + "frame: .db $+1", + ".db 0" + ].join("\n")) + } } diff --git a/forge_core/src/forge_parser.rs b/forge_core/src/forge_parser.rs index 9cdb161..525ee92 100644 --- a/forge_core/src/forge_parser.rs +++ b/forge_core/src/forge_parser.rs @@ -110,7 +110,7 @@ impl PairsExt for Peekable> { } #[derive(Eq, PartialEq, Clone, Debug)] -pub struct ParseError(usize, usize, String); +pub struct ParseError(pub usize, pub usize, pub String); impl From> for ParseError { fn from(err: Error) -> Self { diff --git a/forge_core/src/lib.rs b/forge_core/src/lib.rs index bd3ea00..09231af 100644 --- a/forge_core/src/lib.rs +++ b/forge_core/src/lib.rs @@ -3,5 +3,5 @@ extern crate pest; extern crate pest_derive; mod ast; -mod compiler; +pub mod compiler; mod forge_parser; diff --git a/vtest/Cargo.toml b/vtest/Cargo.toml index 704cfc1..c672784 100644 --- a/vtest/Cargo.toml +++ b/vtest/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" [dependencies] vcore = { path = "../vcore" } -vasm_core = { path = "../vasm_core" } \ No newline at end of file +vasm_core = { path = "../vasm_core" } +forge_core = { path = "../forge_core" } \ No newline at end of file diff --git a/vtest/src/forge_tests.rs b/vtest/src/forge_tests.rs new file mode 100644 index 0000000..485f262 --- /dev/null +++ b/vtest/src/forge_tests.rs @@ -0,0 +1,45 @@ +use vasm_core::assemble_snippet; +use vcore::memory::{Memory, PeekPokeExt}; +use vcore::word::Word; +use vcore::CPU; + +fn run_forge(src: &str) -> CPU { + let asm = forge_core::compiler::build_boot(src).unwrap(); + let bin = assemble_snippet(asm).unwrap(); + let mut cpu = CPU::new(Memory::with_program(bin)); + cpu.run_to_halt(); + cpu +} + +fn main_return(src: &str) -> i32 { + let mut cpu = run_forge(src); + cpu.pop_data().into() +} + +#[test] +fn basic_forge_test() { + assert_eq!( + main_return("fn main() { return 2 + 3; }"), + 5 + ) +} + +// TODO The reason this fails is that functions don't copy their args to the frame when they enter. +// The fix is for fns to know their arity and to do that copy before the body, probably as some +// subroutine call itself (push frame, push arity, call blah, which is written in asm) +// #[test] +// fn loop_test() { +// assert_eq!( +// main_return( +// "fn triangle(rows) { +// var total = 0; +// repeat (rows) n { +// total = total + (n + 1); +// } +// return total; +// } +// +// fn main() { return triangle(5); }"), +// 15 +// ) +// } \ No newline at end of file diff --git a/vtest/src/lib.rs b/vtest/src/lib.rs index 941250f..9ad4492 100644 --- a/vtest/src/lib.rs +++ b/vtest/src/lib.rs @@ -1,2 +1,3 @@ #[cfg(test)] mod integration_tests; +mod forge_tests; \ No newline at end of file