wip refactor

This commit is contained in:
2025-11-22 13:44:50 -07:00
parent df127af3d8
commit 2ad1eef3e8
9 changed files with 427 additions and 98 deletions

23
Cargo.lock generated
View File

@@ -248,10 +248,17 @@ dependencies = [
"anyhow", "anyhow",
"indoc", "indoc",
"parser", "parser",
"pretty_assertions",
"quick-error", "quick-error",
"tokenizer", "tokenizer",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -478,6 +485,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.1.25" version = "0.1.25"
@@ -1046,6 +1063,12 @@ dependencies = [
"tap", "tap",
] ]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.27" version = "0.8.27"

View File

@@ -11,3 +11,4 @@ tokenizer = { path = "../tokenizer" }
[dev-dependencies] [dev-dependencies]
anyhow = { version = "1.0" } anyhow = { version = "1.0" }
indoc = { version = "2.0" } indoc = { version = "2.0" }
pretty_assertions = "1"

View File

@@ -11,7 +11,7 @@ use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
pub use v2::{Compiler, Error}; pub use v2::{Compiler, CompilerConfig, Error};
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]

View File

@@ -0,0 +1,85 @@
use crate::compile;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn no_arguments() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
fn doSomething() {};
let i = doSomething();
"
};
let to_test = indoc! {
"
j main
doSomething:
push ra
sub r0 sp 1
get ra db r0
sub sp sp 1
j ra
main:
jal doSomething
move r8 r15 #i
"
};
assert_eq!(compiled, to_test);
Ok(())
}
#[test]
fn let_var_args() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
fn doSomething(arg1) {};
let arg1 = 123;
let i = doSomething(arg1);
"
};
assert_eq!(
compiled,
indoc! {
"
j main
doSomething:
pop r8 #arg1
push ra
sub r0 sp 1
get ra db r0
sub sp sp 1
j ra
main:
move r8 123 #arg1
push r8
push r8
jal doSomething
sub r0 sp 1
get r8 db r0
sub sp sp 1
move r9 r15 #i
"
}
);
Ok(())
}
#[test]
fn incorrect_args_count() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
fn doSomething(arg1, arg2){};
let i = doSomething();
"
};
Ok(())
}

View File

@@ -0,0 +1,62 @@
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn variable_declaration_numeric_literal() -> anyhow::Result<()> {
let compiled = crate::compile! {
debug r#"
let i = 20c;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 293.15 #i
"
}
);
Ok(())
}
#[test]
fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()> {
let compiled = compile! {debug r#"
let a = 0;
let b = 1;
let c = 2;
let d = 3;
let e = 4;
let f = 5;
let g = 6;
let h = 7;
let i = 8;
let j = 9;
"#};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #a
move r9 1 #b
move r10 2 #c
move r11 3 #d
move r12 4 #e
move r13 5 #f
move r14 6 #g
push 7 #h
push 8 #i
push 9 #j
"
}
);
Ok(())
}

View File

@@ -0,0 +1,58 @@
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#"
// we need more than 4 params to 'spill' into a stack var
fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {};
"#);
assert_eq!(
compiled,
indoc! {"
j main
doSomething:
pop r8 #arg9
pop r9 #arg8
pop r10 #arg7
pop r11 #arg6
pop r12 #arg5
pop r13 #arg4
pop r14 #arg3
push ra
sub r0 sp 1
get ra db r0
sub sp sp 3
j ra
"}
);
Ok(())
}
#[test]
fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#"
// This is a test function declaration with no body
fn doSomething(arg1, arg2) {
};
"#);
assert_eq!(
compiled,
indoc! {"
j main
doSomething:
pop r8 #arg2
pop r9 #arg1
push ra
sub r0 sp 1
get ra db r0
sub sp sp 1
j ra
"}
);
Ok(())
}

View File

@@ -1,21 +1,15 @@
use super::v2::Compiler;
use crate::v2::CompilerConfig;
use indoc::indoc;
use parser::Parser;
use std::io::BufWriter;
use tokenizer::Tokenizer;
macro_rules! output { macro_rules! output {
($input:expr) => { ($input:expr) => {
String::from_utf8($input.into_inner()?)? String::from_utf8($input.into_inner()?)?
}; };
} }
#[macro_export]
macro_rules! compile { macro_rules! compile {
($source:expr) => {{ ($source:expr) => {{
let mut writer = BufWriter::new(Vec::new()); let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = Compiler::new( let compiler = crate::Compiler::new(
Parser::new(Tokenizer::from(String::from($source))), parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
&mut writer, &mut writer,
None, None,
); );
@@ -24,73 +18,16 @@ macro_rules! compile {
}}; }};
(debug $source:expr) => {{ (debug $source:expr) => {{
let mut writer = BufWriter::new(Vec::new()); let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = Compiler::new( let compiler = crate::Compiler::new(
Parser::new(Tokenizer::from(String::from($source))), parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
&mut writer, &mut writer,
Some(CompilerConfig { Some(crate::CompilerConfig { debug: true }),
debug: true,
..Default::default()
}),
); );
compiler.compile()?; compiler.compile()?;
output!(writer) output!(writer)
}}; }};
} }
mod declaration_function_invocation;
#[test] mod declaration_literal;
fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { mod function_declaration;
let compiled = compile!(debug r#"
// we need more than 4 params to 'spill' into a stack var
fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {};
"#);
assert_eq!(
compiled,
indoc! {"
j main
doSomething:
pop r8 #arg9
pop r9 #arg8
pop r10 #arg7
pop r11 #arg6
pop r12 #arg5
pop r13 #arg4
pop r14 #arg3
pop r15 #arg2
push ra
sub r0 sp 1
get ra db r0
sub sp sp 2
j ra
"}
);
Ok(())
}
#[test]
fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#"
// This is a test function declaration with no body
fn doSomething(arg1, arg2) {
};
"#);
assert_eq!(
compiled,
indoc! {"
j main
doSomething:
pop r8 #arg2
pop r9 #arg1
push ra
sub r0 sp 1
get ra db r0
sub sp sp 1
j ra
"}
);
Ok(())
}

View File

@@ -1,7 +1,10 @@
use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope};
use parser::{ use parser::{
Parser as ASTParser, Parser as ASTParser,
tree_node::{BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression}, tree_node::{
BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression,
InvocationExpression, Literal,
},
}; };
use quick_error::quick_error; use quick_error::quick_error;
use std::{ use std::{
@@ -9,6 +12,16 @@ use std::{
io::{BufWriter, Write}, io::{BufWriter, Write},
}; };
macro_rules! debug {
($self: expr, $debug_value: expr) => {
if $self.config.debug {
format!($debug_value)
} else {
"".into()
}
};
}
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@@ -22,10 +35,16 @@ quick_error! {
from() from()
} }
DuplicateFunction(func_name: String) { DuplicateFunction(func_name: String) {
display("{func_name} has already been defined") display("`{func_name}` has already been defined")
}
UnknownIdentifier(ident: String) {
display("`{ident}` is not found in the current scope.")
} }
InvalidDevice(device: String) { InvalidDevice(device: String) {
display("{device} is not valid") display("`{device}` is not valid")
}
AgrumentMismatch(func_name: String) {
display("Incorrect number of arguments passed into `{func_name}`")
} }
Unknown(reason: String) { Unknown(reason: String) {
display("{reason}") display("{reason}")
@@ -41,6 +60,7 @@ pub struct CompilerConfig {
pub struct Compiler<'a, W: std::io::Write> { pub struct Compiler<'a, W: std::io::Write> {
parser: ASTParser, parser: ASTParser,
function_locations: HashMap<String, usize>, function_locations: HashMap<String, usize>,
function_metadata: HashMap<String, Vec<String>>,
devices: HashMap<String, String>, devices: HashMap<String, String>,
output: &'a mut BufWriter<W>, output: &'a mut BufWriter<W>,
current_line: usize, current_line: usize,
@@ -57,6 +77,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Self { Self {
parser, parser,
function_locations: HashMap::new(), function_locations: HashMap::new(),
function_metadata: HashMap::new(),
devices: HashMap::new(), devices: HashMap::new(),
output: writer, output: writer,
current_line: 1, current_line: 1,
@@ -93,6 +114,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Expression::Declaration(var_name, expr) => { Expression::Declaration(var_name, expr) => {
self.expression_declaration(var_name, *expr, scope)? self.expression_declaration(var_name, *expr, scope)?
} }
Expression::Invocation(expr_invoke) => {
self.expression_function_invocation(expr_invoke, scope)?
}
_ => todo!(), _ => todo!(),
}; };
@@ -105,16 +129,135 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
expr: Expression, expr: Expression,
scope: &mut VariableScope<'v>, scope: &mut VariableScope<'v>,
) -> Result<(), Error> { ) -> Result<(), Error> {
scope.add_variable(var_name.clone(), LocationRequest::Persist)?;
match expr { match expr {
_ => unimplemented!(), Expression::Literal(Literal::Number(num)) => {
let var_location =
scope.add_variable(var_name.clone(), LocationRequest::Persist)?;
let num_str = num.to_string();
if let VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) =
var_location
{
self.write_output(format!(
"move r{reg} {num_str} {}",
debug!(self, "#{var_name}")
))?;
} else {
self.write_output(format!("push {num_str} {}", debug!(self, "#{var_name}")))?;
}
}
Expression::Invocation(invoke_expr) => {
self.expression_function_invocation(invoke_expr, scope)?;
// Return value _should_ be in VariableScope::RETURN_REGISTER
match scope.add_variable(var_name.clone(), LocationRequest::Persist)? {
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => self
.write_output(format!(
"move r{reg} r{} {}",
VariableScope::RETURN_REGISTER,
debug!(self, "#{var_name}")
))?,
VariableLocation::Stack(_) => self.write_output(format!(
"push r{} {}",
VariableScope::RETURN_REGISTER,
debug!(self, "#{var_name}")
))?,
}
}
_ => {
return Err(Error::Unknown(
"`{var_name}` declaration of this type is not supported.".into(),
));
}
} }
Ok(()) Ok(())
} }
fn expression_device<'v>(&mut self, expr: DeviceDeclarationExpression) { fn expression_function_invocation(
&mut self,
invoke_expr: InvocationExpression,
stack: &mut VariableScope,
) -> Result<(), Error> {
if !self.function_locations.contains_key(&invoke_expr.name) {
return Err(Error::UnknownIdentifier(invoke_expr.name));
}
let Some(args) = self.function_metadata.get(&invoke_expr.name) else {
return Err(Error::UnknownIdentifier(invoke_expr.name));
};
if args.len() != invoke_expr.arguments.len() {
return Err(Error::AgrumentMismatch(invoke_expr.name));
}
// backup all used registers to the stack
let active_registers = stack.registers().cloned().collect::<Vec<_>>();
for register in &active_registers {
stack.add_variable(format!("temp_{register}"), LocationRequest::Stack)?;
self.write_output(format!("push r{register}"))?;
}
for arg in invoke_expr.arguments {
match arg {
Expression::Literal(Literal::Number(num)) => {
let num_str = num.to_string();
self.write_output(format!("push {num_str}"))?;
}
Expression::Variable(var_name) => match stack.get_location_of(var_name)? {
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => {
self.write_output(format!("push r{reg}"))?;
}
VariableLocation::Stack(stack_offset) => {
self.write_output(format!(
"sub r{0} sp {stack_offset}",
VariableScope::TEMP_STACK_REGISTER
))?;
self.write_output(format!(
"get r{0} db r{0}",
VariableScope::TEMP_STACK_REGISTER
))?;
self.write_output(format!(
"push r{0}",
VariableScope::TEMP_STACK_REGISTER
))?;
}
},
_ => {
return Err(Error::Unknown(format!(
"Attempted to call `{}` with an unsupported argument",
invoke_expr.name
)));
}
}
}
// jump to the function and store current line in ra
self.write_output(format!("jal {}", invoke_expr.name))?;
for register in active_registers {
let VariableLocation::Stack(stack_offset) =
stack.get_location_of(format!("temp_{register}"))?
else {
return Err(Error::UnknownIdentifier(format!("temp_{register}")));
};
self.write_output(format!(
"sub r{0} sp {stack_offset}",
VariableScope::TEMP_STACK_REGISTER
))?;
self.write_output(format!(
"get r{register} db r{0}",
VariableScope::TEMP_STACK_REGISTER
))?;
}
if stack.stack_offset() > 0 {
self.write_output(format!("sub sp sp {}", stack.stack_offset()))?;
}
Ok(())
}
fn expression_device(&mut self, expr: DeviceDeclarationExpression) {
self.devices.insert(expr.name, expr.device); self.devices.insert(expr.name, expr.device);
} }
@@ -166,14 +309,17 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
return Err(Error::DuplicateFunction(name)); return Err(Error::DuplicateFunction(name));
} }
self.function_locations self.function_metadata
.insert(name.clone(), self.current_line); .insert(name.clone(), arguments.clone());
// Declare the function as a line identifier // Declare the function as a line identifier
self.write_output(format!("{}:", name))?; self.write_output(format!("{}:", name))?;
self.function_locations
.insert(name.clone(), self.current_line);
// Create a new block scope for the function body // Create a new block scope for the function body
let mut block_scope = VariableScope::scoped(&scope); let mut block_scope = VariableScope::scoped(scope);
let mut saved_variables = 0; let mut saved_variables = 0;
@@ -189,14 +335,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
match loc { match loc {
VariableLocation::Persistant(loc) => { VariableLocation::Persistant(loc) => {
self.write_output(format!( self.write_output(format!("pop r{loc} {}", debug!(self, "#{var_name}")))?;
"pop r{loc} {}",
if self.config.debug {
format!("#{}", var_name)
} else {
"".into()
}
))?;
} }
VariableLocation::Stack(_) => { VariableLocation::Stack(_) => {
return Err(Error::Unknown( return Err(Error::Unknown(
@@ -234,8 +373,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
)); ));
}; };
self.write_output(format!("sub r0 sp {ra_stack_offset}"))?; self.write_output(format!(
self.write_output("get ra db r0")?; "sub r{0} sp {ra_stack_offset}",
VariableScope::TEMP_STACK_REGISTER
))?;
self.write_output(format!(
"get ra db r{0}",
VariableScope::TEMP_STACK_REGISTER
))?;
if block_scope.stack_offset() > 0 { if block_scope.stack_offset() > 0 {
self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?;

View File

@@ -6,7 +6,7 @@ use quick_error::quick_error;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7]; const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
const PERSIST: [u8; 8] = [8, 9, 10, 11, 12, 13, 14, 15]; const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14];
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
@@ -72,8 +72,26 @@ impl<'a> Default for VariableScope<'a> {
} }
impl<'a> VariableScope<'a> { impl<'a> VariableScope<'a> {
pub const TEMP_REGISTER_COUNT: u8 = 8; pub const TEMP_REGISTER_COUNT: u8 = 7;
pub const PERSIST_REGISTER_COUNT: u8 = 8; pub const PERSIST_REGISTER_COUNT: u8 = 7;
pub const RETURN_REGISTER: u8 = 15;
pub const TEMP_STACK_REGISTER: u8 = 0;
pub fn registers(&self) -> impl Iterator<Item = &u8> {
self.var_lookup_table
.values()
.filter(|val| {
matches!(
val,
VariableLocation::Temporary(_) | VariableLocation::Persistant(_)
)
})
.map(|loc| match loc {
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => reg,
_ => unreachable!(),
})
}
pub fn scoped(parent: &'a VariableScope<'a>) -> Self { pub fn scoped(parent: &'a VariableScope<'a>) -> Self {
Self { Self {