A bunch of prerequisites before we can do arrays, part 1

This commit is contained in:
2023-11-12 23:03:45 -06:00
parent db15f28adc
commit 21af891574
17 changed files with 102 additions and 61 deletions
+1
View File
@@ -173,6 +173,7 @@ pub enum Expr {
Deref(BoxExpr),
Address(Lvalue),
Call(Call),
New(BoxExpr),
Subscript(BoxExpr, BoxExpr),
Infix(BoxExpr, Operator, BoxExpr),
String(String),
@@ -65,7 +65,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x; asm(&x) { swap 12\nstorew } }")),
vec![
"pushr", // capture frame ptr
"peekr", // Push the addr of x
"swap 12", // The asm body, which swaps 12 behind it and stores it there
"storew",
@@ -80,7 +79,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { repeat(5) c { 2; } var k = 5; return k; }")),
vec![
"pushr", // capture frame ptr
"push 0", // Create the 'c' var and store 0 in it
"peekr",
"storew",
+1 -11
View File
@@ -17,6 +17,7 @@ impl Compilable for Call {
}
// Now we increment the frame ptr to right after the current frame:
// see cursed test case in vtest/forge_tests.rs
sig.emit("peekr");
let frame_size = sig.frame_size();
if frame_size > 0 {
@@ -26,11 +27,6 @@ impl Compilable for Call {
// Eval the target
self.target.0.process(state, Some(sig), loc)?;
// Before we actually do the call though, we need to deal with some paperwork around the
// frame pointer. We will store the current frame pointer in the rstack:
//sig.emit("loadw frame");
//sig.emit("pushr");
// Do the call:
sig.emit("call");
@@ -53,12 +49,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a, b) { test(2, 3); }")),
vec![
"pushr",
"peekr", // capture arg b
"add 3",
"storew",
"peekr", // capture arg a
"storew",
"push 2", // evaluating args, in order
"push 3",
"peekr", // The new fn's frame ptr:
@@ -31,7 +31,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; if (x > 2) { x = 1; } }")),
vec![
"pushr",
"push 3",
"peekr",
"storew", // x = 3
@@ -55,7 +54,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; if (x > 2) { x = 1; } else { x = 7; } }")),
vec![
"pushr",
"push 3",
"peekr",
"storew", // x = 3
+1 -1
View File
@@ -49,7 +49,7 @@ pub fn eval_const(expr: Expr, scope: &Scope) -> Result<i32, CompileError> {
0,
String::from("Addresses are not known at compile time"),
)),
Expr::Call(_) | Expr::Subscript(_, _) => Err(CompileError(
Expr::Call(_) | Expr::Subscript(_, _) | Expr::New(_) => Err(CompileError(
0,
0,
String::from("Constants must be statically defined"),
+1 -4
View File
@@ -86,6 +86,7 @@ impl Compilable for Expr {
Ok(())
}
Expr::Call(call) => call.process(state, Some(sig), loc),
Expr::New(size) => todo!("new not yet supported"),
Expr::Subscript(_, _) => todo!("Structs and arrays are not yet supported"),
Expr::Infix(lhs, op, rhs) => {
// Recurse on expressions, handling operators
@@ -158,7 +159,6 @@ mod test {
assert_eq!(
test_body(state),
vec![
"pushr",
"push _forge_gensym_3",
"peekr",
"storew", // the assignment for x
@@ -177,7 +177,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; var y = &x; }")),
vec![
"pushr",
"push 3",
"peekr",
"storew", // the assignment for x
@@ -196,7 +195,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; *1000 = *x; }")),
vec![
"pushr",
"push 3",
"peekr",
"storew", // the assignment for x
@@ -216,7 +214,6 @@ mod test {
assert_eq!(
test_body(state_for("const foo = \"foo\"; fn test() { var x = \"bar\" + 3; }")),
vec![
"pushr",
"push _forge_gensym_3", // 1 is the label in the string table for "foo", 2 for "blah,"
"push 3", // so 3 is the string "bar"
"add", // Add 3 to that address
+28 -11
View File
@@ -14,20 +14,27 @@ impl Compilable for Function {
..Default::default()
};
// This generates the code to copy the args into the frame as well as adds all the arguments
// to the local scope and calculates the arity.
sig.handle_args(&self)?;
// todo we need to store arity somehow in the state, so we can check arglists even
// with recursive calls. This probably becomes splitting "signature" from "function context"
// and setting signature immutably right now, passing context down ("block?") and setting it
// at the end
// Some stuff needs to happen before we can compile the body, we need to at least know how
// many args there are, so they're accounted in the frame size:
sig.calculate_arity(&self.args)?;
// Compile the body, storing all of it in the CompiledFn we just created / added
self.body.process(state, Some(&mut sig), loc)?;
// This generates the code to copy the args into the frame as well as adds all the arguments
// to the local scope and deals with the pool pointer. We can't run this until after the
// body is compiled (won't know if we have any args / need to mess with pool) but things
// created here will be emitted before (preamble) and after (outro) the body
sig.generate_preamble_outro(&self.args)?;
// This fn probably has a return statement... but it's not required. As a final catch just
// in case we fall through to this point, we'll generate a void return and compile it:
// TODO this can die soon because the outro is implicitly a return so we'd just fall into that
Return(None).process(state, Some(&mut sig), loc)?;
// This can't fail because if it were a dupe name, adding the global would have failed
@@ -42,19 +49,13 @@ impl Compilable for Function {
#[cfg(test)]
mod test {
use crate::compiler::test_utils::{state_for, test_body};
use crate::compiler::test_utils::{state_for, test_body, test_preamble};
#[test]
fn test_basic_fns() {
assert_eq!(
test_body(state_for("fn test(a, b) { b = 17 + a; }")),
vec![
"pushr", // Store frame ptr
"peekr", // Capture var b
"add 3",
"storew",
"peekr", // Capture var a
"storew",
"push 17", // Start calculating the rvalue, push the literal
"peekr", // This is looking up the "a" arg, at frame + 0
"loadw",
@@ -67,4 +68,20 @@ mod test {
.join("\n")
)
}
#[test]
fn test_args_preamble() {
assert_eq!(
test_preamble(state_for("fn test(a, b) { b = 17 + a; }")),
vec![
"pushr", // Store frame ptr
"peekr", // Capture var b
"add 3",
"storew",
"peekr", // Capture var a
"storew",
]
.join("\n")
)
}
}
@@ -18,6 +18,7 @@ impl Compilable for Lvalue {
Expr::Not(_) |
Expr::Address(_) |
Expr::Call(_) |
Expr::New(_) |
Expr::Infix(_, _, _) |
Expr::String(_) => {
Err(CompileError(0, 0, String::from("Not a valid lvalue")))
@@ -86,9 +86,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { var x = 0; repeat(a) c { x = x + c; } return x; }")),
vec![
"pushr",
"peekr", // capture arg a
"storew",
"push 0", // Create the 'x' var and store 0 in it
"peekr",
"add 3",
@@ -144,9 +141,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { var x = 1; repeat(a) { x = x * 2; } return x; }")),
vec![
"pushr",
"peekr", // capture arg a
"storew",
"push 1", // Create the 'x' var and store 1 in it
"peekr",
"add 3",
@@ -42,9 +42,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { return a + 3; }")),
vec![
"pushr",
"peekr", // capture arg a
"storew",
"peekr", // Load a
"loadw",
"push 3", // Add 3
@@ -63,9 +60,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { if (a > 0) { return; } }")),
vec![
"pushr",
"peekr", // capture arg a
"storew",
"peekr", // Load a
"loadw",
"push 0", // Compare to 0
@@ -36,7 +36,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var a; var b = 7; a = b * 2; }")),
vec![
"pushr",
"push 7", // Start calculating the rvalue, push the literal
"peekr", // "b" is the second local var at frame + 3
"add 3",
@@ -27,7 +27,6 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 0; var c = 0; while (c < 10) { x = x + c; c = c + 1; } }")),
vec![
"pushr",
"push 0",
"peekr",
"storew", // var x = 0
+54 -15
View File
@@ -48,7 +48,10 @@ pub struct CompiledFn {
pub frame_size: usize,
pub local_scope: Scope,
pub arity: usize,
pub alloc: bool,
pub preamble: Vec<String>,
pub body: Vec<String>,
pub outro: Vec<String>,
}
impl CompiledFn {
@@ -72,45 +75,81 @@ impl CompiledFn {
self.body.push(String::from(opcode))
}
/// Emit a string (ideally one instruction, but whatever) to the function preamble.
/// This is very similar to `emit` but it appends the line to a section that will be written
/// before the body. If we need to write something that depends on the body but must be run
/// before it, it gets written through here.
pub(crate) fn preamble_emit(&mut self, opcode: &str) {
self.preamble.push(String::from(opcode))
}
/// Emit a string (ideally one instruction, but whatever) to the function outro. Just like the
/// body and preamble, the outro is part of the fn, but will be emitted after the fn body. Any
/// cleanup that needs to happen should happen here.
pub(crate) fn outro_emit(&mut self, opcode: &str) {
self.outro.push(String::from(opcode))
}
/// A shorthand method to emit something with a `Display` arg, because emitting a
/// single instruction with a variable (numeric or label) arg is very common.
pub(crate) fn emit_arg<T: Display>(&mut self, opcode: &str, arg: T) {
self.body.push(format!("{} {}", opcode, arg))
}
/// Preamble version of emit_arg
pub(crate) fn preamble_emit_arg<T: Display>(&mut self, opcode: &str, arg: T) {
self.preamble.push(format!("{} {}", opcode, arg))
}
/// The size of the local scope in bytes. This increases as variables are declared
/// todo: this needs to change for arrays; it won't like having vars that aren't 3 bytes long
pub(crate) fn frame_size(&self) -> usize {
self.local_scope.len() * 3
}
/// Emit the code to add the arguments' names to the local scope, and move the arguments off
/// the stack into the frame. Should be run before the body is compiled, because this code
/// has to be the first thing in the body
pub(crate) fn handle_args(&mut self, function: &Function) -> Result<(), CompileError> {
let mut arg_names: Vec<&str> = Vec::new();
// Top argument is the frame ptr; copy it to the rstack:
self.emit("pushr");
/// We won't generate the code to actually capture the args until we generate the preamble
/// (which we can't do until we compile the body) but we need to do some recognition of the
/// args here: count how many there are, add them to the scope, so we know how to refer to them.
pub(crate) fn calculate_arity(&mut self, args: &Vec<String>) -> Result<(), CompileError> {
// Add each argument as a local
for name in function.args.iter() {
for name in args.iter() {
self.add_local(name.as_str())?;
arg_names.push(name.as_str());
self.arity += 1;
}
Ok(())
}
/// Emit the code to add the arguments' names to the local scope, and move the arguments off
/// the stack into the frame. Should be run after the body is compiled, because this code
/// depends on knowledge of the body of the fn, but what this produces will be emitted to
/// the final listing before the fn body
pub(crate) fn generate_preamble_outro(&mut self, args: &Vec<String>) -> Result<(), CompileError> {
let mut arg_names: Vec<&str> = Vec::new();
// Does our fn make any allocations? If so we need to save the old alloc pool pointer:
if self.alloc {
todo!("alloc pool not actually implemented");
self.preamble_emit("loadw pool");
self.preamble_emit("pushr");
}
// Top argument is the frame ptr; copy it to the rstack:
self.preamble_emit("pushr");
// Add each argument as a local
let mut arg_names: Vec<_> = args.iter().map(|a| a.clone()).collect();
// Arguments in calls are pushed with the last on top, so, reverse the vec just to make
// the following loop easier:
arg_names.reverse();
for name in arg_names {
if let Variable::Local(offset) = self.local_scope[name] {
self.emit("peekr");
if let Variable::Local(offset) = self.local_scope[&name] {
self.preamble_emit("peekr");
if offset != 0 {
self.emit_arg("add", offset);
self.preamble_emit_arg("add", offset);
}
self.emit("storew");
self.preamble_emit("storew");
}
}
+2
View File
@@ -46,7 +46,9 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
}
for (_, val) in state.functions.iter_mut() {
asm.push(format!("{}:", val.label));
asm.append(val.preamble.as_mut());
asm.append(val.body.as_mut());
asm.append(val.outro.as_mut());
}
// Final thing is to place a label for the stack:
+4
View File
@@ -14,3 +14,7 @@ pub(crate) fn state_for(src: &str) -> State {
pub(crate) fn test_body(state: State) -> String {
state.functions.get("test").unwrap().body.join("\n")
}
pub(crate) fn test_preamble(state: State) -> String {
state.functions.get("test").unwrap().preamble.join("\n")
}
+6
View File
@@ -18,6 +18,7 @@ impl AstNode for Expr {
PRATT_PARSER
.map_primary(|term| match term.as_rule() {
PestRule::number => Expr::Number(term.into_number()),
PestRule::alloc => Expr::New(Expr::from_pair(term.first()).into()),
PestRule::name => Expr::Name(String::from(term.as_str())),
PestRule::expr => Expr::from_pair(term),
PestRule::string => Self::String(term.into_quoted_string()),
@@ -269,4 +270,9 @@ mod test {
Ok(Expr::Call(Call { target: "blah".into(), args: vec![Expr::String("foo".into()), 2.into()] }))
);
}
#[test]
fn alloc() {
assert_eq!(Expr::from_str("new(8)"), Ok(Expr::New(8.into())));
}
}
+3 -1
View File
@@ -63,7 +63,9 @@ operator = _{
}
expr = { prefix* ~ term ~ suffix* ~ (operator ~ prefix* ~ term ~ suffix*)* }
term = _{ number | name | "(" ~ expr ~ ")" | string }
term = _{ number | alloc | name | "(" ~ expr ~ ")" | string }
alloc = { "new" ~ "(" ~ expr ~ ")" }
suffix = _{ subscript | arglist }
subscript = { "[" ~ expr ~ "]" }