From 21af891574a4b44dd2f901271db8a20fb8999a6e Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sun, 12 Nov 2023 23:03:45 -0600 Subject: [PATCH] A bunch of prerequisites before we can do arrays, part 1 --- forge_core/src/ast.rs | 1 + forge_core/src/compiler/ast_nodes/block.rs | 2 - forge_core/src/compiler/ast_nodes/call.rs | 12 +--- .../src/compiler/ast_nodes/conditional.rs | 2 - forge_core/src/compiler/ast_nodes/const.rs | 2 +- forge_core/src/compiler/ast_nodes/expr.rs | 5 +- forge_core/src/compiler/ast_nodes/function.rs | 39 ++++++++--- forge_core/src/compiler/ast_nodes/lvalue.rs | 1 + .../src/compiler/ast_nodes/repeat_loop.rs | 6 -- forge_core/src/compiler/ast_nodes/return.rs | 6 -- forge_core/src/compiler/ast_nodes/var_decl.rs | 1 - .../src/compiler/ast_nodes/while_loop.rs | 1 - forge_core/src/compiler/compiled_fn.rs | 69 +++++++++++++++---- forge_core/src/compiler/mod.rs | 2 + forge_core/src/compiler/test_utils.rs | 4 ++ forge_core/src/parser/ast_nodes/expr.rs | 6 ++ forge_core/src/parser/forge.pest | 4 +- 17 files changed, 102 insertions(+), 61 deletions(-) diff --git a/forge_core/src/ast.rs b/forge_core/src/ast.rs index 9bc9393..1da378f 100644 --- a/forge_core/src/ast.rs +++ b/forge_core/src/ast.rs @@ -173,6 +173,7 @@ pub enum Expr { Deref(BoxExpr), Address(Lvalue), Call(Call), + New(BoxExpr), Subscript(BoxExpr, BoxExpr), Infix(BoxExpr, Operator, BoxExpr), String(String), diff --git a/forge_core/src/compiler/ast_nodes/block.rs b/forge_core/src/compiler/ast_nodes/block.rs index 331558d..67f9e81 100644 --- a/forge_core/src/compiler/ast_nodes/block.rs +++ b/forge_core/src/compiler/ast_nodes/block.rs @@ -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", diff --git a/forge_core/src/compiler/ast_nodes/call.rs b/forge_core/src/compiler/ast_nodes/call.rs index 65493fe..bf3350c 100644 --- a/forge_core/src/compiler/ast_nodes/call.rs +++ b/forge_core/src/compiler/ast_nodes/call.rs @@ -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: diff --git a/forge_core/src/compiler/ast_nodes/conditional.rs b/forge_core/src/compiler/ast_nodes/conditional.rs index 427b272..d386156 100644 --- a/forge_core/src/compiler/ast_nodes/conditional.rs +++ b/forge_core/src/compiler/ast_nodes/conditional.rs @@ -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 diff --git a/forge_core/src/compiler/ast_nodes/const.rs b/forge_core/src/compiler/ast_nodes/const.rs index 10e624a..b7f2e12 100644 --- a/forge_core/src/compiler/ast_nodes/const.rs +++ b/forge_core/src/compiler/ast_nodes/const.rs @@ -49,7 +49,7 @@ pub fn eval_const(expr: Expr, scope: &Scope) -> Result { 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"), diff --git a/forge_core/src/compiler/ast_nodes/expr.rs b/forge_core/src/compiler/ast_nodes/expr.rs index 8ad02d8..439b858 100644 --- a/forge_core/src/compiler/ast_nodes/expr.rs +++ b/forge_core/src/compiler/ast_nodes/expr.rs @@ -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 diff --git a/forge_core/src/compiler/ast_nodes/function.rs b/forge_core/src/compiler/ast_nodes/function.rs index a0e7a0c..68b6bae 100644 --- a/forge_core/src/compiler/ast_nodes/function.rs +++ b/forge_core/src/compiler/ast_nodes/function.rs @@ -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") + ) + } } \ No newline at end of file diff --git a/forge_core/src/compiler/ast_nodes/lvalue.rs b/forge_core/src/compiler/ast_nodes/lvalue.rs index c35c020..1fd0b93 100644 --- a/forge_core/src/compiler/ast_nodes/lvalue.rs +++ b/forge_core/src/compiler/ast_nodes/lvalue.rs @@ -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"))) diff --git a/forge_core/src/compiler/ast_nodes/repeat_loop.rs b/forge_core/src/compiler/ast_nodes/repeat_loop.rs index 8e3ab70..ed9944f 100644 --- a/forge_core/src/compiler/ast_nodes/repeat_loop.rs +++ b/forge_core/src/compiler/ast_nodes/repeat_loop.rs @@ -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", diff --git a/forge_core/src/compiler/ast_nodes/return.rs b/forge_core/src/compiler/ast_nodes/return.rs index 38eaa48..9c1cea4 100644 --- a/forge_core/src/compiler/ast_nodes/return.rs +++ b/forge_core/src/compiler/ast_nodes/return.rs @@ -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 diff --git a/forge_core/src/compiler/ast_nodes/var_decl.rs b/forge_core/src/compiler/ast_nodes/var_decl.rs index 6820a38..ba3a0a6 100644 --- a/forge_core/src/compiler/ast_nodes/var_decl.rs +++ b/forge_core/src/compiler/ast_nodes/var_decl.rs @@ -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", diff --git a/forge_core/src/compiler/ast_nodes/while_loop.rs b/forge_core/src/compiler/ast_nodes/while_loop.rs index 6941bd1..6346495 100644 --- a/forge_core/src/compiler/ast_nodes/while_loop.rs +++ b/forge_core/src/compiler/ast_nodes/while_loop.rs @@ -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 diff --git a/forge_core/src/compiler/compiled_fn.rs b/forge_core/src/compiler/compiled_fn.rs index 6d50541..e0309d7 100644 --- a/forge_core/src/compiler/compiled_fn.rs +++ b/forge_core/src/compiler/compiled_fn.rs @@ -48,7 +48,10 @@ pub struct CompiledFn { pub frame_size: usize, pub local_scope: Scope, pub arity: usize, + pub alloc: bool, + pub preamble: Vec, pub body: Vec, + pub outro: Vec, } 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(&mut self, opcode: &str, arg: T) { self.body.push(format!("{} {}", opcode, arg)) } + /// Preamble version of emit_arg + pub(crate) fn preamble_emit_arg(&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) -> 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) -> 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"); } } diff --git a/forge_core/src/compiler/mod.rs b/forge_core/src/compiler/mod.rs index 54e1af4..a4943cb 100644 --- a/forge_core/src/compiler/mod.rs +++ b/forge_core/src/compiler/mod.rs @@ -46,7 +46,9 @@ pub fn build_boot(src: &str) -> Result, 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: diff --git a/forge_core/src/compiler/test_utils.rs b/forge_core/src/compiler/test_utils.rs index b6ff695..0501e09 100644 --- a/forge_core/src/compiler/test_utils.rs +++ b/forge_core/src/compiler/test_utils.rs @@ -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") +} diff --git a/forge_core/src/parser/ast_nodes/expr.rs b/forge_core/src/parser/ast_nodes/expr.rs index 874b69e..67d09d6 100644 --- a/forge_core/src/parser/ast_nodes/expr.rs +++ b/forge_core/src/parser/ast_nodes/expr.rs @@ -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()))); + } } \ No newline at end of file diff --git a/forge_core/src/parser/forge.pest b/forge_core/src/parser/forge.pest index 5e5bcc3..ea502bc 100644 --- a/forge_core/src/parser/forge.pest +++ b/forge_core/src/parser/forge.pest @@ -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 ~ "]" }