Merge pull request #3 from dbidwell94/compiler-v2-refactor

Compiler v2 refactor
This commit is contained in:
2025-11-24 23:57:12 -07:00
committed by GitHub
22 changed files with 1928 additions and 678 deletions

63
Cargo.lock generated
View File

@@ -197,9 +197,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.52" version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -207,9 +207,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.52" version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -245,10 +245,20 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
name = "compiler" name = "compiler"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"indoc",
"parser", "parser",
"pretty_assertions",
"quick-error", "quick-error",
"tokenizer",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -324,9 +334,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.0" version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]] [[package]]
name = "heck" name = "heck"
@@ -336,12 +346,21 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.12.0" version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [ dependencies = [
"equivalent", "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]] [[package]]
@@ -466,6 +485,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.1.25" version = "0.1.25"
@@ -1035,19 +1064,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "zerocopy" name = "yansi"
version = "0.8.27" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.27" version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

30
README.md Normal file
View 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)

View File

@@ -6,3 +6,9 @@ edition = "2024"
[dependencies] [dependencies]
quick-error = { workspace = true } quick-error = { workspace = true }
parser = { path = "../parser" } parser = { path = "../parser" }
tokenizer = { path = "../tokenizer" }
[dev-dependencies]
anyhow = { version = "1.0" }
indoc = { version = "2.0" }
pretty_assertions = "1"

View File

@@ -1,399 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod test; mod test;
mod v1;
mod variable_manager;
use parser::Parser as ASTParser; pub use v1::{Compiler, CompilerConfig, Error};
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(())
}
}

View 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(())
}

View 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(())
}

View 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(())
}

View File

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

View File

@@ -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
View 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(())
}
}

View 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(())
}
}

View File

@@ -1,7 +1,7 @@
fn addTemperatures(temp1, temp2) { fn addTemperatures(temp1, temp2) {
let toReturn = temp1 + temp2; return temp1 + temp2;
}; };
addTemperatures(15c, 120c); let newTemp1 = addTemperatures(15c, 120c);
addTemperatures(1500f, 20c); let newTemp2 = addTemperatures(50c, 20c);

View File

@@ -1,3 +1,6 @@
#[cfg(test)]
mod test;
pub mod sys_call; pub mod sys_call;
pub mod tree_node; pub mod tree_node;
@@ -5,7 +8,7 @@ use quick_error::quick_error;
use std::io::SeekFrom; use std::io::SeekFrom;
use sys_call::SysCall; use sys_call::SysCall;
use tokenizer::{ use tokenizer::{
Tokenizer, TokenizerBuffer, TokenizerError, self, Tokenizer, TokenizerBuffer,
token::{Keyword, Symbol, Token, TokenType}, token::{Keyword, Symbol, Token, TokenType},
}; };
use tree_node::*; use tree_node::*;
@@ -20,8 +23,8 @@ macro_rules! boxed {
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
pub enum ParseError { pub enum Error {
TokenizerError(err: TokenizerError) { TokenizerError(err: tokenizer::Error) {
from() from()
display("Tokenizer Error: {}", err) display("Tokenizer Error: {}", err)
source(err) source(err)
@@ -57,7 +60,7 @@ macro_rules! token_from_option {
($token:expr) => { ($token:expr) => {
match $token { match $token {
Some(ref token) => token.clone(), 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) => { ($token:ident, $pattern:pat, $extraction:expr) => {
match $token.token_type { match $token.token_type {
$pattern => $extraction, $pattern => $extraction,
_ => return Err(ParseError::UnexpectedToken($token.clone())), _ => return Err(Error::UnexpectedToken($token.clone())),
} }
}; };
($token:expr, $pattern:pat, $extraction:expr) => { ($token:expr, $pattern:pat, $extraction:expr) => {
match $token.token_type { match $token.token_type {
$pattern => $extraction, $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 /// Parses all the input from the tokenizer buffer and returns the resulting expression
/// Expressions are returned in a root block expression node /// 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(); let mut expressions = Vec::<Expression>::new();
while let Some(expression) = self.parse()? { while let Some(expression) = self.parse()? {
@@ -129,7 +132,7 @@ impl Parser {
} }
/// Parses the input from the tokenizer buffer and returns the resulting expression /// 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()?; self.assign_next()?;
let expr = self.expression()?; let expr = self.expression()?;
@@ -141,18 +144,44 @@ impl Parser {
} }
/// Assigns the next token in the tokenizer buffer to the current token /// 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()?; self.current_token = self.tokenizer.next_token()?;
Ok(()) Ok(())
} }
/// Calls `assign_next` and returns the next token in the tokenizer buffer /// 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()?; self.assign_next()?;
Ok(self.current_token.as_ref()) 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 { macro_rules! matches_keyword {
($keyword:expr, $($pattern:pat),+) => { ($keyword:expr, $($pattern:pat),+) => {
matches!($keyword, $($pattern)|+) matches!($keyword, $($pattern)|+)
@@ -167,12 +196,12 @@ impl Parser {
return Ok(None); return Ok(None);
} }
let expr = Some(match current_token.token_type { let expr = match current_token.token_type {
// match unsupported keywords // match unsupported keywords
TokenType::Keyword(e) TokenType::Keyword(e)
if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => 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 // match declarations with a `let` keyword
@@ -214,30 +243,26 @@ impl Parser {
// match priority expressions with a left parenthesis // match priority expressions with a left parenthesis
TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?), TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?),
_ => { // match minus symbols to handle negative numbers or negated expressions
return Err(ParseError::UnexpectedToken(current_token.clone())); 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))
} }
});
let Some(expr) = expr else { _ => {
return Ok(None); return Err(Error::UnexpectedToken(current_token.clone()));
}
}; };
// 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)) 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); let current_token = token_from_option!(self.current_token);
match current_token.token_type { match current_token.token_type {
@@ -257,16 +282,23 @@ impl Parser {
{ {
self.invocation().map(Expression::Invocation) 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 // sanity check, make sure current token is a `device` keyword
let current_token = token_from_option!(self.current_token); let current_token = token_from_option!(self.current_token);
if !self_matches_current!(self, TokenType::Keyword(Keyword::Device)) { 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!( let identifier = extract_token_data!(
@@ -277,7 +309,7 @@ impl Parser {
let current_token = token_from_option!(self.get_next()?).clone(); let current_token = token_from_option!(self.get_next()?).clone();
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { 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!( 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!( let identifier = extract_token_data!(
token_from_option!(self.current_token), token_from_option!(self.current_token),
TokenType::Identifier(ref id), TokenType::Identifier(ref id),
@@ -301,11 +333,11 @@ impl Parser {
let current_token = token_from_option!(self.get_next()?).clone(); let current_token = token_from_option!(self.get_next()?).clone();
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) {
return Err(ParseError::UnexpectedToken(current_token)); return Err(Error::UnexpectedToken(current_token));
} }
self.assign_next()?; self.assign_next()?;
let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
Ok(AssignmentExpression { Ok(AssignmentExpression {
identifier, identifier,
@@ -314,7 +346,7 @@ impl Parser {
} }
/// Handles mathmatical expressions in the explicit order of PEMDAS /// 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 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. // We need to use a loop to parse the binary expressions.
@@ -330,7 +362,7 @@ impl Parser {
| Expression::Negation(_) // -1 + 2 | 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 // validate the vectors and make sure operators.len() == expressions.len() - 1
if operators.len() != expressions.len() - 1 { if operators.len() != expressions.len() - 1 {
return Err(ParseError::InvalidSyntax( return Err(Error::InvalidSyntax(
current_token.clone(), current_token.clone(),
String::from("Invalid number of operators"), 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. // 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 // This means that we need to keep track of the current iteration to ensure we are
// removing the correct expressions from the vector // removing the correct expressions from the vector
let mut current_iteration = 0;
// Loop through operators, and build the binary expressions for exponential operators only // 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 { if operator == &Symbol::Exp {
let index = i - current_iteration; let right = expressions.remove(i + 1);
let left = expressions.remove(index); let left = expressions.remove(i);
let right = expressions.remove(index);
expressions.insert( expressions.insert(
index, i,
Expression::Binary(BinaryExpression::Exponent(boxed!(left), boxed!(right))), Expression::Binary(BinaryExpression::Exponent(boxed!(left), boxed!(right))),
); );
current_iteration += 1;
} }
} }
// remove all the exponential operators from the operators vector // remove all the exponential operators from the operators vector
operators.retain(|symbol| symbol != &Symbol::Exp); 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 // Loop through operators, and build the binary expressions for multiplication and division operators
for (i, operator) in operators.iter().enumerate() { 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 index = i - current_iteration;
let left = expressions.remove(index); let left = expressions.remove(index);
let right = expressions.remove(index); let right = expressions.remove(index);
@@ -397,6 +426,10 @@ impl Parser {
index, index,
Expression::Binary(BinaryExpression::Divide(boxed!(left), boxed!(right))), 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 // safety: we have already checked for the operator
_ => unreachable!(), _ => unreachable!(),
} }
@@ -405,7 +438,8 @@ impl Parser {
} }
// remove all the multiplication and division operators from the operators vector // 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; current_iteration = 0;
// Loop through operators, and build the binary expressions for addition and subtraction operators // 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 // 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 // Ensure there is only one expression left in the expressions vector, and no operators left
if expressions.len() != 1 || !operators.is_empty() { if expressions.len() != 1 || !operators.is_empty() {
return Err(ParseError::InvalidSyntax( return Err(Error::InvalidSyntax(
current_token.clone(), current_token.clone(),
String::from("Invalid number of operators"), 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); let current_token = token_from_option!(self.current_token);
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { 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()?; 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()?); let current_token = token_from_option!(self.get_next()?);
if !token_matches!(current_token, TokenType::Symbol(Symbol::RParen)) { 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)) Ok(boxed!(expression))
} }
fn invocation(&mut self) -> Result<InvocationExpression, ParseError> { fn invocation(&mut self) -> Result<InvocationExpression, Error> {
let identifier = extract_token_data!( let identifier = extract_token_data!(
token_from_option!(self.current_token), token_from_option!(self.current_token),
TokenType::Identifier(ref id), TokenType::Identifier(ref id),
@@ -484,7 +518,7 @@ impl Parser {
// Ensure the next token is a left parenthesis // Ensure the next token is a left parenthesis
let current_token = token_from_option!(self.get_next()?); let current_token = token_from_option!(self.get_next()?);
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { 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(); let mut arguments = Vec::<Expression>::new();
@@ -495,10 +529,10 @@ impl Parser {
TokenType::Symbol(Symbol::RParen) TokenType::Symbol(Symbol::RParen)
) { ) {
let current_token = token_from_option!(self.current_token); 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 { if let Expression::Block(_) = expression {
return Err(ParseError::InvalidSyntax( return Err(Error::InvalidSyntax(
current_token, current_token,
String::from("Block expressions are not allowed in function invocations"), 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)) if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma))
&& !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen))
{ {
return Err(ParseError::UnexpectedToken( return Err(Error::UnexpectedToken(
token_from_option!(self.get_next()?).clone(), 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 mut expressions = Vec::<Expression>::new();
let current_token = token_from_option!(self.current_token); let current_token = token_from_option!(self.current_token);
// sanity check: make sure the current token is a left brace // sanity check: make sure the current token is a left brace
if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) { 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!( while !self_matches_peek!(
self, self,
TokenType::Symbol(Symbol::RBrace) | TokenType::Keyword(Keyword::Return) 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); expressions.push(expression);
} }
@@ -552,7 +586,7 @@ impl Parser {
if token_matches!(current_token, TokenType::Keyword(Keyword::Return)) { if token_matches!(current_token, TokenType::Keyword(Keyword::Return)) {
self.assign_next()?; 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)); let return_expr = Expression::Return(boxed!(expression));
expressions.push(return_expr); expressions.push(return_expr);
self.assign_next()?; self.assign_next()?;
@@ -563,10 +597,10 @@ impl Parser {
Ok(BlockExpression(expressions)) 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); let current_token = token_from_option!(self.current_token);
if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) { 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!( let identifier = extract_token_data!(
token_from_option!(self.get_next()?), token_from_option!(self.get_next()?),
@@ -577,16 +611,16 @@ impl Parser {
let current_token = token_from_option!(self.get_next()?).clone(); let current_token = token_from_option!(self.get_next()?).clone();
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { 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()?; 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 // make sure the next token is a semi-colon
let current_token = token_from_option!(self.get_next()?); let current_token = token_from_option!(self.get_next()?);
if !token_matches!(current_token, TokenType::Symbol(Symbol::Semicolon)) { 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( 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 current_token = token_from_option!(self.current_token);
let literal = match current_token.token_type { let literal = match current_token.token_type {
TokenType::Number(num) => Literal::Number(num), TokenType::Number(num) => Literal::Number(num),
TokenType::String(string) => Literal::String(string), TokenType::String(string) => Literal::String(string),
_ => return Err(ParseError::UnexpectedToken(current_token.clone())), _ => return Err(Error::UnexpectedToken(current_token.clone())),
}; };
Ok(literal) 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); let current_token = token_from_option!(self.current_token);
// Sanify check that the current token is a `fn` keyword // Sanify check that the current token is a `fn` keyword
if !self_matches_current!(self, TokenType::Keyword(Keyword::Fn)) { 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!( let fn_ident = extract_token_data!(
@@ -622,7 +656,7 @@ impl Parser {
// make sure next token is a left parenthesis // make sure next token is a left parenthesis
let current_token = token_from_option!(self.get_next()?); let current_token = token_from_option!(self.get_next()?);
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { 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(); let mut arguments = Vec::<String>::new();
@@ -638,7 +672,7 @@ impl Parser {
extract_token_data!(current_token, TokenType::Identifier(ref id), id.clone()); extract_token_data!(current_token, TokenType::Identifier(ref id), id.clone());
if arguments.contains(&argument) { if arguments.contains(&argument) {
return Err(ParseError::DuplicateIdentifier(current_token.clone())); return Err(Error::DuplicateIdentifier(current_token.clone()));
} }
arguments.push(argument); arguments.push(argument);
@@ -647,7 +681,7 @@ impl Parser {
if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma)) if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma))
&& !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen))
{ {
return Err(ParseError::UnexpectedToken( return Err(Error::UnexpectedToken(
token_from_option!(self.get_next()?).clone(), token_from_option!(self.get_next()?).clone(),
)); ));
} }
@@ -664,7 +698,7 @@ impl Parser {
// make sure the next token is a left brace // make sure the next token is a left brace
let current_token = token_from_option!(self.get_next()?); let current_token = token_from_option!(self.get_next()?);
if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) { 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 { 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 /// Checks the length of the arguments and returns an error if the length is not equal to the expected length
fn check_length( fn check_length(
parser: &Parser, parser: &Parser,
arguments: &[Expression], arguments: &[Expression],
length: usize, length: usize,
) -> Result<(), ParseError> { ) -> Result<(), Error> {
if arguments.len() != length { if arguments.len() != length {
return Err(ParseError::InvalidSyntax( return Err(Error::InvalidSyntax(
token_from_option!(parser.current_token).clone(), token_from_option!(parser.current_token).clone(),
format!("Expected {} arguments", length), format!("Expected {} arguments", length),
)); ));
@@ -698,7 +732,7 @@ impl Parser {
} }
Some(Expression::Variable(ident)) => LiteralOrVariable::Variable(ident.clone()), Some(Expression::Variable(ident)) => LiteralOrVariable::Variable(ident.clone()),
_ => { _ => {
return Err(ParseError::UnexpectedToken( return Err(Error::UnexpectedToken(
token_from_option!(self.current_token).clone(), token_from_option!(self.current_token).clone(),
)) ))
} }
@@ -712,7 +746,7 @@ impl Parser {
match $arg { match $arg {
LiteralOrVariable::$matcher(i) => i, LiteralOrVariable::$matcher(i) => i,
_ => { _ => {
return Err(ParseError::InvalidSyntax( return Err(Error::InvalidSyntax(
token_from_option!(self.current_token).clone(), token_from_option!(self.current_token).clone(),
String::from("Expected a variable"), String::from("Expected a variable"),
)) ))
@@ -726,7 +760,10 @@ impl Parser {
match invocation.name.as_str() { match invocation.name.as_str() {
// system calls // 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" => { "sleep" => {
check_length(self, &invocation.arguments, 1)?; check_length(self, &invocation.arguments, 1)?;
let mut arg = invocation.arguments.iter(); let mut arg = invocation.arguments.iter();
@@ -740,14 +777,44 @@ impl Parser {
let device = literal_or_variable!(args.next()); let device = literal_or_variable!(args.next());
let Some(Expression::Literal(Literal::String(variable))) = args.next() else { 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(), token_from_option!(self.current_token).clone(),
)); ));
}; };
Ok(SysCall::System(sys_call::System::LoadFromDevice( Ok(SysCall::System(sys_call::System::LoadFromDevice(
device, 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" => { "setOnDevice" => {
@@ -759,7 +826,7 @@ impl Parser {
let Literal::String(logic_type) = let Literal::String(logic_type) =
get_arg!(Literal, literal_or_variable!(args.next())) get_arg!(Literal, literal_or_variable!(args.next()))
else { else {
return Err(ParseError::UnexpectedToken( return Err(Error::UnexpectedToken(
token_from_option!(self.current_token).clone(), token_from_option!(self.current_token).clone(),
)); ));
}; };
@@ -767,7 +834,9 @@ impl Parser {
let variable = literal_or_variable!(args.next()); let variable = literal_or_variable!(args.next());
Ok(SysCall::System(sys_call::System::SetOnDevice( Ok(SysCall::System(sys_call::System::SetOnDevice(
device, logic_type, variable, device,
Literal::String(logic_type),
variable,
))) )))
} }
// math calls // 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(())
}
}

View File

@@ -1,3 +1,5 @@
use crate::tree_node::Literal;
use super::LiteralOrVariable; use super::LiteralOrVariable;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@@ -111,13 +113,27 @@ pub enum System {
/// ## Examples /// ## Examples
/// `l r0 d0 Setting` /// `l r0 d0 Setting`
/// `l r1 d5 Pressure` /// `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. /// Represents a function which stores a setting into a specific device.
/// ## In Game /// ## In Game
/// `s d? logicType r?` /// `s d? logicType r?`
/// ## Example /// ## Example
/// `s d0 Setting r0` /// `s d0 Setting r0`
SetOnDevice(LiteralOrVariable, String, LiteralOrVariable), SetOnDevice(LiteralOrVariable, Literal, LiteralOrVariable),
} }
impl std::fmt::Display for System { impl std::fmt::Display for System {
@@ -127,6 +143,10 @@ impl std::fmt::Display for System {
System::Sleep(a) => write!(f, "sleep({})", a), System::Sleep(a) => write!(f, "sleep({})", a),
System::Hash(a) => write!(f, "HASH({})", a), System::Hash(a) => write!(f, "HASH({})", a),
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b), 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), System::SetOnDevice(a, b, c) => write!(f, "setOnDevice({}, {}, {})", a, b, c),
} }
} }

View 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
View 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(())
}

View File

@@ -23,6 +23,7 @@ pub enum BinaryExpression {
Divide(Box<Expression>, Box<Expression>), Divide(Box<Expression>, Box<Expression>),
Subtract(Box<Expression>, Box<Expression>), Subtract(Box<Expression>, Box<Expression>),
Exponent(Box<Expression>, Box<Expression>), Exponent(Box<Expression>, Box<Expression>),
Modulo(Box<Expression>, Box<Expression>),
} }
impl std::fmt::Display for BinaryExpression { 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::Divide(l, r) => write!(f, "({} / {})", l, r),
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r), BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r), BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
} }
} }
} }

View File

@@ -12,7 +12,7 @@ use token::{Keyword, Number, Symbol, Temperature, Token, TokenType};
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
pub enum TokenizerError { pub enum Error {
IOError(err: std::io::Error) { IOError(err: std::io::Error) {
from() from()
display("IO Error: {}", err) display("IO Error: {}", err)
@@ -48,7 +48,7 @@ pub struct Tokenizer {
} }
impl 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 file = std::fs::File::open(input_file.into())?;
let reader = BufReader::new(Box::new(file) as Box<dyn Tokenize>); let reader = BufReader::new(Box::new(file) as Box<dyn Tokenize>);
@@ -83,7 +83,7 @@ impl Tokenizer {
/// ///
/// # Important /// # Important
/// This function will increment the line and column counters /// 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)?; let bytes_read = self.reader.read(&mut self.char_buffer)?;
if bytes_read == 0 { if bytes_read == 0 {
@@ -106,7 +106,7 @@ impl Tokenizer {
/// ///
/// # Important /// # Important
/// This does not increment the line or column counters /// 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 current_pos = self.reader.stream_position()?;
let to_return = if self.reader.read(&mut self.char_buffer)? == 0 { let to_return = if self.reader.read(&mut self.char_buffer)? == 0 {
@@ -126,7 +126,7 @@ impl Tokenizer {
/// ///
/// # Important /// # Important
/// This function will increment the line and column counters /// 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()? { while let Some(next_char) = self.next_char()? {
if next_char == '\n' { if next_char == '\n' {
break; break;
@@ -137,7 +137,7 @@ impl Tokenizer {
/// Consumes the tokenizer and returns the next token in the stream /// Consumes the tokenizer and returns the next token in the stream
/// If there are no more tokens in the stream, this function returns None /// 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()? { while let Some(next_char) = self.next_char()? {
// skip whitespace // skip whitespace
if next_char.is_whitespace() { if next_char.is_whitespace() {
@@ -165,11 +165,7 @@ impl Tokenizer {
return self.tokenize_keyword_or_identifier(next_char).map(Some); return self.tokenize_keyword_or_identifier(next_char).map(Some);
} }
_ => { _ => {
return Err(TokenizerError::UnknownSymbolError( return Err(Error::UnknownSymbolError(next_char, self.line, self.column));
next_char,
self.line,
self.column,
));
} }
} }
} }
@@ -183,7 +179,7 @@ impl Tokenizer {
/// Peeks the next token in the stream without consuming it /// Peeks the next token in the stream without consuming it
/// If there are no more tokens in the stream, this function returns None /// 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 current_pos = self.reader.stream_position()?;
let column = self.column; let column = self.column;
let line = self.line; let line = self.line;
@@ -196,7 +192,7 @@ impl Tokenizer {
} }
/// Tokenizes a symbol /// 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 /// Helper macro to create a symbol token
macro_rules! symbol { macro_rules! symbol {
($symbol:ident) => { ($symbol:ident) => {
@@ -225,6 +221,7 @@ impl Tokenizer {
'.' => symbol!(Dot), '.' => symbol!(Dot),
'^' => symbol!(Caret), '^' => symbol!(Caret),
'%' => symbol!(Percent),
// multi-character symbols // multi-character symbols
'<' if self.peek_next_char()? == Some('=') => { '<' if self.peek_next_char()? == Some('=') => {
@@ -266,7 +263,7 @@ impl Tokenizer {
symbol!(LogicalOr) symbol!(LogicalOr)
} }
_ => Err(TokenizerError::UnknownSymbolError( _ => Err(Error::UnknownSymbolError(
first_symbol, first_symbol,
self.line, self.line,
self.column, self.column,
@@ -275,7 +272,7 @@ impl Tokenizer {
} }
/// Tokenizes a number literal. Also handles temperatures with a suffix of `c`, `f`, or `k`. /// 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 primary = String::with_capacity(16);
let mut decimal: Option<String> = None; let mut decimal: Option<String> = None;
let mut reading_decimal = false; let mut reading_decimal = false;
@@ -319,16 +316,16 @@ impl Tokenizer {
let decimal_scale = decimal.len() as u32; let decimal_scale = decimal.len() as u32;
let number = format!("{}{}", primary, decimal) let number = format!("{}{}", primary, decimal)
.parse::<i128>() .parse::<i128>()
.map_err(|e| TokenizerError::NumberParseError(e, self.line, self.column))?; .map_err(|e| Error::NumberParseError(e, self.line, self.column))?;
Number::Decimal( Number::Decimal(
Decimal::try_from_i128_with_scale(number, decimal_scale) 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 { } else {
Number::Integer( Number::Integer(
primary primary
.parse() .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 /// 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 mut buffer = String::with_capacity(16);
let column = self.column; let column = self.column;
@@ -368,10 +365,7 @@ impl Tokenizer {
} }
/// Tokenizes a keyword or an identifier. Also handles boolean literals /// Tokenizes a keyword or an identifier. Also handles boolean literals
fn tokenize_keyword_or_identifier( fn tokenize_keyword_or_identifier(&mut self, first_char: char) -> Result<Token, Error> {
&mut self,
first_char: char,
) -> Result<Token, TokenizerError> {
macro_rules! keyword { macro_rules! keyword {
($keyword:ident) => {{ ($keyword:ident) => {{
return Ok(Token::new( return Ok(Token::new(
@@ -441,9 +435,7 @@ impl Tokenizer {
looped_char = self.next_char()?; looped_char = self.next_char()?;
} }
Err(TokenizerError::UnknownKeywordOrIdentifierError( Err(Error::UnknownKeywordOrIdentifierError(buffer, line, column))
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 /// Reads the next token from the tokenizer, pushing the value to the back of the history
/// and returning the token /// 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() { if let Some(token) = self.buffer.pop_front() {
self.history.push_back(token.clone()); self.history.push_back(token.clone());
return Ok(Some(token)); return Ok(Some(token));
@@ -478,7 +470,7 @@ impl TokenizerBuffer {
} }
/// Peeks the next token in the stream without adding to the history stack /// 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() { if let Some(token) = self.buffer.front() {
return Ok(Some(token.clone())); return Ok(Some(token.clone()));
} }
@@ -487,7 +479,7 @@ impl TokenizerBuffer {
Ok(token) 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::*; 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 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) // 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()? { if let Some(token) = self.tokenizer.next_token()? {
tokens.push(token); tokens.push(token);
} else { } else {
return Err(TokenizerError::IOError(std::io::Error::new( return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"Unexpected EOF", "Unexpected EOF",
))); )));
@@ -515,7 +507,7 @@ impl TokenizerBuffer {
if let Some(token) = self.history.pop_back() { if let Some(token) = self.history.pop_back() {
tokens.push(token); tokens.push(token);
} else { } else {
return Err(TokenizerError::IOError(std::io::Error::new( return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"Unexpected EOF", "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 /// 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 { match from {
SeekFrom::Current(seek_to) => self.seek_from_current(seek_to)?, SeekFrom::Current(seek_to) => self.seek_from_current(seek_to)?,
SeekFrom::End(_) => unimplemented!("SeekFrom::End will not be implemented"), SeekFrom::End(_) => unimplemented!("SeekFrom::End will not be implemented"),
@@ -745,7 +737,7 @@ mod tests {
#[test] #[test]
fn test_symbol_parse() -> Result<()> { fn test_symbol_parse() -> Result<()> {
let mut tokenizer = Tokenizer::from(String::from( let mut tokenizer = Tokenizer::from(String::from(
"^ ! () [] {} , . ; : + - * / < > = != && || >= <=**", "^ ! () [] {} , . ; : + - * / < > = != && || >= <=**%",
)); ));
let expected_tokens = vec![ let expected_tokens = vec![
@@ -774,6 +766,7 @@ mod tests {
TokenType::Symbol(Symbol::GreaterThanOrEqual), TokenType::Symbol(Symbol::GreaterThanOrEqual),
TokenType::Symbol(Symbol::LessThanOrEqual), TokenType::Symbol(Symbol::LessThanOrEqual),
TokenType::Symbol(Symbol::Exp), TokenType::Symbol(Symbol::Exp),
TokenType::Symbol(Symbol::Percent),
]; ];
for expected_token in expected_tokens { for expected_token in expected_tokens {

View File

@@ -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)] #[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
pub enum Symbol { pub enum Symbol {
// Single Character Symbols // Single Character Symbols
@@ -152,6 +158,8 @@ pub enum Symbol {
Dot, Dot,
/// Represents the `^` symbol /// Represents the `^` symbol
Caret, Caret,
/// Represents the `%` symbol
Percent,
// Double Character Symbols // Double Character Symbols
/// Represents the `==` symbol /// Represents the `==` symbol
@@ -174,7 +182,12 @@ impl Symbol {
pub fn is_operator(&self) -> bool { pub fn is_operator(&self) -> bool {
matches!( matches!(
self, self,
Symbol::Plus | Symbol::Minus | Symbol::Asterisk | Symbol::Slash | Symbol::Exp Symbol::Plus
| Symbol::Minus
| Symbol::Asterisk
| Symbol::Slash
| Symbol::Exp
| Symbol::Percent
) )
} }

View File

@@ -9,14 +9,12 @@ fn compile_from_string(
input: &safer_ffi::string::String, input: &safer_ffi::string::String,
output: &mut safer_ffi::string::String, output: &mut safer_ffi::string::String,
) -> bool { ) -> bool {
let buffer = Vec::<u8>::new(); let mut writer = BufWriter::new(Vec::new());
let mut writer = BufWriter::new(buffer);
let tokenizer = Tokenizer::from(input.to_string()); let tokenizer = Tokenizer::from(input.to_string());
let parser = Parser::new(tokenizer); 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 { let Ok(()) = compiler.compile() else {
return false; return false;

View File

@@ -9,20 +9,20 @@ use std::{
io::{BufWriter, Read, Write}, io::{BufWriter, Read, Write},
path::PathBuf, path::PathBuf,
}; };
use tokenizer::{Tokenizer, TokenizerError}; use tokenizer::{self, Tokenizer};
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
enum StationlangError { enum StationlangError {
TokenizerError(err: TokenizerError) { TokenizerError(err: tokenizer::Error) {
from() from()
display("Tokenizer error: {}", err) display("Tokenizer error: {}", err)
} }
ParserError(err: parser::ParseError) { ParserError(err: parser::Error) {
from() from()
display("Parser error: {}", err) display("Parser error: {}", err)
} }
CompileError(err: compiler::CompileError) { CompileError(err: compiler::Error) {
from() from()
display("Compile error: {}", err) display("Compile error: {}", err)
} }
@@ -71,7 +71,7 @@ fn run_logic() -> Result<(), StationlangError> {
None => BufWriter::new(Box::new(std::io::stdout())), 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()?; compiler.compile()?;
writer.flush()?; writer.flush()?;