Merge pull request #3 from dbidwell94/compiler-v2-refactor
Compiler v2 refactor
This commit is contained in:
63
Cargo.lock
generated
63
Cargo.lock
generated
@@ -197,9 +197,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.52"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -207,9 +207,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.52"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -245,10 +245,20 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
name = "compiler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indoc",
|
||||
"parser",
|
||||
"pretty_assertions",
|
||||
"quick-error",
|
||||
"tokenizer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -324,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -336,12 +346,21 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -466,6 +485,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.25"
|
||||
@@ -1035,19 +1064,25 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
version = "0.8.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Devin Bidwell
|
||||
Copyright (c) 2024-2025 Devin Bidwell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Stationeers Language (slang)
|
||||
|
||||
This is an ambitious attempt at creating:
|
||||
|
||||
- A new programming language (slang)
|
||||
- A compiler to translate slang -> IC10
|
||||
- A mod to allow direct input of slang in the in-game script editor to
|
||||
automatically compile to IC10 before running
|
||||
|
||||
This project currently outputs 3 files:
|
||||
|
||||
- A Linux CLI
|
||||
- A Windows CLI
|
||||
- A Windows FFI dll
|
||||
- Contains a single function: `compile_from_string`
|
||||
|
||||
The aim of this project is to lower the amount of time it takes to code simple
|
||||
scripts in Stationeers so you can get back to engineering atmospherics or
|
||||
whatever you are working on. This project is NOT meant to fully replace IC10.
|
||||
Obviously hand-coded assembly written by an experienced programmer is more
|
||||
optimized and smaller than something that a C compiler will spit out. This is
|
||||
the same way. It WILL produce valid IC10, but for large complicated projects it
|
||||
might produce over the allowed limit of lines the in-game editor supports.
|
||||
|
||||
Current Unknowns
|
||||
|
||||
- Should I support a configurable script line length in-game to allow larger
|
||||
scripts to be saved?
|
||||
- Should compilation be "behind the scenes" (in game editor will ALWAYS be what
|
||||
you put in. IC10 will be IC10, slang will be slang)
|
||||
@@ -6,3 +6,9 @@ edition = "2024"
|
||||
[dependencies]
|
||||
quick-error = { workspace = true }
|
||||
parser = { path = "../parser" }
|
||||
tokenizer = { path = "../tokenizer" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "1.0" }
|
||||
indoc = { version = "2.0" }
|
||||
pretty_assertions = "1"
|
||||
|
||||
@@ -1,399 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod v1;
|
||||
mod variable_manager;
|
||||
|
||||
use parser::Parser as ASTParser;
|
||||
use parser::sys_call::SysCall;
|
||||
use parser::tree_node::*;
|
||||
use quick_error::quick_error;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufWriter, Write};
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum CompileError {
|
||||
ParseError(err: parser::ParseError) {
|
||||
from()
|
||||
display("Parse error: {}", err)
|
||||
}
|
||||
ScopeError {
|
||||
display("A fatal error has occurred with the compiler. Scope could not be found.")
|
||||
}
|
||||
WriteError(err: std::io::Error) {
|
||||
from()
|
||||
display("Write error: {}", err)
|
||||
}
|
||||
DuplicateVariable(variable: String) {
|
||||
display("A variable with the same name already exists in the current scope: {}", variable)
|
||||
}
|
||||
VariableNotFound(variable: String) {
|
||||
display("Variable {} was not found in the current scope.", variable)
|
||||
}
|
||||
MissingFunction(name: String) {
|
||||
display("Function {} was not found in the function table.", name)
|
||||
}
|
||||
MissingDevice(name: String) {
|
||||
display("Device {} was not found in the device table.", name)
|
||||
}
|
||||
InvalidSyscall(syscall: SysCall) {
|
||||
display("Syscall {} is not valid.", syscall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Compiler<'a, W: std::io::Write> {
|
||||
parser: ASTParser,
|
||||
/// Max stack size for the program is by default 512.
|
||||
variable_scope: Vec<HashMap<String, i32>>,
|
||||
function_locations: HashMap<String, usize>,
|
||||
devices: HashMap<String, String>,
|
||||
output: &'a mut BufWriter<W>,
|
||||
current_line: usize,
|
||||
declared_main: bool,
|
||||
}
|
||||
|
||||
impl<'a, W: std::io::Write> Compiler<'a, W> {
|
||||
pub fn new(parser: ASTParser, writer: &'a mut BufWriter<W>) -> Self {
|
||||
Self {
|
||||
parser,
|
||||
variable_scope: Vec::new(),
|
||||
function_locations: HashMap::new(),
|
||||
devices: HashMap::new(),
|
||||
output: writer,
|
||||
current_line: 0,
|
||||
declared_main: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_variable_index(&self, var_name: &str) -> Result<i32, CompileError> {
|
||||
let mut offset = 0;
|
||||
|
||||
for scope in &self.variable_scope {
|
||||
let scope_size = scope.len() as i32;
|
||||
if let Some(index) = scope.get(var_name) {
|
||||
let index = (scope_size - *index) + offset;
|
||||
|
||||
return Ok(index);
|
||||
}
|
||||
|
||||
offset += scope_size;
|
||||
}
|
||||
|
||||
Err(CompileError::VariableNotFound(var_name.to_owned()))
|
||||
}
|
||||
|
||||
fn push_stack(&mut self, var_name: &str) -> Result<(), CompileError> {
|
||||
// check to make sure the variable doesn't already exist in the current scope
|
||||
if self
|
||||
.variable_scope
|
||||
.last()
|
||||
.ok_or(CompileError::ScopeError)?
|
||||
.contains_key(var_name)
|
||||
{
|
||||
return Err(CompileError::DuplicateVariable(var_name.to_string()));
|
||||
}
|
||||
|
||||
let scope_size = self
|
||||
.variable_scope
|
||||
.last()
|
||||
.ok_or(CompileError::ScopeError)?
|
||||
.len();
|
||||
|
||||
self.variable_scope
|
||||
.last_mut()
|
||||
.ok_or(CompileError::ScopeError)?
|
||||
.insert(var_name.to_string(), scope_size as i32);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop the given variable from the current stack. Errors if the variable is not found in the
|
||||
/// current scope.
|
||||
fn pop_current(&mut self, var_name: &str) -> Result<i32, CompileError> {
|
||||
let last_scope = self
|
||||
.variable_scope
|
||||
.last_mut()
|
||||
.ok_or(CompileError::ScopeError)?;
|
||||
|
||||
last_scope
|
||||
.remove(var_name)
|
||||
.ok_or(CompileError::VariableNotFound(var_name.to_string()))
|
||||
}
|
||||
|
||||
fn write_output(&mut self, output: impl Into<String>) -> Result<(), CompileError> {
|
||||
self.output.write_all(output.into().as_bytes())?;
|
||||
self.output.write_all(b"\n")?;
|
||||
self.current_line += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile(mut self) -> Result<(), CompileError> {
|
||||
let ast = self.parser.parse_all()?;
|
||||
|
||||
let Some(ast) = ast else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Jump directly to the main block. This will avoid executing functions before the main block.
|
||||
self.write_output("j main")?;
|
||||
|
||||
self.expression(ast)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expression(&mut self, expression: Expression) -> Result<(), CompileError> {
|
||||
match expression {
|
||||
Expression::Function(expr) => self.function_expression(expr)?,
|
||||
Expression::Block(expr) => self.block_expression(expr)?,
|
||||
Expression::Invocation(expr) => self.invocation_expression(expr)?,
|
||||
Expression::Binary(expr) => self.binary_expression(expr)?,
|
||||
Expression::Declaration(var_name, expr) => {
|
||||
self.declaration_expression(&var_name, *expr)?
|
||||
}
|
||||
Expression::DeviceDeclaration(DeviceDeclarationExpression { name, device }) => {
|
||||
self.devices.insert(name, device);
|
||||
}
|
||||
_ => todo!("{:?}", expression),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn declaration_expression(
|
||||
&mut self,
|
||||
var_name: &str,
|
||||
expr: Expression,
|
||||
) -> Result<(), CompileError> {
|
||||
match expr {
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
self.push_stack(var_name)?;
|
||||
self.write_output(format!("push {num}"))?;
|
||||
}
|
||||
Expression::Binary(expr) => {
|
||||
self.binary_expression(expr)?;
|
||||
self.push_stack(var_name)?;
|
||||
}
|
||||
Expression::Syscall(expr) => {
|
||||
self.syscall_declaration_expression(expr)?;
|
||||
self.push_stack(var_name)?;
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn syscall_declaration_expression(&mut self, expr: SysCall) -> Result<(), CompileError> {
|
||||
use parser::sys_call::System;
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match expr {
|
||||
SysCall::System(ref sys) => match sys {
|
||||
System::LoadFromDevice(LiteralOrVariable::Variable(device), value) => {
|
||||
let device = self
|
||||
.devices
|
||||
.get(device)
|
||||
.ok_or(CompileError::MissingDevice(device.clone()))?;
|
||||
|
||||
self.write_output(format!("l r15 {device} {value}"))?;
|
||||
self.write_output("push r15")?;
|
||||
}
|
||||
_ => return Err(CompileError::InvalidSyscall(expr)),
|
||||
},
|
||||
_ => return Err(CompileError::InvalidSyscall(expr)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn binary_expression(&mut self, expr: BinaryExpression) -> Result<(), CompileError> {
|
||||
self.variable_scope.push(HashMap::new());
|
||||
|
||||
fn perform_operation<W: std::io::Write>(
|
||||
compiler: &mut Compiler<W>,
|
||||
op: &str,
|
||||
left: Expression,
|
||||
right: Expression,
|
||||
) -> Result<(), CompileError> {
|
||||
match left {
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
compiler.write_output(format!("push {num}"))?;
|
||||
compiler.push_stack(&format!("{op}ExpressionLeft"))?;
|
||||
}
|
||||
Expression::Variable(var_name) => {
|
||||
let var_offset = compiler.get_variable_index(&var_name)? + 1;
|
||||
compiler.write_output(format!("sub r15 sp {var_offset}"))?;
|
||||
compiler.write_output("get r15 db r15")?;
|
||||
compiler.write_output("push r15")?;
|
||||
compiler.push_stack(&format!("{op}ExpressionLeft"))?;
|
||||
}
|
||||
Expression::Binary(expr) => {
|
||||
compiler.binary_expression(expr)?;
|
||||
compiler.push_stack(&format!("{op}ExpressionLeft"))?;
|
||||
}
|
||||
Expression::Priority(expr) => match *expr {
|
||||
Expression::Binary(expr) => {
|
||||
compiler.binary_expression(expr)?;
|
||||
compiler.push_stack(&format!("{op}ExpressionLeft"))?;
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
match right {
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
compiler.write_output(format!("push {num}"))?;
|
||||
compiler.push_stack(&format!("{op}ExpressionRight"))?;
|
||||
}
|
||||
Expression::Variable(var_name) => {
|
||||
let var_offset = compiler.get_variable_index(&var_name)? + 1;
|
||||
compiler.write_output(format!("sub r15 sp {}", var_offset))?;
|
||||
compiler.write_output("get r15 db r15")?;
|
||||
compiler.write_output("push r15")?;
|
||||
compiler.push_stack(&format!("{op}ExpressionRight"))?;
|
||||
}
|
||||
Expression::Binary(expr) => {
|
||||
compiler.binary_expression(expr)?;
|
||||
compiler.push_stack(&format!("{op}ExpressionRight"))?;
|
||||
}
|
||||
Expression::Priority(expr) => match *expr {
|
||||
Expression::Binary(expr) => {
|
||||
compiler.binary_expression(expr)?;
|
||||
compiler.push_stack(&format!("{op}ExpressionRight"))?;
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
compiler.write_output("pop r1")?;
|
||||
compiler.write_output("pop r0")?;
|
||||
compiler.write_output(format!("{op} r0 r0 r1"))?;
|
||||
compiler.write_output("push r0")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match expr {
|
||||
BinaryExpression::Add(left, right) => {
|
||||
perform_operation(self, "add", *left, *right)?;
|
||||
}
|
||||
BinaryExpression::Subtract(left, right) => {
|
||||
perform_operation(self, "sub", *left, *right)?;
|
||||
}
|
||||
BinaryExpression::Multiply(left, right) => {
|
||||
perform_operation(self, "mul", *left, *right)?;
|
||||
}
|
||||
BinaryExpression::Divide(left, right) => {
|
||||
perform_operation(self, "div", *left, *right)?;
|
||||
}
|
||||
_ => todo!("Operation not currently supported"),
|
||||
}
|
||||
self.variable_scope.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invocation_expression(&mut self, expr: InvocationExpression) -> Result<(), CompileError> {
|
||||
let function_name = expr.name;
|
||||
let args_count = expr.arguments.len();
|
||||
|
||||
let function_line = *self
|
||||
.function_locations
|
||||
.get(&function_name)
|
||||
.ok_or(CompileError::MissingFunction(function_name.clone()))?;
|
||||
|
||||
let mut to_write = String::new();
|
||||
|
||||
self.push_stack(&format!("{function_name}ReturnAddress"))?;
|
||||
|
||||
for (iter_index, arg) in expr.arguments.into_iter().enumerate() {
|
||||
match arg {
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
to_write.push_str(&format!("push {}\n", num));
|
||||
}
|
||||
Expression::Variable(var_name) => {
|
||||
let index = self.get_variable_index(&var_name)?;
|
||||
|
||||
to_write.push_str(&format!("sub r15 sp {index}\n"));
|
||||
to_write.push_str("get r15 db r15\n");
|
||||
to_write.push_str("push r15\n");
|
||||
}
|
||||
Expression::Binary(expr) => {
|
||||
self.binary_expression(expr)?;
|
||||
to_write.push_str("push r0\n");
|
||||
}
|
||||
_ => todo!("something is up with the arguments: {arg:?}"),
|
||||
}
|
||||
self.push_stack(&format!("{function_name}Invocation{iter_index}"))?;
|
||||
}
|
||||
|
||||
// push the return address onto the stack. Current + to write + pushing the return address
|
||||
let return_addr = self.current_line + to_write.lines().count() + 2;
|
||||
self.write_output(format!("push {return_addr}"))?;
|
||||
self.output.write_all(to_write.as_bytes())?;
|
||||
self.current_line = return_addr - 1;
|
||||
|
||||
self.write_output(format!("j {function_line}"))?;
|
||||
|
||||
self.pop_current(&format!("{function_name}ReturnAddress"))?;
|
||||
for i in 0..args_count {
|
||||
self.pop_current(&format!("{function_name}Invocation{i}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn function_expression(&mut self, expression: FunctionExpression) -> Result<(), CompileError> {
|
||||
let func_name = expression.name;
|
||||
|
||||
self.variable_scope.push(HashMap::new());
|
||||
|
||||
self.function_locations.insert(func_name, self.current_line);
|
||||
|
||||
for arg in expression.arguments.iter().rev() {
|
||||
self.push_stack(arg)?;
|
||||
}
|
||||
|
||||
for expr in expression.body.0 {
|
||||
self.expression(expr)?;
|
||||
}
|
||||
|
||||
let scope = self.variable_scope.pop().ok_or(CompileError::ScopeError)?;
|
||||
|
||||
self.write_output(format!("sub sp sp {0}", scope.len()))?;
|
||||
self.write_output("pop ra")?;
|
||||
self.write_output("j ra")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn block_expression(&mut self, mut expression: BlockExpression) -> Result<(), CompileError> {
|
||||
self.variable_scope.push(HashMap::new());
|
||||
|
||||
// hoist functions to the top of the block
|
||||
expression.0.sort_by(|a, b| {
|
||||
if matches!(a, Expression::Function(_)) && matches!(b, Expression::Function(_)) {
|
||||
Ordering::Equal
|
||||
} else if matches!(a, Expression::Function(_)) {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
for expr in expression.0 {
|
||||
// if we haven't declared main yet and we have already declared all the function expressions, declare main
|
||||
if !self.declared_main && !matches!(expr, Expression::Function(_)) {
|
||||
self.write_output("main:")?;
|
||||
self.declared_main = true;
|
||||
}
|
||||
self.expression(expr)?;
|
||||
}
|
||||
|
||||
self.variable_scope.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub use v1::{Compiler, CompilerConfig, Error};
|
||||
|
||||
100
libs/compiler/src/test/binary_expression.rs
Normal file
100
libs/compiler/src/test/binary_expression.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn simple_binary_expression() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let i = 1 + 2;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
add r1 1 2
|
||||
move r8 r1 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_binary_expressions() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn calculateArgs(arg1, arg2, arg3) {
|
||||
return (arg1 + arg2) * arg3;
|
||||
};
|
||||
|
||||
let returned = calculateArgs(10, 20, 30) + 100;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
calculateArgs:
|
||||
pop r8 #arg3
|
||||
pop r9 #arg2
|
||||
pop r10 #arg1
|
||||
push ra
|
||||
add r1 r10 r9
|
||||
mul r2 r1 r8
|
||||
move r15 r2
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
push 10
|
||||
push 20
|
||||
push 30
|
||||
jal calculateArgs
|
||||
move r1 r15 #__binary_temp_3
|
||||
add r2 r1 100
|
||||
move r8 r2 #returned
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let negationHell = (-1 + -2) * (-3 + (-4 * (-5 + -6)));
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
add r1 -1 -2
|
||||
add r2 -5 -6
|
||||
mul r3 -4 r2
|
||||
add r4 -3 r3
|
||||
mul r5 r1 r4
|
||||
move r8 r5 #negationHell
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
243
libs/compiler/src/test/declaration_function_invocation.rs
Normal file
243
libs/compiler/src/test/declaration_function_invocation.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
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! {
|
||||
result
|
||||
"
|
||||
fn doSomething(arg1, arg2){};
|
||||
let i = doSomething();
|
||||
"
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
compiled,
|
||||
Err(super::super::Error::AgrumentMismatch(_))
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_literal_args() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething(arg1, arg2) {};
|
||||
let thisVariableShouldStayInPlace = 123;
|
||||
let returnedValue = doSomething(12, 34);
|
||||
"
|
||||
};
|
||||
|
||||
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
|
||||
main:
|
||||
move r8 123 #thisVariableShouldStayInPlace
|
||||
push r8
|
||||
push 12
|
||||
push 34
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
move r9 r15 #returnedValue
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_args() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let arg1 = 123;
|
||||
let returnValue = doSomething(arg1, 456);
|
||||
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
|
||||
main:
|
||||
move r8 123 #arg1
|
||||
push r8
|
||||
push r8
|
||||
push 456
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
move r9 r15 #returnValue
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_return_statement() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething(arg1) {
|
||||
return 456;
|
||||
};
|
||||
|
||||
let returned = doSomething(123);
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg1
|
||||
push ra
|
||||
move r15 456 #returnValue
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
push 123
|
||||
jal doSomething
|
||||
move r8 r15 #returned
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_negative_return_literal() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething() {
|
||||
return -1;
|
||||
};
|
||||
let i = doSomething();
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
push ra
|
||||
move r15 -1 #returnValue
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
jal doSomething
|
||||
move r8 r15 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
87
libs/compiler/src/test/declaration_literal.rs
Normal file
87
libs/compiler/src/test/declaration_literal.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_declaration_negative() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let i = -1;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 -1 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
58
libs/compiler/src/test/function_declaration.rs
Normal file
58
libs/compiler/src/test/function_declaration.rs
Normal 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(())
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#![allow(clippy::crate_in_macro_def)]
|
||||
|
||||
macro_rules! output {
|
||||
($input:expr) => {
|
||||
String::from_utf8($input.into_inner()?)?
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg_attr(test, macro_export)]
|
||||
macro_rules! compile {
|
||||
($source:expr) => {{
|
||||
let mut writer = std::io::BufWriter::new(Vec::new());
|
||||
let compiler = ::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
|
||||
&mut writer,
|
||||
None,
|
||||
);
|
||||
compiler.compile()?;
|
||||
output!(writer)
|
||||
}};
|
||||
|
||||
(result $source:expr) => {{
|
||||
let mut writer = std::io::BufWriter::new(Vec::new());
|
||||
let compiler = crate::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
|
||||
&mut writer,
|
||||
Some(crate::CompilerConfig { debug: true }),
|
||||
);
|
||||
compiler.compile()
|
||||
}};
|
||||
|
||||
(debug $source:expr) => {{
|
||||
let mut writer = std::io::BufWriter::new(Vec::new());
|
||||
let compiler = crate::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
|
||||
&mut writer,
|
||||
Some(crate::CompilerConfig { debug: true }),
|
||||
);
|
||||
compiler.compile()?;
|
||||
output!(writer)
|
||||
}};
|
||||
}
|
||||
mod binary_expression;
|
||||
mod declaration_function_invocation;
|
||||
mod declaration_literal;
|
||||
mod function_declaration;
|
||||
|
||||
750
libs/compiler/src/v1.rs
Normal file
750
libs/compiler/src/v1.rs
Normal file
@@ -0,0 +1,750 @@
|
||||
use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope};
|
||||
use parser::{
|
||||
Parser as ASTParser,
|
||||
tree_node::{
|
||||
BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression,
|
||||
FunctionExpression, InvocationExpression, Literal,
|
||||
},
|
||||
};
|
||||
use quick_error::quick_error;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{BufWriter, Write},
|
||||
};
|
||||
|
||||
macro_rules! debug {
|
||||
($self: expr, $debug_value: expr) => {
|
||||
if $self.config.debug {
|
||||
format!($debug_value)
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseError(error: parser::Error) {
|
||||
from()
|
||||
}
|
||||
IoError(error: std::io::Error) {
|
||||
from()
|
||||
}
|
||||
ScopeError(error: variable_manager::Error) {
|
||||
from()
|
||||
}
|
||||
DuplicateIdentifier(func_name: String) {
|
||||
display("`{func_name}` has already been defined")
|
||||
}
|
||||
UnknownIdentifier(ident: String) {
|
||||
display("`{ident}` is not found in the current scope.")
|
||||
}
|
||||
InvalidDevice(device: String) {
|
||||
display("`{device}` is not valid")
|
||||
}
|
||||
AgrumentMismatch(func_name: String) {
|
||||
display("Incorrect number of arguments passed into `{func_name}`")
|
||||
}
|
||||
Unknown(reason: String) {
|
||||
display("{reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct CompilerConfig {
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
struct CompilationResult {
|
||||
location: VariableLocation,
|
||||
/// If Some, this is the name of the temporary variable that holds the result.
|
||||
/// It must be freed by the caller when done.
|
||||
temp_name: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Compiler<'a, W: std::io::Write> {
|
||||
parser: ASTParser,
|
||||
function_locations: HashMap<String, usize>,
|
||||
function_metadata: HashMap<String, Vec<String>>,
|
||||
devices: HashMap<String, String>,
|
||||
output: &'a mut BufWriter<W>,
|
||||
current_line: usize,
|
||||
declared_main: bool,
|
||||
config: CompilerConfig,
|
||||
temp_counter: usize,
|
||||
}
|
||||
|
||||
impl<'a, W: std::io::Write> Compiler<'a, W> {
|
||||
pub fn new(
|
||||
parser: ASTParser,
|
||||
writer: &'a mut BufWriter<W>,
|
||||
config: Option<CompilerConfig>,
|
||||
) -> Self {
|
||||
Self {
|
||||
parser,
|
||||
function_locations: HashMap::new(),
|
||||
function_metadata: HashMap::new(),
|
||||
devices: HashMap::new(),
|
||||
output: writer,
|
||||
current_line: 1,
|
||||
declared_main: false,
|
||||
config: config.unwrap_or_default(),
|
||||
temp_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(mut self) -> Result<(), Error> {
|
||||
let expr = self.parser.parse_all()?;
|
||||
|
||||
let Some(expr) = expr else { return Ok(()) };
|
||||
|
||||
self.write_output("j main")?;
|
||||
// We ignore the result of the root expression (usually a block)
|
||||
let _ = self.expression(expr, &mut VariableScope::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_output(&mut self, output: impl Into<String>) -> Result<(), Error> {
|
||||
self.output.write_all(output.into().as_bytes())?;
|
||||
self.output.write_all(b"\n")?;
|
||||
self.current_line += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_temp_name(&mut self) -> String {
|
||||
self.temp_counter += 1;
|
||||
format!("__binary_temp_{}", self.temp_counter)
|
||||
}
|
||||
|
||||
fn expression<'v>(
|
||||
&mut self,
|
||||
expr: Expression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<Option<CompilationResult>, Error> {
|
||||
match expr {
|
||||
Expression::Function(expr_func) => {
|
||||
self.expression_function(expr_func, scope)?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::Block(expr_block) => {
|
||||
self.expression_block(expr_block, scope)?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::DeviceDeclaration(expr_dev) => {
|
||||
self.expression_device(expr_dev)?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::Declaration(var_name, expr) => {
|
||||
let loc = self.expression_declaration(var_name, *expr, scope)?;
|
||||
Ok(loc.map(|l| CompilationResult {
|
||||
location: l,
|
||||
temp_name: None,
|
||||
}))
|
||||
}
|
||||
Expression::Invocation(expr_invoke) => {
|
||||
self.expression_function_invocation(expr_invoke, scope)?;
|
||||
// Invocation returns result in r15 (RETURN_REGISTER).
|
||||
// If used as an expression, we must move it to a temp to avoid overwrite.
|
||||
let temp_name = self.next_temp_name();
|
||||
let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp)?;
|
||||
self.emit_variable_assignment(
|
||||
&temp_name,
|
||||
&temp_loc,
|
||||
format!("r{}", VariableScope::RETURN_REGISTER),
|
||||
)?;
|
||||
Ok(Some(CompilationResult {
|
||||
location: temp_loc,
|
||||
temp_name: Some(temp_name),
|
||||
}))
|
||||
}
|
||||
Expression::Binary(bin_expr) => {
|
||||
let result = self.expression_binary(bin_expr, scope)?;
|
||||
Ok(Some(result))
|
||||
}
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
let temp_name = self.next_temp_name();
|
||||
let loc = scope.add_variable(&temp_name, LocationRequest::Temp)?;
|
||||
self.emit_variable_assignment(&temp_name, &loc, num.to_string())?;
|
||||
Ok(Some(CompilationResult {
|
||||
location: loc,
|
||||
temp_name: Some(temp_name),
|
||||
}))
|
||||
}
|
||||
Expression::Variable(name) => {
|
||||
let loc = scope.get_location_of(&name)?;
|
||||
Ok(Some(CompilationResult {
|
||||
location: loc,
|
||||
temp_name: None, // User variable, do not free
|
||||
}))
|
||||
}
|
||||
Expression::Priority(inner_expr) => self.expression(*inner_expr, scope),
|
||||
Expression::Negation(inner_expr) => {
|
||||
// Compile negation as 0 - inner
|
||||
let (inner_str, cleanup) = self.compile_operand(*inner_expr, scope)?;
|
||||
let result_name = self.next_temp_name();
|
||||
let result_loc = scope.add_variable(&result_name, LocationRequest::Temp)?;
|
||||
let result_reg = self.resolve_register(&result_loc)?;
|
||||
|
||||
self.write_output(format!("sub {result_reg} 0 {inner_str}"))?;
|
||||
|
||||
if let Some(name) = cleanup {
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
|
||||
Ok(Some(CompilationResult {
|
||||
location: result_loc,
|
||||
temp_name: Some(result_name),
|
||||
}))
|
||||
}
|
||||
_ => Err(Error::Unknown(format!(
|
||||
"Expression type not yet supported in general expression context: {:?}",
|
||||
expr
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_variable_assignment(
|
||||
&mut self,
|
||||
var_name: &str,
|
||||
location: &VariableLocation,
|
||||
source_value: impl Into<String>,
|
||||
) -> Result<(), Error> {
|
||||
let debug_tag = if self.config.debug {
|
||||
format!(" #{var_name}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
match location {
|
||||
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
|
||||
self.write_output(format!("move r{reg} {}{debug_tag}", source_value.into()))?;
|
||||
}
|
||||
VariableLocation::Stack(_) => {
|
||||
self.write_output(format!("push {}{debug_tag}", source_value.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expression_declaration<'v>(
|
||||
&mut self,
|
||||
var_name: String,
|
||||
expr: Expression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<Option<VariableLocation>, Error> {
|
||||
// optimization. Check for a negated numeric literal
|
||||
if let Expression::Negation(box_expr) = &expr
|
||||
&& let Expression::Literal(Literal::Number(neg_num)) = &**box_expr
|
||||
{
|
||||
let loc = scope.add_variable(&var_name, LocationRequest::Persist)?;
|
||||
self.emit_variable_assignment(&var_name, &loc, format!("-{neg_num}"))?;
|
||||
return Ok(Some(loc));
|
||||
}
|
||||
|
||||
let loc = match expr {
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
let var_location =
|
||||
scope.add_variable(var_name.clone(), LocationRequest::Persist)?;
|
||||
|
||||
self.emit_variable_assignment(&var_name, &var_location, num)?;
|
||||
var_location
|
||||
}
|
||||
Expression::Invocation(invoke_expr) => {
|
||||
self.expression_function_invocation(invoke_expr, scope)?;
|
||||
|
||||
let loc = scope.add_variable(&var_name, LocationRequest::Persist)?;
|
||||
self.emit_variable_assignment(
|
||||
&var_name,
|
||||
&loc,
|
||||
format!("r{}", VariableScope::RETURN_REGISTER),
|
||||
)?;
|
||||
loc
|
||||
}
|
||||
// Support assigning binary expressions to variables directly
|
||||
Expression::Binary(bin_expr) => {
|
||||
let result = self.expression_binary(bin_expr, scope)?;
|
||||
let var_loc = scope.add_variable(&var_name, LocationRequest::Persist)?;
|
||||
|
||||
// Move result from temp to new persistent variable
|
||||
let result_reg = self.resolve_register(&result.location)?;
|
||||
self.emit_variable_assignment(&var_name, &var_loc, result_reg)?;
|
||||
|
||||
// Free the temp result
|
||||
if let Some(name) = result.temp_name {
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
var_loc
|
||||
}
|
||||
Expression::Variable(name) => {
|
||||
let src_loc = scope.get_location_of(&name)?;
|
||||
let var_loc = scope.add_variable(&var_name, LocationRequest::Persist)?;
|
||||
|
||||
// Handle loading from stack if necessary
|
||||
let src_str = match src_loc {
|
||||
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => {
|
||||
format!("r{r}")
|
||||
}
|
||||
VariableLocation::Stack(offset) => {
|
||||
self.write_output(format!(
|
||||
"sub r{0} sp {offset}",
|
||||
VariableScope::TEMP_STACK_REGISTER
|
||||
))?;
|
||||
self.write_output(format!(
|
||||
"get r{0} db r{0}",
|
||||
VariableScope::TEMP_STACK_REGISTER
|
||||
))?;
|
||||
format!("r{}", VariableScope::TEMP_STACK_REGISTER)
|
||||
}
|
||||
};
|
||||
self.emit_variable_assignment(&var_name, &var_loc, src_str)?;
|
||||
var_loc
|
||||
}
|
||||
Expression::Priority(inner) => {
|
||||
return self.expression_declaration(var_name, *inner, scope);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Unknown(format!(
|
||||
"`{var_name}` declaration of this type is not supported/implemented."
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(loc))
|
||||
}
|
||||
|
||||
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
|
||||
))?;
|
||||
}
|
||||
},
|
||||
Expression::Binary(bin_expr) => {
|
||||
// Compile the binary expression to a temp register
|
||||
let result = self.expression_binary(bin_expr, stack)?;
|
||||
let reg_str = self.resolve_register(&result.location)?;
|
||||
self.write_output(format!("push {reg_str}"))?;
|
||||
if let Some(name) = result.temp_name {
|
||||
stack.free_temp(name)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Unknown(format!(
|
||||
"Attempted to call `{}` with an unsupported argument type",
|
||||
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) -> Result<(), Error> {
|
||||
if self.devices.contains_key(&expr.name) {
|
||||
return Err(Error::DuplicateIdentifier(expr.name));
|
||||
}
|
||||
self.devices.insert(expr.name, expr.device);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper to resolve a location to a register string (e.g., "r0").
|
||||
/// Note: This does not handle Stack locations automatically, as they require
|
||||
/// instruction emission to load. Use `compile_operand` for general handling.
|
||||
fn resolve_register(&self, loc: &VariableLocation) -> Result<String, Error> {
|
||||
match loc {
|
||||
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(format!("r{r}")),
|
||||
VariableLocation::Stack(_) => Err(Error::Unknown(
|
||||
"Cannot resolve Stack location directly to register string without context".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles an expression and ensures the result is available as a string valid for an
|
||||
/// IC10 operand (either a register "rX" or a literal value "123").
|
||||
/// If the result was stored in a new temporary register, returns the name of that temp
|
||||
/// so the caller can free it.
|
||||
fn compile_operand(
|
||||
&mut self,
|
||||
expr: Expression,
|
||||
scope: &mut VariableScope,
|
||||
) -> Result<(String, Option<String>), Error> {
|
||||
// Optimization for literals
|
||||
if let Expression::Literal(Literal::Number(n)) = expr {
|
||||
return Ok((n.to_string(), None));
|
||||
}
|
||||
|
||||
// Optimization for negated literals used as operands.
|
||||
// E.g., `1 + -2` -> return "-2" string, no register used.
|
||||
if let Expression::Negation(inner) = &expr
|
||||
&& let Expression::Literal(Literal::Number(n)) = &**inner
|
||||
{
|
||||
return Ok((format!("-{}", n), None));
|
||||
}
|
||||
|
||||
let result = self
|
||||
.expression(expr, scope)?
|
||||
.ok_or(Error::Unknown("Expression did not return a value".into()))?;
|
||||
|
||||
match result.location {
|
||||
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => {
|
||||
Ok((format!("r{r}"), result.temp_name))
|
||||
}
|
||||
VariableLocation::Stack(offset) => {
|
||||
// If it's on the stack, we must load it into a temp to use it as an operand
|
||||
let temp_name = self.next_temp_name();
|
||||
let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp)?;
|
||||
let temp_reg = self.resolve_register(&temp_loc)?;
|
||||
|
||||
self.write_output(format!(
|
||||
"sub r{0} sp {offset}",
|
||||
VariableScope::TEMP_STACK_REGISTER
|
||||
))?;
|
||||
self.write_output(format!(
|
||||
"get {temp_reg} db r{0}",
|
||||
VariableScope::TEMP_STACK_REGISTER
|
||||
))?;
|
||||
|
||||
// If the original result had a temp name (unlikely for Stack, but possible logic),
|
||||
// we technically should free it if it's not needed, but Stack usually implies it's safe there.
|
||||
// We return the NEW temp name to be freed.
|
||||
Ok((temp_reg, Some(temp_name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expression_binary<'v>(
|
||||
&mut self,
|
||||
expr: BinaryExpression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<CompilationResult, Error> {
|
||||
let (op_str, left_expr, right_expr) = match expr {
|
||||
BinaryExpression::Add(l, r) => ("add", l, r),
|
||||
BinaryExpression::Multiply(l, r) => ("mul", l, r),
|
||||
BinaryExpression::Divide(l, r) => ("div", l, r),
|
||||
BinaryExpression::Subtract(l, r) => ("sub", l, r),
|
||||
BinaryExpression::Exponent(l, r) => ("pow", l, r),
|
||||
BinaryExpression::Modulo(l, r) => ("mod", l, r),
|
||||
};
|
||||
|
||||
// Compile LHS
|
||||
let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?;
|
||||
// Compile RHS
|
||||
let (rhs_str, rhs_cleanup) = self.compile_operand(*right_expr, scope)?;
|
||||
|
||||
// Allocate result register
|
||||
let result_name = self.next_temp_name();
|
||||
let result_loc = scope.add_variable(&result_name, LocationRequest::Temp)?;
|
||||
let result_reg = self.resolve_register(&result_loc)?;
|
||||
|
||||
// Emit instruction: op result lhs rhs
|
||||
self.write_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?;
|
||||
|
||||
// Clean up operand temps
|
||||
if let Some(name) = lhs_cleanup {
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
if let Some(name) = rhs_cleanup {
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
|
||||
Ok(CompilationResult {
|
||||
location: result_loc,
|
||||
temp_name: Some(result_name),
|
||||
})
|
||||
}
|
||||
|
||||
fn expression_block<'v>(
|
||||
&mut self,
|
||||
mut expr: BlockExpression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<(), Error> {
|
||||
// First, sort the expressions to ensure functions are hoisted
|
||||
expr.0.sort_by(|a, b| {
|
||||
if matches!(b, Expression::Function(_)) && matches!(a, Expression::Function(_)) {
|
||||
std::cmp::Ordering::Equal
|
||||
} else if matches!(a, Expression::Function(_)) {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
for expr in expr.0 {
|
||||
if !self.declared_main
|
||||
&& !matches!(expr, Expression::Function(_))
|
||||
&& !scope.has_parent()
|
||||
{
|
||||
self.write_output("main:")?;
|
||||
self.declared_main = true;
|
||||
}
|
||||
|
||||
match expr {
|
||||
Expression::Return(ret_expr) => {
|
||||
self.expression_return(*ret_expr, scope)?;
|
||||
}
|
||||
_ => {
|
||||
let result = self.expression(expr, scope)?;
|
||||
// If the expression was a statement that returned a temp result (e.g. `1 + 2;` line),
|
||||
// we must free it to avoid leaking registers.
|
||||
if let Some(comp_res) = result
|
||||
&& let Some(name) = comp_res.temp_name
|
||||
{
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER
|
||||
fn expression_return<'v>(
|
||||
&mut self,
|
||||
expr: Expression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<VariableLocation, Error> {
|
||||
if let Expression::Negation(neg_expr) = &expr
|
||||
&& let Expression::Literal(Literal::Number(neg_num)) = &**neg_expr
|
||||
{
|
||||
let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER);
|
||||
self.emit_variable_assignment("returnValue", &loc, format!("-{neg_num}"))?;
|
||||
return Ok(loc);
|
||||
};
|
||||
|
||||
match expr {
|
||||
Expression::Variable(var_name) => match scope.get_location_of(var_name)? {
|
||||
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
|
||||
self.write_output(format!(
|
||||
"move r{} r{reg} {}",
|
||||
VariableScope::RETURN_REGISTER,
|
||||
debug!(self, "#returnValue")
|
||||
))?;
|
||||
}
|
||||
VariableLocation::Stack(offset) => {
|
||||
self.write_output(format!(
|
||||
"sub r{} sp {offset}",
|
||||
VariableScope::TEMP_STACK_REGISTER
|
||||
))?;
|
||||
self.write_output(format!(
|
||||
"get r{} db r{}",
|
||||
VariableScope::RETURN_REGISTER,
|
||||
VariableScope::TEMP_STACK_REGISTER
|
||||
))?;
|
||||
}
|
||||
},
|
||||
Expression::Literal(Literal::Number(num)) => {
|
||||
self.emit_variable_assignment(
|
||||
"returnValue",
|
||||
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
|
||||
num,
|
||||
)?;
|
||||
}
|
||||
Expression::Binary(bin_expr) => {
|
||||
let result = self.expression_binary(bin_expr, scope)?;
|
||||
let result_reg = self.resolve_register(&result.location)?;
|
||||
self.write_output(format!(
|
||||
"move r{} {}",
|
||||
VariableScope::RETURN_REGISTER,
|
||||
result_reg
|
||||
))?;
|
||||
if let Some(name) = result.temp_name {
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Unknown(format!(
|
||||
"Unsupported `return` statement: {:?}",
|
||||
expr
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER))
|
||||
}
|
||||
|
||||
/// Compile a function declaration.
|
||||
/// Calees are responsible for backing up any registers they wish to use.
|
||||
fn expression_function<'v>(
|
||||
&mut self,
|
||||
expr: FunctionExpression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<(), Error> {
|
||||
let FunctionExpression {
|
||||
name,
|
||||
arguments,
|
||||
body,
|
||||
} = expr;
|
||||
|
||||
if self.function_locations.contains_key(&name) {
|
||||
return Err(Error::DuplicateIdentifier(name));
|
||||
}
|
||||
|
||||
self.function_metadata
|
||||
.insert(name.clone(), arguments.clone());
|
||||
|
||||
// Declare the function as a line identifier
|
||||
self.write_output(format!("{}:", name))?;
|
||||
|
||||
self.function_locations
|
||||
.insert(name.clone(), self.current_line);
|
||||
|
||||
// Create a new block scope for the function body
|
||||
let mut block_scope = VariableScope::scoped(scope);
|
||||
|
||||
let mut saved_variables = 0;
|
||||
|
||||
// do a reverse pass to pop variables from the stack and put them into registers
|
||||
for var_name in arguments
|
||||
.iter()
|
||||
.rev()
|
||||
.take(VariableScope::PERSIST_REGISTER_COUNT as usize)
|
||||
{
|
||||
let loc = block_scope.add_variable(var_name, LocationRequest::Persist)?;
|
||||
// we don't need to imcrement the stack offset as it's already on the stack from the
|
||||
// previous scope
|
||||
|
||||
match loc {
|
||||
VariableLocation::Persistant(loc) => {
|
||||
self.write_output(format!("pop r{loc} {}", debug!(self, "#{var_name}")))?;
|
||||
}
|
||||
VariableLocation::Stack(_) => {
|
||||
return Err(Error::Unknown(
|
||||
"Attempted to save to stack without tracking in scope".into(),
|
||||
));
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(Error::Unknown(
|
||||
"Attempted to return a Temporary scoped variable from a Persistant request"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
saved_variables += 1;
|
||||
}
|
||||
|
||||
// now do a forward pass in case we have spilled into the stack. We don't need to push
|
||||
// anything as they already exist on the stack, but we DO need to let our block_scope be
|
||||
// aware that the variables exist on the stack (left to right)
|
||||
for var_name in arguments.iter().take(arguments.len() - saved_variables) {
|
||||
block_scope.add_variable(var_name, LocationRequest::Stack)?;
|
||||
}
|
||||
|
||||
self.write_output("push ra")?;
|
||||
block_scope.add_variable(format!("{name}_ra"), LocationRequest::Stack)?;
|
||||
|
||||
for expr in body.0 {
|
||||
match expr {
|
||||
Expression::Return(ret_expr) => {
|
||||
self.expression_return(*ret_expr, &mut block_scope)?;
|
||||
}
|
||||
_ => {
|
||||
let result = self.expression(expr, &mut block_scope)?;
|
||||
// Free unused statement results
|
||||
if let Some(comp_res) = result
|
||||
&& let Some(name) = comp_res.temp_name
|
||||
{
|
||||
block_scope.free_temp(name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the saved return address and save it back into `ra`
|
||||
let VariableLocation::Stack(ra_stack_offset) =
|
||||
block_scope.get_location_of(format!("{name}_ra"))?
|
||||
else {
|
||||
return Err(Error::Unknown(
|
||||
"Stored return address not in stack as expected".into(),
|
||||
));
|
||||
};
|
||||
|
||||
self.write_output(format!(
|
||||
"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 {
|
||||
self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?;
|
||||
}
|
||||
|
||||
self.write_output("j ra")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
188
libs/compiler/src/variable_manager.rs
Normal file
188
libs/compiler/src/variable_manager.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
// r15 : Return Value
|
||||
// r0 : Unmanaged temp variable
|
||||
// r1 - r7 : Temporary Variables
|
||||
// r8 - r14 : Persistant Variables
|
||||
|
||||
use quick_error::quick_error;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
|
||||
const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14];
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
DuplicateVariable(var: String) {
|
||||
display("{var} already exists.")
|
||||
}
|
||||
UnknownVariable(var: String) {
|
||||
display("{var} does not exist.")
|
||||
}
|
||||
Unknown(reason: String) {
|
||||
display("{reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request to store a variable at a specific register type
|
||||
pub enum LocationRequest {
|
||||
#[allow(dead_code)]
|
||||
/// Request to store a variable in a temprary register.
|
||||
Temp,
|
||||
/// Request to store a variable in a persistant register.
|
||||
Persist,
|
||||
/// Request to store a variable in the stack.
|
||||
Stack,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum VariableLocation {
|
||||
/// Represents a temporary register (r1 - r7)
|
||||
Temporary(u8),
|
||||
/// Represents a persistant register (r8 - r14)
|
||||
Persistant(u8),
|
||||
/// Represents a a stack offset (current stack - offset = variable loc)
|
||||
Stack(u16),
|
||||
}
|
||||
|
||||
pub struct VariableScope<'a> {
|
||||
temporary_vars: VecDeque<u8>,
|
||||
persistant_vars: VecDeque<u8>,
|
||||
var_lookup_table: HashMap<String, VariableLocation>,
|
||||
stack_offset: u16,
|
||||
parent: Option<&'a VariableScope<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Default for VariableScope<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
parent: None,
|
||||
stack_offset: 0,
|
||||
persistant_vars: PERSIST.to_vec().into(),
|
||||
temporary_vars: TEMP.to_vec().into(),
|
||||
var_lookup_table: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VariableScope<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub const TEMP_REGISTER_COUNT: u8 = 7;
|
||||
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 {
|
||||
Self {
|
||||
parent: Option::Some(parent),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack_offset(&self) -> u16 {
|
||||
self.stack_offset
|
||||
}
|
||||
|
||||
/// Adds and tracks a new scoped variable. If the location you request is full, will fall back
|
||||
/// to the stack.
|
||||
pub fn add_variable(
|
||||
&mut self,
|
||||
var_name: impl Into<String>,
|
||||
location: LocationRequest,
|
||||
) -> Result<VariableLocation, Error> {
|
||||
let var_name = var_name.into();
|
||||
if self.var_lookup_table.contains_key(var_name.as_str()) {
|
||||
return Err(Error::DuplicateVariable(var_name));
|
||||
}
|
||||
let var_location = match location {
|
||||
LocationRequest::Temp => {
|
||||
if let Some(next_var) = self.temporary_vars.pop_front() {
|
||||
VariableLocation::Temporary(next_var)
|
||||
} else {
|
||||
let loc = VariableLocation::Stack(self.stack_offset);
|
||||
self.stack_offset += 1;
|
||||
loc
|
||||
}
|
||||
}
|
||||
LocationRequest::Persist => {
|
||||
if let Some(next_var) = self.persistant_vars.pop_front() {
|
||||
VariableLocation::Persistant(next_var)
|
||||
} else {
|
||||
let loc = VariableLocation::Stack(self.stack_offset);
|
||||
self.stack_offset += 1;
|
||||
loc
|
||||
}
|
||||
}
|
||||
LocationRequest::Stack => {
|
||||
let loc = VariableLocation::Stack(self.stack_offset);
|
||||
self.stack_offset += 1;
|
||||
loc
|
||||
}
|
||||
};
|
||||
self.var_lookup_table.insert(var_name, var_location.clone());
|
||||
|
||||
Ok(var_location)
|
||||
}
|
||||
|
||||
pub fn get_location_of(
|
||||
&mut self,
|
||||
var_name: impl Into<String>,
|
||||
) -> Result<VariableLocation, Error> {
|
||||
let var_name = var_name.into();
|
||||
let var = self
|
||||
.var_lookup_table
|
||||
.get(var_name.as_str())
|
||||
.cloned()
|
||||
.ok_or(Error::UnknownVariable(var_name))?;
|
||||
|
||||
if let VariableLocation::Stack(inserted_at_offset) = var {
|
||||
Ok(VariableLocation::Stack(
|
||||
self.stack_offset - inserted_at_offset,
|
||||
))
|
||||
} else {
|
||||
Ok(var)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_parent(&self) -> bool {
|
||||
self.parent.is_some()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn free_temp(&mut self, var_name: impl Into<String>) -> Result<(), Error> {
|
||||
let var_name = var_name.into();
|
||||
let Some(location) = self.var_lookup_table.remove(var_name.as_str()) else {
|
||||
return Err(Error::UnknownVariable(var_name));
|
||||
};
|
||||
|
||||
match location {
|
||||
VariableLocation::Temporary(t) => {
|
||||
self.temporary_vars.push_back(t);
|
||||
}
|
||||
VariableLocation::Persistant(_) => {
|
||||
return Err(Error::UnknownVariable(String::from(
|
||||
"Attempted to free a `let` variable.",
|
||||
)));
|
||||
}
|
||||
VariableLocation::Stack(_) => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
fn addTemperatures(temp1, temp2) {
|
||||
let toReturn = temp1 + temp2;
|
||||
return temp1 + temp2;
|
||||
};
|
||||
|
||||
|
||||
addTemperatures(15c, 120c);
|
||||
addTemperatures(1500f, 20c);
|
||||
let newTemp1 = addTemperatures(15c, 120c);
|
||||
let newTemp2 = addTemperatures(50c, 20c);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
pub mod sys_call;
|
||||
pub mod tree_node;
|
||||
|
||||
@@ -5,7 +8,7 @@ use quick_error::quick_error;
|
||||
use std::io::SeekFrom;
|
||||
use sys_call::SysCall;
|
||||
use tokenizer::{
|
||||
Tokenizer, TokenizerBuffer, TokenizerError,
|
||||
self, Tokenizer, TokenizerBuffer,
|
||||
token::{Keyword, Symbol, Token, TokenType},
|
||||
};
|
||||
use tree_node::*;
|
||||
@@ -20,8 +23,8 @@ macro_rules! boxed {
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
TokenizerError(err: TokenizerError) {
|
||||
pub enum Error {
|
||||
TokenizerError(err: tokenizer::Error) {
|
||||
from()
|
||||
display("Tokenizer Error: {}", err)
|
||||
source(err)
|
||||
@@ -57,7 +60,7 @@ macro_rules! token_from_option {
|
||||
($token:expr) => {
|
||||
match $token {
|
||||
Some(ref token) => token.clone(),
|
||||
None => return Err(ParseError::UnexpectedEOF),
|
||||
None => return Err(Error::UnexpectedEOF),
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -66,14 +69,14 @@ macro_rules! extract_token_data {
|
||||
($token:ident, $pattern:pat, $extraction:expr) => {
|
||||
match $token.token_type {
|
||||
$pattern => $extraction,
|
||||
_ => return Err(ParseError::UnexpectedToken($token.clone())),
|
||||
_ => return Err(Error::UnexpectedToken($token.clone())),
|
||||
}
|
||||
};
|
||||
($token:expr, $pattern:pat, $extraction:expr) => {
|
||||
match $token.token_type {
|
||||
$pattern => $extraction,
|
||||
_ => {
|
||||
return Err(ParseError::UnexpectedToken($token.clone()));
|
||||
return Err(Error::UnexpectedToken($token.clone()));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -118,7 +121,7 @@ impl Parser {
|
||||
|
||||
/// Parses all the input from the tokenizer buffer and returns the resulting expression
|
||||
/// Expressions are returned in a root block expression node
|
||||
pub fn parse_all(&mut self) -> Result<Option<tree_node::Expression>, ParseError> {
|
||||
pub fn parse_all(&mut self) -> Result<Option<tree_node::Expression>, Error> {
|
||||
let mut expressions = Vec::<Expression>::new();
|
||||
|
||||
while let Some(expression) = self.parse()? {
|
||||
@@ -129,7 +132,7 @@ impl Parser {
|
||||
}
|
||||
|
||||
/// Parses the input from the tokenizer buffer and returns the resulting expression
|
||||
pub fn parse(&mut self) -> Result<Option<tree_node::Expression>, ParseError> {
|
||||
pub fn parse(&mut self) -> Result<Option<tree_node::Expression>, Error> {
|
||||
self.assign_next()?;
|
||||
let expr = self.expression()?;
|
||||
|
||||
@@ -141,18 +144,44 @@ impl Parser {
|
||||
}
|
||||
|
||||
/// Assigns the next token in the tokenizer buffer to the current token
|
||||
fn assign_next(&mut self) -> Result<(), ParseError> {
|
||||
fn assign_next(&mut self) -> Result<(), Error> {
|
||||
self.current_token = self.tokenizer.next_token()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calls `assign_next` and returns the next token in the tokenizer buffer
|
||||
fn get_next(&mut self) -> Result<Option<&Token>, ParseError> {
|
||||
fn get_next(&mut self) -> Result<Option<&Token>, Error> {
|
||||
self.assign_next()?;
|
||||
Ok(self.current_token.as_ref())
|
||||
}
|
||||
|
||||
fn expression(&mut self) -> Result<Option<tree_node::Expression>, ParseError> {
|
||||
/// Parses an expression, handling binary operations with correct precedence.
|
||||
fn expression(&mut self) -> Result<Option<tree_node::Expression>, Error> {
|
||||
// Parse the Left Hand Side (unary/primary expression)
|
||||
let lhs = self.unary()?;
|
||||
|
||||
let Some(lhs) = lhs else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// check if the next or current token is an operator
|
||||
if self_matches_peek!(self, TokenType::Symbol(s) if s.is_operator()) {
|
||||
return Ok(Some(Expression::Binary(self.binary(lhs)?)));
|
||||
}
|
||||
// This is an edge case. We need to move back one token if the current token is an operator
|
||||
// so the binary expression can pick up the operator
|
||||
else if self_matches_current!(self, TokenType::Symbol(s) if s.is_operator()) {
|
||||
self.tokenizer.seek(SeekFrom::Current(-1))?;
|
||||
return Ok(Some(Expression::Binary(self.binary(lhs)?)));
|
||||
}
|
||||
|
||||
Ok(Some(lhs))
|
||||
}
|
||||
|
||||
/// Parses a unary or primary expression.
|
||||
/// This handles prefix operators (like negation) and atomic expressions (literals, variables, etc.),
|
||||
/// but stops before consuming binary operators.
|
||||
fn unary(&mut self) -> Result<Option<tree_node::Expression>, Error> {
|
||||
macro_rules! matches_keyword {
|
||||
($keyword:expr, $($pattern:pat),+) => {
|
||||
matches!($keyword, $($pattern)|+)
|
||||
@@ -167,12 +196,12 @@ impl Parser {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let expr = Some(match current_token.token_type {
|
||||
let expr = match current_token.token_type {
|
||||
// match unsupported keywords
|
||||
TokenType::Keyword(e)
|
||||
if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) =>
|
||||
{
|
||||
return Err(ParseError::UnsupportedKeyword(current_token.clone()));
|
||||
return Err(Error::UnsupportedKeyword(current_token.clone()));
|
||||
}
|
||||
|
||||
// match declarations with a `let` keyword
|
||||
@@ -214,30 +243,26 @@ impl Parser {
|
||||
// match priority expressions with a left parenthesis
|
||||
TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?),
|
||||
|
||||
// match minus symbols to handle negative numbers or negated expressions
|
||||
TokenType::Symbol(Symbol::Minus) => {
|
||||
self.assign_next()?; // consume the `-` symbol
|
||||
// IMPORTANT: We call `unary()` here, NOT `expression()`.
|
||||
// This ensures negation binds tightly to the operand and doesn't consume binary ops.
|
||||
// e.g. `-1 + 2` parses as `(-1) + 2`
|
||||
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
Expression::Negation(boxed!(inner_expr))
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
});
|
||||
|
||||
let Some(expr) = expr else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// check if the next or current token is an operator
|
||||
if self_matches_peek!(self, TokenType::Symbol(s) if s.is_operator()) {
|
||||
return Ok(Some(Expression::Binary(self.binary(expr)?)));
|
||||
}
|
||||
// This is an edge case. We need to move back one token if the current token is an operator
|
||||
// so the binary expression can pick up the operator
|
||||
else if self_matches_current!(self, TokenType::Symbol(s) if s.is_operator()) {
|
||||
self.tokenizer.seek(SeekFrom::Current(-1))?;
|
||||
return Ok(Some(Expression::Binary(self.binary(expr)?)));
|
||||
}
|
||||
|
||||
Ok(Some(expr))
|
||||
}
|
||||
|
||||
fn get_binary_child_node(&mut self) -> Result<tree_node::Expression, ParseError> {
|
||||
fn get_binary_child_node(&mut self) -> Result<tree_node::Expression, Error> {
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
|
||||
match current_token.token_type {
|
||||
@@ -257,16 +282,23 @@ impl Parser {
|
||||
{
|
||||
self.invocation().map(Expression::Invocation)
|
||||
}
|
||||
_ => Err(ParseError::UnexpectedToken(current_token.clone())),
|
||||
// Handle Negation
|
||||
TokenType::Symbol(Symbol::Minus) => {
|
||||
self.assign_next()?;
|
||||
// recurse to handle double negation or simple negation of atoms
|
||||
let inner = self.get_binary_child_node()?;
|
||||
Ok(Expression::Negation(boxed!(inner)))
|
||||
}
|
||||
_ => Err(Error::UnexpectedToken(current_token.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn device(&mut self) -> Result<DeviceDeclarationExpression, ParseError> {
|
||||
fn device(&mut self) -> Result<DeviceDeclarationExpression, Error> {
|
||||
// sanity check, make sure current token is a `device` keyword
|
||||
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
if !self_matches_current!(self, TokenType::Keyword(Keyword::Device)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
let identifier = extract_token_data!(
|
||||
@@ -277,7 +309,7 @@ impl Parser {
|
||||
|
||||
let current_token = token_from_option!(self.get_next()?).clone();
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token));
|
||||
return Err(Error::UnexpectedToken(current_token));
|
||||
}
|
||||
|
||||
let device = extract_token_data!(
|
||||
@@ -292,7 +324,7 @@ impl Parser {
|
||||
})
|
||||
}
|
||||
|
||||
fn assignment(&mut self) -> Result<AssignmentExpression, ParseError> {
|
||||
fn assignment(&mut self) -> Result<AssignmentExpression, Error> {
|
||||
let identifier = extract_token_data!(
|
||||
token_from_option!(self.current_token),
|
||||
TokenType::Identifier(ref id),
|
||||
@@ -301,11 +333,11 @@ impl Parser {
|
||||
|
||||
let current_token = token_from_option!(self.get_next()?).clone();
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token));
|
||||
return Err(Error::UnexpectedToken(current_token));
|
||||
}
|
||||
self.assign_next()?;
|
||||
|
||||
let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?;
|
||||
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
Ok(AssignmentExpression {
|
||||
identifier,
|
||||
@@ -314,7 +346,7 @@ impl Parser {
|
||||
}
|
||||
|
||||
/// Handles mathmatical expressions in the explicit order of PEMDAS
|
||||
fn binary(&mut self, previous: Expression) -> Result<BinaryExpression, ParseError> {
|
||||
fn binary(&mut self, previous: Expression) -> Result<BinaryExpression, Error> {
|
||||
// We cannot use recursion here, as we need to handle the precedence of the operators
|
||||
// We need to use a loop to parse the binary expressions.
|
||||
|
||||
@@ -330,7 +362,7 @@ impl Parser {
|
||||
| Expression::Negation(_) // -1 + 2
|
||||
=> {}
|
||||
_ => {
|
||||
return Err(ParseError::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation")))
|
||||
return Err(Error::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +384,7 @@ impl Parser {
|
||||
|
||||
// validate the vectors and make sure operators.len() == expressions.len() - 1
|
||||
if operators.len() != expressions.len() - 1 {
|
||||
return Err(ParseError::InvalidSyntax(
|
||||
return Err(Error::InvalidSyntax(
|
||||
current_token.clone(),
|
||||
String::from("Invalid number of operators"),
|
||||
));
|
||||
@@ -361,29 +393,26 @@ impl Parser {
|
||||
// Every time we find a valid operator, we pop 2 off the expressions and add one back.
|
||||
// This means that we need to keep track of the current iteration to ensure we are
|
||||
// removing the correct expressions from the vector
|
||||
let mut current_iteration = 0;
|
||||
|
||||
// Loop through operators, and build the binary expressions for exponential operators only
|
||||
for (i, operator) in operators.iter().enumerate() {
|
||||
for (i, operator) in operators.iter().enumerate().rev() {
|
||||
if operator == &Symbol::Exp {
|
||||
let index = i - current_iteration;
|
||||
let left = expressions.remove(index);
|
||||
let right = expressions.remove(index);
|
||||
let right = expressions.remove(i + 1);
|
||||
let left = expressions.remove(i);
|
||||
expressions.insert(
|
||||
index,
|
||||
i,
|
||||
Expression::Binary(BinaryExpression::Exponent(boxed!(left), boxed!(right))),
|
||||
);
|
||||
current_iteration += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// remove all the exponential operators from the operators vector
|
||||
operators.retain(|symbol| symbol != &Symbol::Exp);
|
||||
current_iteration = 0;
|
||||
let mut current_iteration = 0;
|
||||
|
||||
// Loop through operators, and build the binary expressions for multiplication and division operators
|
||||
for (i, operator) in operators.iter().enumerate() {
|
||||
if operator == &Symbol::Asterisk || operator == &Symbol::Slash {
|
||||
if matches!(operator, Symbol::Slash | Symbol::Asterisk | Symbol::Percent) {
|
||||
let index = i - current_iteration;
|
||||
let left = expressions.remove(index);
|
||||
let right = expressions.remove(index);
|
||||
@@ -397,6 +426,10 @@ impl Parser {
|
||||
index,
|
||||
Expression::Binary(BinaryExpression::Divide(boxed!(left), boxed!(right))),
|
||||
),
|
||||
Symbol::Percent => expressions.insert(
|
||||
index,
|
||||
Expression::Binary(BinaryExpression::Modulo(boxed!(left), boxed!(right))),
|
||||
),
|
||||
// safety: we have already checked for the operator
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -405,7 +438,8 @@ impl Parser {
|
||||
}
|
||||
|
||||
// remove all the multiplication and division operators from the operators vector
|
||||
operators.retain(|symbol| symbol != &Symbol::Asterisk && symbol != &Symbol::Slash);
|
||||
operators
|
||||
.retain(|symbol| !matches!(symbol, Symbol::Asterisk | Symbol::Percent | Symbol::Slash));
|
||||
current_iteration = 0;
|
||||
|
||||
// Loop through operators, and build the binary expressions for addition and subtraction operators
|
||||
@@ -432,11 +466,11 @@ impl Parser {
|
||||
}
|
||||
|
||||
// remove all the addition and subtraction operators from the operators vector
|
||||
operators.retain(|symbol| symbol != &Symbol::Plus && symbol != &Symbol::Minus);
|
||||
operators.retain(|symbol| !matches!(symbol, Symbol::Plus | Symbol::Minus));
|
||||
|
||||
// Ensure there is only one expression left in the expressions vector, and no operators left
|
||||
if expressions.len() != 1 || !operators.is_empty() {
|
||||
return Err(ParseError::InvalidSyntax(
|
||||
return Err(Error::InvalidSyntax(
|
||||
current_token.clone(),
|
||||
String::from("Invalid number of operators"),
|
||||
));
|
||||
@@ -457,24 +491,24 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
fn priority(&mut self) -> Result<Box<Expression>, ParseError> {
|
||||
fn priority(&mut self) -> Result<Box<Expression>, Error> {
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
self.assign_next()?;
|
||||
let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?;
|
||||
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
let current_token = token_from_option!(self.get_next()?);
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::RParen)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
Ok(boxed!(expression))
|
||||
}
|
||||
|
||||
fn invocation(&mut self) -> Result<InvocationExpression, ParseError> {
|
||||
fn invocation(&mut self) -> Result<InvocationExpression, Error> {
|
||||
let identifier = extract_token_data!(
|
||||
token_from_option!(self.current_token),
|
||||
TokenType::Identifier(ref id),
|
||||
@@ -484,7 +518,7 @@ impl Parser {
|
||||
// Ensure the next token is a left parenthesis
|
||||
let current_token = token_from_option!(self.get_next()?);
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
let mut arguments = Vec::<Expression>::new();
|
||||
@@ -495,10 +529,10 @@ impl Parser {
|
||||
TokenType::Symbol(Symbol::RParen)
|
||||
) {
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?;
|
||||
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
if let Expression::Block(_) = expression {
|
||||
return Err(ParseError::InvalidSyntax(
|
||||
return Err(Error::InvalidSyntax(
|
||||
current_token,
|
||||
String::from("Block expressions are not allowed in function invocations"),
|
||||
));
|
||||
@@ -510,7 +544,7 @@ impl Parser {
|
||||
if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma))
|
||||
&& !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen))
|
||||
{
|
||||
return Err(ParseError::UnexpectedToken(
|
||||
return Err(Error::UnexpectedToken(
|
||||
token_from_option!(self.get_next()?).clone(),
|
||||
));
|
||||
}
|
||||
@@ -530,20 +564,20 @@ impl Parser {
|
||||
})
|
||||
}
|
||||
|
||||
fn block(&mut self) -> Result<BlockExpression, ParseError> {
|
||||
fn block(&mut self) -> Result<BlockExpression, Error> {
|
||||
let mut expressions = Vec::<Expression>::new();
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
|
||||
// sanity check: make sure the current token is a left brace
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
while !self_matches_peek!(
|
||||
self,
|
||||
TokenType::Symbol(Symbol::RBrace) | TokenType::Keyword(Keyword::Return)
|
||||
) {
|
||||
let expression = self.parse()?.ok_or(ParseError::UnexpectedEOF)?;
|
||||
let expression = self.parse()?.ok_or(Error::UnexpectedEOF)?;
|
||||
expressions.push(expression);
|
||||
}
|
||||
|
||||
@@ -552,7 +586,7 @@ impl Parser {
|
||||
|
||||
if token_matches!(current_token, TokenType::Keyword(Keyword::Return)) {
|
||||
self.assign_next()?;
|
||||
let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?;
|
||||
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||
let return_expr = Expression::Return(boxed!(expression));
|
||||
expressions.push(return_expr);
|
||||
self.assign_next()?;
|
||||
@@ -563,10 +597,10 @@ impl Parser {
|
||||
Ok(BlockExpression(expressions))
|
||||
}
|
||||
|
||||
fn declaration(&mut self) -> Result<Expression, ParseError> {
|
||||
fn declaration(&mut self) -> Result<Expression, Error> {
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
let identifier = extract_token_data!(
|
||||
token_from_option!(self.get_next()?),
|
||||
@@ -577,16 +611,16 @@ impl Parser {
|
||||
let current_token = token_from_option!(self.get_next()?).clone();
|
||||
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
self.assign_next()?;
|
||||
let assignment_expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?;
|
||||
let assignment_expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
// make sure the next token is a semi-colon
|
||||
let current_token = token_from_option!(self.get_next()?);
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::Semicolon)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
Ok(Expression::Declaration(
|
||||
@@ -595,22 +629,22 @@ impl Parser {
|
||||
))
|
||||
}
|
||||
|
||||
fn literal(&mut self) -> Result<Literal, ParseError> {
|
||||
fn literal(&mut self) -> Result<Literal, Error> {
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
let literal = match current_token.token_type {
|
||||
TokenType::Number(num) => Literal::Number(num),
|
||||
TokenType::String(string) => Literal::String(string),
|
||||
_ => return Err(ParseError::UnexpectedToken(current_token.clone())),
|
||||
_ => return Err(Error::UnexpectedToken(current_token.clone())),
|
||||
};
|
||||
|
||||
Ok(literal)
|
||||
}
|
||||
|
||||
fn function(&mut self) -> Result<FunctionExpression, ParseError> {
|
||||
fn function(&mut self) -> Result<FunctionExpression, Error> {
|
||||
let current_token = token_from_option!(self.current_token);
|
||||
// Sanify check that the current token is a `fn` keyword
|
||||
if !self_matches_current!(self, TokenType::Keyword(Keyword::Fn)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
let fn_ident = extract_token_data!(
|
||||
@@ -622,7 +656,7 @@ impl Parser {
|
||||
// make sure next token is a left parenthesis
|
||||
let current_token = token_from_option!(self.get_next()?);
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
}
|
||||
|
||||
let mut arguments = Vec::<String>::new();
|
||||
@@ -638,7 +672,7 @@ impl Parser {
|
||||
extract_token_data!(current_token, TokenType::Identifier(ref id), id.clone());
|
||||
|
||||
if arguments.contains(&argument) {
|
||||
return Err(ParseError::DuplicateIdentifier(current_token.clone()));
|
||||
return Err(Error::DuplicateIdentifier(current_token.clone()));
|
||||
}
|
||||
|
||||
arguments.push(argument);
|
||||
@@ -647,7 +681,7 @@ impl Parser {
|
||||
if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma))
|
||||
&& !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen))
|
||||
{
|
||||
return Err(ParseError::UnexpectedToken(
|
||||
return Err(Error::UnexpectedToken(
|
||||
token_from_option!(self.get_next()?).clone(),
|
||||
));
|
||||
}
|
||||
@@ -664,7 +698,7 @@ impl Parser {
|
||||
// make sure the next token is a left brace
|
||||
let current_token = token_from_option!(self.get_next()?);
|
||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) {
|
||||
return Err(ParseError::UnexpectedToken(current_token.clone()));
|
||||
return Err(Error::UnexpectedToken(current_token.clone()));
|
||||
};
|
||||
|
||||
Ok(FunctionExpression {
|
||||
@@ -674,15 +708,15 @@ impl Parser {
|
||||
})
|
||||
}
|
||||
|
||||
fn syscall(&mut self) -> Result<SysCall, ParseError> {
|
||||
fn syscall(&mut self) -> Result<SysCall, Error> {
|
||||
/// Checks the length of the arguments and returns an error if the length is not equal to the expected length
|
||||
fn check_length(
|
||||
parser: &Parser,
|
||||
arguments: &[Expression],
|
||||
length: usize,
|
||||
) -> Result<(), ParseError> {
|
||||
) -> Result<(), Error> {
|
||||
if arguments.len() != length {
|
||||
return Err(ParseError::InvalidSyntax(
|
||||
return Err(Error::InvalidSyntax(
|
||||
token_from_option!(parser.current_token).clone(),
|
||||
format!("Expected {} arguments", length),
|
||||
));
|
||||
@@ -698,7 +732,7 @@ impl Parser {
|
||||
}
|
||||
Some(Expression::Variable(ident)) => LiteralOrVariable::Variable(ident.clone()),
|
||||
_ => {
|
||||
return Err(ParseError::UnexpectedToken(
|
||||
return Err(Error::UnexpectedToken(
|
||||
token_from_option!(self.current_token).clone(),
|
||||
))
|
||||
}
|
||||
@@ -712,7 +746,7 @@ impl Parser {
|
||||
match $arg {
|
||||
LiteralOrVariable::$matcher(i) => i,
|
||||
_ => {
|
||||
return Err(ParseError::InvalidSyntax(
|
||||
return Err(Error::InvalidSyntax(
|
||||
token_from_option!(self.current_token).clone(),
|
||||
String::from("Expected a variable"),
|
||||
))
|
||||
@@ -726,7 +760,10 @@ impl Parser {
|
||||
|
||||
match invocation.name.as_str() {
|
||||
// system calls
|
||||
"yield" => Ok(SysCall::System(sys_call::System::Yield)),
|
||||
"yield" => {
|
||||
check_length(self, &invocation.arguments, 0)?;
|
||||
Ok(SysCall::System(sys_call::System::Yield))
|
||||
}
|
||||
"sleep" => {
|
||||
check_length(self, &invocation.arguments, 1)?;
|
||||
let mut arg = invocation.arguments.iter();
|
||||
@@ -740,14 +777,44 @@ impl Parser {
|
||||
let device = literal_or_variable!(args.next());
|
||||
|
||||
let Some(Expression::Literal(Literal::String(variable))) = args.next() else {
|
||||
return Err(ParseError::UnexpectedToken(
|
||||
return Err(Error::UnexpectedToken(
|
||||
token_from_option!(self.current_token).clone(),
|
||||
));
|
||||
};
|
||||
|
||||
Ok(SysCall::System(sys_call::System::LoadFromDevice(
|
||||
device,
|
||||
variable.clone(),
|
||||
LiteralOrVariable::Variable(variable.clone()),
|
||||
)))
|
||||
}
|
||||
"loadBatch" => {
|
||||
check_length(self, &invocation.arguments, 3)?;
|
||||
let mut args = invocation.arguments.iter();
|
||||
|
||||
let device_hash = literal_or_variable!(args.next());
|
||||
let logic_type = get_arg!(Literal, literal_or_variable!(args.next()));
|
||||
let batch_mode = get_arg!(Literal, literal_or_variable!(args.next()));
|
||||
|
||||
Ok(SysCall::System(sys_call::System::LoadBatch(
|
||||
device_hash,
|
||||
logic_type,
|
||||
batch_mode,
|
||||
)))
|
||||
}
|
||||
"loadBatchNamed" => {
|
||||
check_length(self, &invocation.arguments, 4)?;
|
||||
let mut args = invocation.arguments.iter();
|
||||
|
||||
let device_hash = literal_or_variable!(args.next());
|
||||
let name_hash = get_arg!(Literal, literal_or_variable!(args.next()));
|
||||
let logic_type = get_arg!(Literal, literal_or_variable!(args.next()));
|
||||
let batch_mode = get_arg!(Literal, literal_or_variable!(args.next()));
|
||||
|
||||
Ok(SysCall::System(sys_call::System::LoadBatchNamed(
|
||||
device_hash,
|
||||
name_hash,
|
||||
logic_type,
|
||||
batch_mode,
|
||||
)))
|
||||
}
|
||||
"setOnDevice" => {
|
||||
@@ -759,7 +826,7 @@ impl Parser {
|
||||
let Literal::String(logic_type) =
|
||||
get_arg!(Literal, literal_or_variable!(args.next()))
|
||||
else {
|
||||
return Err(ParseError::UnexpectedToken(
|
||||
return Err(Error::UnexpectedToken(
|
||||
token_from_option!(self.current_token).clone(),
|
||||
));
|
||||
};
|
||||
@@ -767,7 +834,9 @@ impl Parser {
|
||||
let variable = literal_or_variable!(args.next());
|
||||
|
||||
Ok(SysCall::System(sys_call::System::SetOnDevice(
|
||||
device, logic_type, variable,
|
||||
device,
|
||||
Literal::String(logic_type),
|
||||
variable,
|
||||
)))
|
||||
}
|
||||
// math calls
|
||||
@@ -860,134 +929,3 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
|
||||
macro_rules! parser {
|
||||
($input:expr) => {
|
||||
Parser::new(Tokenizer::from($input.to_owned()))
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_keywords() -> Result<()> {
|
||||
let mut parser = parser!("enum x;");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
let mut parser = parser!("if x {}");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
let mut parser = parser!("else {}");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_declarations() -> Result<()> {
|
||||
let input = r#"
|
||||
let x = 5;
|
||||
// The below line should fail
|
||||
let y = 234
|
||||
"#;
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("(let x = 5)", expression.to_string());
|
||||
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block() -> Result<()> {
|
||||
let input = r#"
|
||||
{
|
||||
let x = 5;
|
||||
let y = 10;
|
||||
}
|
||||
"#;
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("{ (let x = 5); (let y = 10); }", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_expression() -> Result<()> {
|
||||
let input = r#"
|
||||
// This is a function. The parser is starting to get more complex
|
||||
fn add(x, y) {
|
||||
let z = x;
|
||||
}
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
"(fn add(x, y) { { (let z = x); } })",
|
||||
expression.to_string()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_invocation() -> Result<()> {
|
||||
let input = r#"
|
||||
add();
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("add()", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_priority_expression() -> Result<()> {
|
||||
let input = r#"
|
||||
let x = (4);
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("(let x = (4))", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_expression() -> Result<()> {
|
||||
let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap();
|
||||
assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string());
|
||||
|
||||
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap();
|
||||
assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());
|
||||
|
||||
let expr = parser!("(5 - 2) * 10").parse()?.unwrap();
|
||||
assert_eq!("(((5 - 2)) * 10)", expr.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::tree_node::Literal;
|
||||
|
||||
use super::LiteralOrVariable;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -111,13 +113,27 @@ pub enum System {
|
||||
/// ## Examples
|
||||
/// `l r0 d0 Setting`
|
||||
/// `l r1 d5 Pressure`
|
||||
LoadFromDevice(LiteralOrVariable, String),
|
||||
LoadFromDevice(LiteralOrVariable, LiteralOrVariable),
|
||||
/// Function which gets a LogicType from all connected network devices that match
|
||||
/// the provided device hash and name, aggregating them via a batchMode
|
||||
/// ## In Game
|
||||
/// lbn r? deviceHash nameHash logicType batchMode
|
||||
/// ## Examples
|
||||
/// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum
|
||||
LoadBatchNamed(LiteralOrVariable, Literal, Literal, Literal),
|
||||
/// Loads a LogicType from all connected network devices, aggregating them via a
|
||||
/// batchMode
|
||||
/// ## In Game
|
||||
/// lb r? deviceHash loggicType batchMode
|
||||
/// ## Examples
|
||||
/// lb r0 HASH("StructureWallLight") On Minimum
|
||||
LoadBatch(LiteralOrVariable, Literal, Literal),
|
||||
/// Represents a function which stores a setting into a specific device.
|
||||
/// ## In Game
|
||||
/// `s d? logicType r?`
|
||||
/// ## Example
|
||||
/// `s d0 Setting r0`
|
||||
SetOnDevice(LiteralOrVariable, String, LiteralOrVariable),
|
||||
SetOnDevice(LiteralOrVariable, Literal, LiteralOrVariable),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for System {
|
||||
@@ -127,6 +143,10 @@ impl std::fmt::Display for System {
|
||||
System::Sleep(a) => write!(f, "sleep({})", a),
|
||||
System::Hash(a) => write!(f, "HASH({})", a),
|
||||
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b),
|
||||
System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c),
|
||||
System::LoadBatchNamed(a, b, c, d) => {
|
||||
write!(f, "loadBatchNamed({}, {}, {}, {})", a, b, c, d)
|
||||
}
|
||||
System::SetOnDevice(a, b, c) => write!(f, "setOnDevice({}, {}, {})", a, b, c),
|
||||
}
|
||||
}
|
||||
|
||||
21
libs/parser/src/test/blocks.rs
Normal file
21
libs/parser/src/test/blocks.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use tokenizer::Tokenizer;
|
||||
|
||||
use crate::Parser;
|
||||
|
||||
#[test]
|
||||
fn test_block() -> anyhow::Result<()> {
|
||||
let mut parser = crate::parser!(
|
||||
r#"
|
||||
{
|
||||
let x = 5;
|
||||
let y = 10;
|
||||
}
|
||||
"#
|
||||
);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("{ (let x = 5); (let y = 10); }", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
115
libs/parser/src/test/mod.rs
Normal file
115
libs/parser/src/test/mod.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
#[macro_export]
|
||||
macro_rules! parser {
|
||||
($input:expr) => {
|
||||
Parser::new(Tokenizer::from($input.to_owned()))
|
||||
};
|
||||
}
|
||||
|
||||
mod blocks;
|
||||
use super::Parser;
|
||||
use super::Tokenizer;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_keywords() -> Result<()> {
|
||||
let mut parser = parser!("enum x;");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
let mut parser = parser!("if x {}");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
let mut parser = parser!("else {}");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_declarations() -> Result<()> {
|
||||
let input = r#"
|
||||
let x = 5;
|
||||
// The below line should fail
|
||||
let y = 234
|
||||
"#;
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("(let x = 5)", expression.to_string());
|
||||
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_expression() -> Result<()> {
|
||||
let input = r#"
|
||||
// This is a function. The parser is starting to get more complex
|
||||
fn add(x, y) {
|
||||
let z = x;
|
||||
}
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
"(fn add(x, y) { { (let z = x); } })",
|
||||
expression.to_string()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_invocation() -> Result<()> {
|
||||
let input = r#"
|
||||
add();
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("add()", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_priority_expression() -> Result<()> {
|
||||
let input = r#"
|
||||
let x = (4);
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("(let x = (4))", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_expression() -> Result<()> {
|
||||
let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap();
|
||||
assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string());
|
||||
|
||||
let expr = parser!("2 ** 3 ** 4").parse()?.unwrap();
|
||||
assert_eq!("(2 ** (3 ** 4))", expr.to_string());
|
||||
|
||||
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap();
|
||||
assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());
|
||||
|
||||
let expr = parser!("(5 - 2) * 10").parse()?.unwrap();
|
||||
assert_eq!("(((5 - 2)) * 10)", expr.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -23,6 +23,7 @@ pub enum BinaryExpression {
|
||||
Divide(Box<Expression>, Box<Expression>),
|
||||
Subtract(Box<Expression>, Box<Expression>),
|
||||
Exponent(Box<Expression>, Box<Expression>),
|
||||
Modulo(Box<Expression>, Box<Expression>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BinaryExpression {
|
||||
@@ -33,6 +34,7 @@ impl std::fmt::Display for BinaryExpression {
|
||||
BinaryExpression::Divide(l, r) => write!(f, "({} / {})", l, r),
|
||||
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
|
||||
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
|
||||
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use token::{Keyword, Number, Symbol, Temperature, Token, TokenType};
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum TokenizerError {
|
||||
pub enum Error {
|
||||
IOError(err: std::io::Error) {
|
||||
from()
|
||||
display("IO Error: {}", err)
|
||||
@@ -48,7 +48,7 @@ pub struct Tokenizer {
|
||||
}
|
||||
|
||||
impl Tokenizer {
|
||||
pub fn from_path(input_file: impl Into<PathBuf>) -> Result<Self, TokenizerError> {
|
||||
pub fn from_path(input_file: impl Into<PathBuf>) -> Result<Self, Error> {
|
||||
let file = std::fs::File::open(input_file.into())?;
|
||||
let reader = BufReader::new(Box::new(file) as Box<dyn Tokenize>);
|
||||
|
||||
@@ -83,7 +83,7 @@ impl Tokenizer {
|
||||
///
|
||||
/// # Important
|
||||
/// This function will increment the line and column counters
|
||||
fn next_char(&mut self) -> Result<Option<char>, TokenizerError> {
|
||||
fn next_char(&mut self) -> Result<Option<char>, Error> {
|
||||
let bytes_read = self.reader.read(&mut self.char_buffer)?;
|
||||
|
||||
if bytes_read == 0 {
|
||||
@@ -106,7 +106,7 @@ impl Tokenizer {
|
||||
///
|
||||
/// # Important
|
||||
/// This does not increment the line or column counters
|
||||
fn peek_next_char(&mut self) -> Result<Option<char>, TokenizerError> {
|
||||
fn peek_next_char(&mut self) -> Result<Option<char>, Error> {
|
||||
let current_pos = self.reader.stream_position()?;
|
||||
|
||||
let to_return = if self.reader.read(&mut self.char_buffer)? == 0 {
|
||||
@@ -126,7 +126,7 @@ impl Tokenizer {
|
||||
///
|
||||
/// # Important
|
||||
/// This function will increment the line and column counters
|
||||
fn skip_line(&mut self) -> Result<(), TokenizerError> {
|
||||
fn skip_line(&mut self) -> Result<(), Error> {
|
||||
while let Some(next_char) = self.next_char()? {
|
||||
if next_char == '\n' {
|
||||
break;
|
||||
@@ -137,7 +137,7 @@ impl Tokenizer {
|
||||
|
||||
/// Consumes the tokenizer and returns the next token in the stream
|
||||
/// If there are no more tokens in the stream, this function returns None
|
||||
pub fn next_token(&mut self) -> Result<Option<Token>, TokenizerError> {
|
||||
pub fn next_token(&mut self) -> Result<Option<Token>, Error> {
|
||||
while let Some(next_char) = self.next_char()? {
|
||||
// skip whitespace
|
||||
if next_char.is_whitespace() {
|
||||
@@ -165,11 +165,7 @@ impl Tokenizer {
|
||||
return self.tokenize_keyword_or_identifier(next_char).map(Some);
|
||||
}
|
||||
_ => {
|
||||
return Err(TokenizerError::UnknownSymbolError(
|
||||
next_char,
|
||||
self.line,
|
||||
self.column,
|
||||
));
|
||||
return Err(Error::UnknownSymbolError(next_char, self.line, self.column));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +179,7 @@ impl Tokenizer {
|
||||
|
||||
/// Peeks the next token in the stream without consuming it
|
||||
/// If there are no more tokens in the stream, this function returns None
|
||||
pub fn peek_next(&mut self) -> Result<Option<Token>, TokenizerError> {
|
||||
pub fn peek_next(&mut self) -> Result<Option<Token>, Error> {
|
||||
let current_pos = self.reader.stream_position()?;
|
||||
let column = self.column;
|
||||
let line = self.line;
|
||||
@@ -196,7 +192,7 @@ impl Tokenizer {
|
||||
}
|
||||
|
||||
/// Tokenizes a symbol
|
||||
fn tokenize_symbol(&mut self, first_symbol: char) -> Result<Token, TokenizerError> {
|
||||
fn tokenize_symbol(&mut self, first_symbol: char) -> Result<Token, Error> {
|
||||
/// Helper macro to create a symbol token
|
||||
macro_rules! symbol {
|
||||
($symbol:ident) => {
|
||||
@@ -225,6 +221,7 @@ impl Tokenizer {
|
||||
|
||||
'.' => symbol!(Dot),
|
||||
'^' => symbol!(Caret),
|
||||
'%' => symbol!(Percent),
|
||||
|
||||
// multi-character symbols
|
||||
'<' if self.peek_next_char()? == Some('=') => {
|
||||
@@ -266,7 +263,7 @@ impl Tokenizer {
|
||||
symbol!(LogicalOr)
|
||||
}
|
||||
|
||||
_ => Err(TokenizerError::UnknownSymbolError(
|
||||
_ => Err(Error::UnknownSymbolError(
|
||||
first_symbol,
|
||||
self.line,
|
||||
self.column,
|
||||
@@ -275,7 +272,7 @@ impl Tokenizer {
|
||||
}
|
||||
|
||||
/// Tokenizes a number literal. Also handles temperatures with a suffix of `c`, `f`, or `k`.
|
||||
fn tokenize_number(&mut self, first_char: char) -> Result<Token, TokenizerError> {
|
||||
fn tokenize_number(&mut self, first_char: char) -> Result<Token, Error> {
|
||||
let mut primary = String::with_capacity(16);
|
||||
let mut decimal: Option<String> = None;
|
||||
let mut reading_decimal = false;
|
||||
@@ -319,16 +316,16 @@ impl Tokenizer {
|
||||
let decimal_scale = decimal.len() as u32;
|
||||
let number = format!("{}{}", primary, decimal)
|
||||
.parse::<i128>()
|
||||
.map_err(|e| TokenizerError::NumberParseError(e, self.line, self.column))?;
|
||||
.map_err(|e| Error::NumberParseError(e, self.line, self.column))?;
|
||||
Number::Decimal(
|
||||
Decimal::try_from_i128_with_scale(number, decimal_scale)
|
||||
.map_err(|e| TokenizerError::DecimalParseError(e, line, column))?,
|
||||
.map_err(|e| Error::DecimalParseError(e, line, column))?,
|
||||
)
|
||||
} else {
|
||||
Number::Integer(
|
||||
primary
|
||||
.parse()
|
||||
.map_err(|e| TokenizerError::NumberParseError(e, line, column))?,
|
||||
.map_err(|e| Error::NumberParseError(e, line, column))?,
|
||||
)
|
||||
};
|
||||
|
||||
@@ -350,7 +347,7 @@ impl Tokenizer {
|
||||
}
|
||||
|
||||
/// Tokenizes a string literal
|
||||
fn tokenize_string(&mut self, beginning_quote: char) -> Result<Token, TokenizerError> {
|
||||
fn tokenize_string(&mut self, beginning_quote: char) -> Result<Token, Error> {
|
||||
let mut buffer = String::with_capacity(16);
|
||||
|
||||
let column = self.column;
|
||||
@@ -368,10 +365,7 @@ impl Tokenizer {
|
||||
}
|
||||
|
||||
/// Tokenizes a keyword or an identifier. Also handles boolean literals
|
||||
fn tokenize_keyword_or_identifier(
|
||||
&mut self,
|
||||
first_char: char,
|
||||
) -> Result<Token, TokenizerError> {
|
||||
fn tokenize_keyword_or_identifier(&mut self, first_char: char) -> Result<Token, Error> {
|
||||
macro_rules! keyword {
|
||||
($keyword:ident) => {{
|
||||
return Ok(Token::new(
|
||||
@@ -441,9 +435,7 @@ impl Tokenizer {
|
||||
|
||||
looped_char = self.next_char()?;
|
||||
}
|
||||
Err(TokenizerError::UnknownKeywordOrIdentifierError(
|
||||
buffer, line, column,
|
||||
))
|
||||
Err(Error::UnknownKeywordOrIdentifierError(buffer, line, column))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +456,7 @@ impl TokenizerBuffer {
|
||||
|
||||
/// Reads the next token from the tokenizer, pushing the value to the back of the history
|
||||
/// and returning the token
|
||||
pub fn next_token(&mut self) -> Result<Option<Token>, TokenizerError> {
|
||||
pub fn next_token(&mut self) -> Result<Option<Token>, Error> {
|
||||
if let Some(token) = self.buffer.pop_front() {
|
||||
self.history.push_back(token.clone());
|
||||
return Ok(Some(token));
|
||||
@@ -478,7 +470,7 @@ impl TokenizerBuffer {
|
||||
}
|
||||
|
||||
/// Peeks the next token in the stream without adding to the history stack
|
||||
pub fn peek(&mut self) -> Result<Option<Token>, TokenizerError> {
|
||||
pub fn peek(&mut self) -> Result<Option<Token>, Error> {
|
||||
if let Some(token) = self.buffer.front() {
|
||||
return Ok(Some(token.clone()));
|
||||
}
|
||||
@@ -487,7 +479,7 @@ impl TokenizerBuffer {
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn seek_from_current(&mut self, seek_to: i64) -> Result<(), TokenizerError> {
|
||||
fn seek_from_current(&mut self, seek_to: i64) -> Result<(), Error> {
|
||||
use Ordering::*;
|
||||
// if seek_to > 0 then we need to check if the buffer has enough tokens to pop, otherwise we need to read from the tokenizer
|
||||
// if seek_to < 0 then we need to pop from the history and push to the front of the buffer. If not enough, then we throw (we reached the front of the history)
|
||||
@@ -500,7 +492,7 @@ impl TokenizerBuffer {
|
||||
if let Some(token) = self.tokenizer.next_token()? {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
return Err(TokenizerError::IOError(std::io::Error::new(
|
||||
return Err(Error::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"Unexpected EOF",
|
||||
)));
|
||||
@@ -515,7 +507,7 @@ impl TokenizerBuffer {
|
||||
if let Some(token) = self.history.pop_back() {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
return Err(TokenizerError::IOError(std::io::Error::new(
|
||||
return Err(Error::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"Unexpected EOF",
|
||||
)));
|
||||
@@ -530,7 +522,7 @@ impl TokenizerBuffer {
|
||||
}
|
||||
|
||||
/// Adds to or removes from the History stack, allowing the user to move back and forth in the stream
|
||||
pub fn seek(&mut self, from: SeekFrom) -> Result<(), TokenizerError> {
|
||||
pub fn seek(&mut self, from: SeekFrom) -> Result<(), Error> {
|
||||
match from {
|
||||
SeekFrom::Current(seek_to) => self.seek_from_current(seek_to)?,
|
||||
SeekFrom::End(_) => unimplemented!("SeekFrom::End will not be implemented"),
|
||||
@@ -745,7 +737,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_symbol_parse() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from(
|
||||
"^ ! () [] {} , . ; : + - * / < > = != && || >= <=**",
|
||||
"^ ! () [] {} , . ; : + - * / < > = != && || >= <=**%",
|
||||
));
|
||||
|
||||
let expected_tokens = vec![
|
||||
@@ -774,6 +766,7 @@ mod tests {
|
||||
TokenType::Symbol(Symbol::GreaterThanOrEqual),
|
||||
TokenType::Symbol(Symbol::LessThanOrEqual),
|
||||
TokenType::Symbol(Symbol::Exp),
|
||||
TokenType::Symbol(Symbol::Percent),
|
||||
];
|
||||
|
||||
for expected_token in expected_tokens {
|
||||
|
||||
@@ -111,6 +111,12 @@ impl std::fmt::Display for Number {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Number> for String {
|
||||
fn from(value: Number) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum Symbol {
|
||||
// Single Character Symbols
|
||||
@@ -152,6 +158,8 @@ pub enum Symbol {
|
||||
Dot,
|
||||
/// Represents the `^` symbol
|
||||
Caret,
|
||||
/// Represents the `%` symbol
|
||||
Percent,
|
||||
|
||||
// Double Character Symbols
|
||||
/// Represents the `==` symbol
|
||||
@@ -174,7 +182,12 @@ impl Symbol {
|
||||
pub fn is_operator(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Symbol::Plus | Symbol::Minus | Symbol::Asterisk | Symbol::Slash | Symbol::Exp
|
||||
Symbol::Plus
|
||||
| Symbol::Minus
|
||||
| Symbol::Asterisk
|
||||
| Symbol::Slash
|
||||
| Symbol::Exp
|
||||
| Symbol::Percent
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,12 @@ fn compile_from_string(
|
||||
input: &safer_ffi::string::String,
|
||||
output: &mut safer_ffi::string::String,
|
||||
) -> bool {
|
||||
let buffer = Vec::<u8>::new();
|
||||
|
||||
let mut writer = BufWriter::new(buffer);
|
||||
let mut writer = BufWriter::new(Vec::new());
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_string());
|
||||
let parser = Parser::new(tokenizer);
|
||||
|
||||
let compiler = Compiler::new(parser, &mut writer);
|
||||
let compiler = Compiler::new(parser, &mut writer, None);
|
||||
|
||||
let Ok(()) = compiler.compile() else {
|
||||
return false;
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -9,20 +9,20 @@ use std::{
|
||||
io::{BufWriter, Read, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use tokenizer::{Tokenizer, TokenizerError};
|
||||
use tokenizer::{self, Tokenizer};
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
enum StationlangError {
|
||||
TokenizerError(err: TokenizerError) {
|
||||
TokenizerError(err: tokenizer::Error) {
|
||||
from()
|
||||
display("Tokenizer error: {}", err)
|
||||
}
|
||||
ParserError(err: parser::ParseError) {
|
||||
ParserError(err: parser::Error) {
|
||||
from()
|
||||
display("Parser error: {}", err)
|
||||
}
|
||||
CompileError(err: compiler::CompileError) {
|
||||
CompileError(err: compiler::Error) {
|
||||
from()
|
||||
display("Compile error: {}", err)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ fn run_logic() -> Result<(), StationlangError> {
|
||||
None => BufWriter::new(Box::new(std::io::stdout())),
|
||||
};
|
||||
|
||||
let compiler = Compiler::new(parser, &mut writer);
|
||||
let compiler = Compiler::new(parser, &mut writer, None);
|
||||
|
||||
compiler.compile()?;
|
||||
writer.flush()?;
|
||||
|
||||
Reference in New Issue
Block a user