Arrays (local) now work!
This commit is contained in:
@@ -15,3 +15,42 @@ impl Compilable for Assignment {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::compiler::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_subscripts() {
|
||||
assert_eq!(
|
||||
test_body(state_for("fn test(x) { x[3] = 5; }")),
|
||||
vec![
|
||||
"push 5", // rvalue
|
||||
"peekr", "loadw", // Base of x
|
||||
"push 3", // index
|
||||
"mul 3", // as byte offset
|
||||
"add", // addr of x[3]
|
||||
"storew"
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_subscripts() {
|
||||
assert_eq!(
|
||||
test_body(state_for("fn test() { var x = new(5); x[0] = 5; }")),
|
||||
vec![
|
||||
"push 5", // size of array
|
||||
"mul 3","popr","swap","popr","swap","pick 1","add","pushr","swap","pushr", // alloc call
|
||||
"peekr","storew", // store addr in x
|
||||
"push 5", // rvalue
|
||||
"peekr", "loadw", // Base of x
|
||||
"push 0", // index
|
||||
"mul 3", // as byte offset
|
||||
"add", // addr of x[0]
|
||||
"storew"
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,8 +86,19 @@ 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::New(size_expr) => {
|
||||
size_expr.0.process(state, Some(sig), loc)?;
|
||||
compile_alloc(&mut sig);
|
||||
Ok(())
|
||||
},
|
||||
Expr::Subscript(array, index) => {
|
||||
array.0.process(state, Some(sig), loc)?;
|
||||
index.0.process(state, Some(sig), loc)?;
|
||||
sig.emit_arg("mul", 3); // Indices are in words, convert to byte offset
|
||||
sig.emit("add"); // Add offset
|
||||
sig.emit("loadw");
|
||||
Ok(())
|
||||
},
|
||||
Expr::Infix(lhs, op, rhs) => {
|
||||
// Recurse on expressions, handling operators
|
||||
(*lhs.0).process(state, Some(&mut sig), loc)?;
|
||||
@@ -141,6 +152,28 @@ impl Compilable for Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile the code to run an alloc. This is just generated in-place for now.
|
||||
fn compile_alloc(sig: &mut CompiledFn) {
|
||||
// We have the size we want to alloc on top of the stack, in words. So first turn it to bytes:
|
||||
sig.emit_arg("mul", 3);
|
||||
|
||||
// We need to pull off the frame ptr and pool ptr, so the stack looks like ( frame pool size )
|
||||
sig.emit("popr");
|
||||
sig.emit("swap");
|
||||
sig.emit("popr");
|
||||
sig.emit("swap");
|
||||
|
||||
// We need to leave the pool pointer (as it was at the start) on top of the stack. So, copy it
|
||||
// and move it forward, so the stack is ( frame old-pool new-pool )
|
||||
sig.emit_arg("pick", 1);
|
||||
sig.emit("add");
|
||||
|
||||
// Put the new pool in place and the frame on top of it, so the stack is just ( old-pool ):
|
||||
sig.emit("pushr");
|
||||
sig.emit("swap");
|
||||
sig.emit("pushr");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::compiler::test_utils::*;
|
||||
@@ -220,4 +253,33 @@ mod test {
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc() {
|
||||
assert_eq!(
|
||||
test_body(state_for("fn test() { var x = new(10); return x[2]; }")),
|
||||
vec![
|
||||
"push 10", // eval the size
|
||||
"mul 3", // turn to bytes
|
||||
"popr", // take out the frame / old-pool
|
||||
"swap",
|
||||
"popr",
|
||||
"swap",
|
||||
"pick 1", // construct new-pool
|
||||
"add",
|
||||
"pushr", // put new-pool in place
|
||||
"swap", // put frame back in place
|
||||
"pushr",
|
||||
"peekr", // store new-pool in x
|
||||
"storew",
|
||||
"peekr", // Get x
|
||||
"loadw",
|
||||
"push 2", // Index...
|
||||
"mul 3", // ...to byte offset...
|
||||
"add", // ..added to base...
|
||||
"loadw", // ...And loaded
|
||||
"jmpr @_forge_gensym_2", // (and returned)
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use crate::compiler::utils::Variable;
|
||||
impl Compilable for Global {
|
||||
fn process(self, state: &mut State, _: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> {
|
||||
if self.size.is_some() {
|
||||
todo!("Arrays are not yet supported")
|
||||
todo!("Global arrays are not yet supported")
|
||||
}
|
||||
state.add_global(&self.name, |s| Variable::IndirectLabel(s.gensym()))
|
||||
}
|
||||
|
||||
@@ -57,8 +57,12 @@ impl Compilable for Lvalue {
|
||||
Expr::Deref(BoxExpr(expr)) => {
|
||||
(*expr).process(state, Some(sig), loc)
|
||||
}
|
||||
Expr::Subscript(_, _) => {
|
||||
Err(CompileError(0, 0, String::from("Arrays are not yet supported")))
|
||||
Expr::Subscript(array, index) => {
|
||||
array.0.process(state, Some(sig), loc)?;
|
||||
index.0.process(state, Some(sig), loc)?;
|
||||
sig.emit_arg("mul", 3); // Indices are in words, convert to byte offset
|
||||
sig.emit("add"); // Add offset
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,6 @@ use crate::compiler::utils::{Label, Scope, Variable};
|
||||
/// memory for any reason (like, new(), or calling a fn and giving it a valid frame ptr), this is
|
||||
/// what we pass.
|
||||
///
|
||||
/// todo: the allocator problem has (probably) been solved! Make an alloca() that increases the
|
||||
/// current frame pointer by some size. To dynamically allocate memory, just put it in the stack
|
||||
/// frame of the current fn. All fns return one word and all params are one word long (structs
|
||||
/// get passed around by reference)
|
||||
///
|
||||
/// todo: more of a global todo. Add a register that stores an offset that's added implicitly to
|
||||
/// all absolute addresses. This makes it a lot simpler to make relocatable code
|
||||
///
|
||||
@@ -72,6 +67,7 @@ impl CompiledFn {
|
||||
let frame = self.frame_size();
|
||||
if let Vacant(e) = self.local_scope.entry(name.into()) {
|
||||
e.insert(Variable::Local(frame));
|
||||
let frame = self.frame_size();
|
||||
self.max_frame_size = max(frame, self.max_frame_size);
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -139,8 +135,8 @@ impl CompiledFn {
|
||||
// (the frame ptr) plus a (known) max scope size:
|
||||
self.preamble_emit("dup");
|
||||
if self.max_frame_size > 0 {
|
||||
// Pool ptr is right after the locals, so, max_frame_size + 3
|
||||
self.preamble_emit_arg("add", self.max_frame_size + 3);
|
||||
// Pool ptr is right after the locals, so, add max_frame_size
|
||||
self.preamble_emit_arg("add", self.max_frame_size);
|
||||
}
|
||||
self.preamble_emit("pushr");
|
||||
|
||||
@@ -212,3 +208,51 @@ impl CompiledFn {
|
||||
self.reduce_frame_size_to(self.frame_size() - 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::compiler::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn max_frame_size_args() {
|
||||
assert_eq!(test_preamble(state_for("fn test(a) { return 5; }")),
|
||||
vec![
|
||||
"dup", // ( frame frame )
|
||||
"add 3", // add max size to frame to get ( frame pool )
|
||||
"pushr", // save pool ptr
|
||||
"pushr", // save frame ptr
|
||||
"peekr", // store param in 'a'
|
||||
"storew",
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_frame_size_locals() {
|
||||
assert_eq!(test_preamble(state_for("fn test() { var x; return 5; }")),
|
||||
vec![
|
||||
"dup", // ( frame frame )
|
||||
"add 3", // add max size to frame to get ( frame pool )
|
||||
"pushr", // save pool ptr
|
||||
"pushr", // save frame ptr
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_frame_size_block_scope() {
|
||||
assert_eq!(test_preamble(state_for("fn test(p) { var x; if(3) { var y; } var z; }")),
|
||||
vec![
|
||||
"dup", // ( frame frame )
|
||||
// This is the test: p and x are always in scope; y is created but leaves
|
||||
// scope before z enters so z can reuse y's slot
|
||||
"add 9", // add max size to frame to get ( frame pool )
|
||||
"pushr", // save pool ptr
|
||||
"pushr", // save frame ptr
|
||||
"peekr", // Capture param p
|
||||
"storew",
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ fn run_forge(src: &str) -> CPU {
|
||||
cpu
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn compiler_output(src: &str) -> Vec<String> {
|
||||
forge_core::compiler::build_boot(src).unwrap()
|
||||
}
|
||||
@@ -102,6 +103,29 @@ fn recursion_test() {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_test() {
|
||||
// Declare an array and do things
|
||||
assert_eq!(
|
||||
main_return(
|
||||
"fn sum(arr) {
|
||||
var i = 0;
|
||||
var s = 0;
|
||||
while (arr[i] != -1) {
|
||||
s = arr[i] + s;
|
||||
i = i + 1;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
fn main() {
|
||||
var a = new(5);
|
||||
a[0] = 1; a[1] = 3; a[2] = 5; a[3] = -1; a[4] = 10;
|
||||
return sum(a);
|
||||
}"),
|
||||
9
|
||||
)
|
||||
}
|
||||
|
||||
//#[test]
|
||||
// This is no longer cursed, or a test. The revised calling convention with the pool pointer makes
|
||||
// it now perfectly sane. Left here for posterity.
|
||||
|
||||
Reference in New Issue
Block a user