build_boot for tests

This commit is contained in:
2023-11-07 21:59:05 -06:00
parent f03f6f15a4
commit 62d340e507
7 changed files with 132 additions and 3 deletions
+81
View File
@@ -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<ParseError> 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<i32, CompileError> {
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// 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<Vec<String>, 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<String> = 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"))
}
}