Compiling calls
This commit is contained in:
@@ -135,6 +135,12 @@ pub struct Asm {
|
||||
pub body: String
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct Call {
|
||||
pub target: BoxExpr,
|
||||
pub args: Vec<Expr>
|
||||
}
|
||||
|
||||
/// One of the five arithmetical operators
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum Operator {
|
||||
@@ -166,7 +172,7 @@ pub enum Expr {
|
||||
Neg(BoxExpr),
|
||||
Deref(BoxExpr),
|
||||
Address(Lvalue),
|
||||
Call(BoxExpr, Vec<Expr>),
|
||||
Call(Call),
|
||||
Subscript(BoxExpr, BoxExpr),
|
||||
Infix(BoxExpr, Operator, BoxExpr),
|
||||
String(String),
|
||||
|
||||
@@ -99,6 +99,12 @@ impl CompiledFn {
|
||||
fn emit_arg<T: Display>(&mut self, opcode: &str, arg: T) {
|
||||
self.body.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
|
||||
fn frame_size(&self) -> usize {
|
||||
self.local_scope.len() * 3
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps from names to the variables they represent
|
||||
@@ -296,9 +302,6 @@ impl Compilable for Function {
|
||||
// and setting signature immutably right now, passing context down ("block?") and setting it
|
||||
// at the end
|
||||
|
||||
// Add it to the global namespace so we can make recursive calls
|
||||
// state.add_global(&self.name, |_| Variable::DirectLabel(sig.label.clone()))?;
|
||||
|
||||
// Compile the body, storing all of it in the CompiledFn we just created / added
|
||||
self.body.process(state, Some(&mut sig), loc)?;
|
||||
|
||||
@@ -380,7 +383,7 @@ impl Compilable for Lvalue {
|
||||
Expr::Neg(_) |
|
||||
Expr::Not(_) |
|
||||
Expr::Address(_) |
|
||||
Expr::Call(_, _) |
|
||||
Expr::Call(_) |
|
||||
Expr::Infix(_, _, _) |
|
||||
Expr::String(_) => {
|
||||
Err(CompileError(0, 0, String::from("Not a valid lvalue")))
|
||||
@@ -428,6 +431,50 @@ impl Compilable for Lvalue {
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
impl Compilable for Call {
|
||||
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> {
|
||||
// Require a function
|
||||
let sig = sig.expect("lvalue outside a function");
|
||||
|
||||
// We need a stack consisting of the arguments (last on top), followed by the address to call
|
||||
// So, first eval the args:
|
||||
for arg in self.args {
|
||||
arg.process(state, Some(sig), loc)?;
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
// Now we increment the frame ptr to right after the current frame:
|
||||
let frame_size = sig.frame_size();
|
||||
if frame_size > 0 {
|
||||
sig.emit("loadw frame");
|
||||
sig.emit_arg("add", frame_size);
|
||||
sig.emit("storew frame");
|
||||
}
|
||||
|
||||
// Frame is now pointing at a safe place, the top of stack is the target, do the call:
|
||||
sig.emit("call");
|
||||
|
||||
// And this is where we'll return to. The function has popped its args and left a word on
|
||||
// the stack as a return value, which someone else will deal with (this is the word that
|
||||
// this expr::call will end up evaluating to). But before we're done, we need to restore
|
||||
// our frame pointerS
|
||||
sig.emit("popr");
|
||||
sig.emit("storew frame");
|
||||
|
||||
// And we're finished!
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
/// Evaluate an expression in the context of a local scope. The runtime brother to eval_const.
|
||||
/// This recursively evaluates a Node and leaves its value on the stack.
|
||||
impl Compilable for Expr {
|
||||
@@ -506,7 +553,7 @@ impl Compilable for Expr {
|
||||
sig.emit_arg("push", label);
|
||||
Ok(())
|
||||
}
|
||||
Expr::Call(_, _) => todo!(),
|
||||
Expr::Call(call) => call.process(state, Some(sig), loc),
|
||||
Expr::Subscript(_, _) => todo!("Structs and arrays are not yen supported"),
|
||||
Expr::Infix(lhs, op, rhs) => {
|
||||
// Recurse on expressions, handling operators
|
||||
@@ -628,7 +675,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(_, _) => Err(CompileError(
|
||||
0,
|
||||
0,
|
||||
String::from("Constants must be statically defined"),
|
||||
@@ -976,4 +1023,26 @@ mod test {
|
||||
assert_eq!(state.functions.remove("blah").unwrap().label, String::from("_forge_gensym_1"));
|
||||
assert_eq!(state.prototypes.remove("blah"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calls() {
|
||||
assert_eq!(
|
||||
test_body(state_for("fn test(a, b) { test(2, 3); }")),
|
||||
vec![
|
||||
"push 2", // evaluating args, in order
|
||||
"push 3",
|
||||
"push _forge_gensym_1", // evaluating target (this fn)
|
||||
"loadw frame", // Store the frame ptr
|
||||
"pushr",
|
||||
"loadw frame", // Increment the frame ptr
|
||||
"add 6",
|
||||
"storew frame",
|
||||
"call", // Actually make the call
|
||||
"popr", // Restore the frame ptr
|
||||
"storew frame",
|
||||
"pop" // expr-as-statement drops the evaluated value
|
||||
]
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,8 +416,10 @@ impl AstNode for Expr {
|
||||
})
|
||||
.map_postfix(|expr, suffix| match suffix.as_rule() {
|
||||
Rule::arglist => Expr::Call(
|
||||
expr.into(),
|
||||
suffix.into_inner().map(Expr::from_pair).collect(),
|
||||
Call {
|
||||
target: expr.into(),
|
||||
args: suffix.into_inner().map(Expr::from_pair).collect(),
|
||||
}
|
||||
),
|
||||
Rule::subscript => {
|
||||
Expr::Subscript(expr.into(), Expr::from_pair(suffix.first()).into())
|
||||
@@ -695,7 +697,7 @@ mod test {
|
||||
// compiler can detect this and error at that stage.
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo()"),
|
||||
Ok(Expr::Address(Expr::Call("foo".into(), vec![]).into()))
|
||||
Ok(Expr::Address(Expr::Call(Call { target: "foo".into(), args: vec![] }).into()))
|
||||
);
|
||||
|
||||
// Dereferencing
|
||||
@@ -712,7 +714,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn parse_calls() {
|
||||
let blah = Expr::Call("blah".into(), vec![]);
|
||||
let blah = Expr::Call(Call { target: "blah".into(), args: vec![] });
|
||||
|
||||
// Can Node parse a call?
|
||||
assert_eq!(Expr::from_str("blah()"), Ok(blah.clone()));
|
||||
@@ -723,13 +725,13 @@ mod test {
|
||||
// Calls with args
|
||||
assert_eq!(
|
||||
Expr::from_str("blah(1, 2)"),
|
||||
Ok(Expr::Call("blah".into(), vec![1.into(), 2.into()]))
|
||||
Ok(Expr::Call(Call { target: "blah".into(), args: vec![1.into(), 2.into()] }))
|
||||
);
|
||||
|
||||
//Calls with strings
|
||||
assert_eq!(
|
||||
Expr::from_str("blah(\"foo\", 2)"),
|
||||
Ok(Expr::Call("blah".into(), vec![Expr::String("foo".into()), 2.into()]))
|
||||
Ok(Expr::Call(Call { target: "blah".into(), args: vec![Expr::String("foo".into()), 2.into()] }))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -801,8 +803,8 @@ mod test {
|
||||
assert_eq!(
|
||||
statements,
|
||||
vec![
|
||||
Statement::Expr(Expr::Call("foo".into(), vec![])),
|
||||
Statement::Expr(Expr::Call("bar".into(), vec![])),
|
||||
Statement::Expr(Expr::Call(Call { target: "foo".into(), args: vec![] })),
|
||||
Statement::Expr(Expr::Call(Call { target: "bar".into(), args: vec![] })),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user