diff --git a/Cargo.lock b/Cargo.lock index 916ef8f..b534c4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,9 +197,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.52" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.52" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -245,10 +245,20 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" name = "compiler" version = "0.1.0" dependencies = [ + "anyhow", + "indoc", "parser", + "pretty_assertions", "quick-error", + "tokenizer", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "equivalent" version = "1.0.2" @@ -324,9 +334,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -336,12 +346,21 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", ] [[package]] @@ -466,6 +485,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.1.25" @@ -1035,19 +1064,25 @@ dependencies = [ ] [[package]] -name = "zerocopy" -version = "0.8.27" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", "quote", diff --git a/LICENSE b/LICENSE index 789d263..23f03a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Devin Bidwell +Copyright (c) 2024-2025 Devin Bidwell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md new file mode 100644 index 0000000..b169a15 --- /dev/null +++ b/README.md @@ -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) diff --git a/libs/compiler/Cargo.toml b/libs/compiler/Cargo.toml index edf4fd3..85434ac 100644 --- a/libs/compiler/Cargo.toml +++ b/libs/compiler/Cargo.toml @@ -6,3 +6,9 @@ edition = "2024" [dependencies] quick-error = { workspace = true } parser = { path = "../parser" } +tokenizer = { path = "../tokenizer" } + +[dev-dependencies] +anyhow = { version = "1.0" } +indoc = { version = "2.0" } +pretty_assertions = "1" diff --git a/libs/compiler/src/lib.rs b/libs/compiler/src/lib.rs index 2a490af..db2ac3f 100644 --- a/libs/compiler/src/lib.rs +++ b/libs/compiler/src/lib.rs @@ -1,399 +1,6 @@ #[cfg(test)] mod test; +mod v1; +mod variable_manager; -use parser::Parser as ASTParser; -use parser::sys_call::SysCall; -use parser::tree_node::*; -use quick_error::quick_error; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::io::{BufWriter, Write}; - -quick_error! { - #[derive(Debug)] - pub enum CompileError { - ParseError(err: parser::ParseError) { - from() - display("Parse error: {}", err) - } - ScopeError { - display("A fatal error has occurred with the compiler. Scope could not be found.") - } - WriteError(err: std::io::Error) { - from() - display("Write error: {}", err) - } - DuplicateVariable(variable: String) { - display("A variable with the same name already exists in the current scope: {}", variable) - } - VariableNotFound(variable: String) { - display("Variable {} was not found in the current scope.", variable) - } - MissingFunction(name: String) { - display("Function {} was not found in the function table.", name) - } - MissingDevice(name: String) { - display("Device {} was not found in the device table.", name) - } - InvalidSyscall(syscall: SysCall) { - display("Syscall {} is not valid.", syscall) - } - } -} - -pub struct Compiler<'a, W: std::io::Write> { - parser: ASTParser, - /// Max stack size for the program is by default 512. - variable_scope: Vec>, - function_locations: HashMap, - devices: HashMap, - output: &'a mut BufWriter, - current_line: usize, - declared_main: bool, -} - -impl<'a, W: std::io::Write> Compiler<'a, W> { - pub fn new(parser: ASTParser, writer: &'a mut BufWriter) -> 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 { - 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 { - 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) -> 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( - compiler: &mut Compiler, - op: &str, - left: Expression, - right: Expression, - ) -> Result<(), CompileError> { - match left { - Expression::Literal(Literal::Number(num)) => { - compiler.write_output(format!("push {num}"))?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - Expression::Variable(var_name) => { - let var_offset = compiler.get_variable_index(&var_name)? + 1; - compiler.write_output(format!("sub r15 sp {var_offset}"))?; - compiler.write_output("get r15 db r15")?; - compiler.write_output("push r15")?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - Expression::Priority(expr) => match *expr { - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - _ => todo!(), - }, - _ => todo!(), - }; - - match right { - Expression::Literal(Literal::Number(num)) => { - compiler.write_output(format!("push {num}"))?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - Expression::Variable(var_name) => { - let var_offset = compiler.get_variable_index(&var_name)? + 1; - compiler.write_output(format!("sub r15 sp {}", var_offset))?; - compiler.write_output("get r15 db r15")?; - compiler.write_output("push r15")?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - Expression::Priority(expr) => match *expr { - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - _ => todo!(), - }, - _ => todo!(), - }; - - compiler.write_output("pop r1")?; - compiler.write_output("pop r0")?; - compiler.write_output(format!("{op} r0 r0 r1"))?; - compiler.write_output("push r0")?; - - Ok(()) - } - - match expr { - BinaryExpression::Add(left, right) => { - perform_operation(self, "add", *left, *right)?; - } - BinaryExpression::Subtract(left, right) => { - perform_operation(self, "sub", *left, *right)?; - } - BinaryExpression::Multiply(left, right) => { - perform_operation(self, "mul", *left, *right)?; - } - BinaryExpression::Divide(left, right) => { - perform_operation(self, "div", *left, *right)?; - } - _ => todo!("Operation not currently supported"), - } - self.variable_scope.pop(); - - Ok(()) - } - - fn invocation_expression(&mut self, expr: InvocationExpression) -> Result<(), CompileError> { - let function_name = expr.name; - let args_count = expr.arguments.len(); - - let function_line = *self - .function_locations - .get(&function_name) - .ok_or(CompileError::MissingFunction(function_name.clone()))?; - - let mut to_write = String::new(); - - self.push_stack(&format!("{function_name}ReturnAddress"))?; - - for (iter_index, arg) in expr.arguments.into_iter().enumerate() { - match arg { - Expression::Literal(Literal::Number(num)) => { - to_write.push_str(&format!("push {}\n", num)); - } - Expression::Variable(var_name) => { - let index = self.get_variable_index(&var_name)?; - - to_write.push_str(&format!("sub r15 sp {index}\n")); - to_write.push_str("get r15 db r15\n"); - to_write.push_str("push r15\n"); - } - Expression::Binary(expr) => { - self.binary_expression(expr)?; - to_write.push_str("push r0\n"); - } - _ => todo!("something is up with the arguments: {arg:?}"), - } - self.push_stack(&format!("{function_name}Invocation{iter_index}"))?; - } - - // push the return address onto the stack. Current + to write + pushing the return address - let return_addr = self.current_line + to_write.lines().count() + 2; - self.write_output(format!("push {return_addr}"))?; - self.output.write_all(to_write.as_bytes())?; - self.current_line = return_addr - 1; - - self.write_output(format!("j {function_line}"))?; - - self.pop_current(&format!("{function_name}ReturnAddress"))?; - for i in 0..args_count { - self.pop_current(&format!("{function_name}Invocation{i}"))?; - } - - Ok(()) - } - - fn function_expression(&mut self, expression: FunctionExpression) -> Result<(), CompileError> { - let func_name = expression.name; - - self.variable_scope.push(HashMap::new()); - - self.function_locations.insert(func_name, self.current_line); - - for arg in expression.arguments.iter().rev() { - self.push_stack(arg)?; - } - - for expr in expression.body.0 { - self.expression(expr)?; - } - - let scope = self.variable_scope.pop().ok_or(CompileError::ScopeError)?; - - self.write_output(format!("sub sp sp {0}", scope.len()))?; - self.write_output("pop ra")?; - self.write_output("j ra")?; - - Ok(()) - } - - fn block_expression(&mut self, mut expression: BlockExpression) -> Result<(), CompileError> { - self.variable_scope.push(HashMap::new()); - - // hoist functions to the top of the block - expression.0.sort_by(|a, b| { - if matches!(a, Expression::Function(_)) && matches!(b, Expression::Function(_)) { - Ordering::Equal - } else if matches!(a, Expression::Function(_)) { - Ordering::Less - } else { - Ordering::Greater - } - }); - - for expr in expression.0 { - // if we haven't declared main yet and we have already declared all the function expressions, declare main - if !self.declared_main && !matches!(expr, Expression::Function(_)) { - self.write_output("main:")?; - self.declared_main = true; - } - self.expression(expr)?; - } - - self.variable_scope.pop(); - - Ok(()) - } -} +pub use v1::{Compiler, CompilerConfig, Error}; diff --git a/libs/compiler/src/test/binary_expression.rs b/libs/compiler/src/test/binary_expression.rs new file mode 100644 index 0000000..8f890d4 --- /dev/null +++ b/libs/compiler/src/test/binary_expression.rs @@ -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(()) +} diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/libs/compiler/src/test/declaration_function_invocation.rs new file mode 100644 index 0000000..788bb5f --- /dev/null +++ b/libs/compiler/src/test/declaration_function_invocation.rs @@ -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(()) +} diff --git a/libs/compiler/src/test/declaration_literal.rs b/libs/compiler/src/test/declaration_literal.rs new file mode 100644 index 0000000..bcfec16 --- /dev/null +++ b/libs/compiler/src/test/declaration_literal.rs @@ -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(()) +} diff --git a/libs/compiler/src/test/function_declaration.rs b/libs/compiler/src/test/function_declaration.rs new file mode 100644 index 0000000..28200f7 --- /dev/null +++ b/libs/compiler/src/test/function_declaration.rs @@ -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(()) +} diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index e69de29..09fa1aa 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -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; diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs new file mode 100644 index 0000000..8e0fd28 --- /dev/null +++ b/libs/compiler/src/v1.rs @@ -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, +} + +pub struct Compiler<'a, W: std::io::Write> { + parser: ASTParser, + function_locations: HashMap, + function_metadata: HashMap>, + devices: HashMap, + output: &'a mut BufWriter, + 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, + config: Option, + ) -> 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) -> 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, 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, + ) -> 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, 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::>(); + 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 { + 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), 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 { + 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 { + 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(()) + } +} diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs new file mode 100644 index 0000000..ca47f1c --- /dev/null +++ b/libs/compiler/src/variable_manager.rs @@ -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, + persistant_vars: VecDeque, + var_lookup_table: HashMap, + 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 { + 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, + location: LocationRequest, + ) -> Result { + 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, + ) -> Result { + 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) -> 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(()) + } +} diff --git a/libs/compiler/test_files/math.slang b/libs/compiler/test_files/math.slang index 2fa5adc..1d9adb2 100644 --- a/libs/compiler/test_files/math.slang +++ b/libs/compiler/test_files/math.slang @@ -1,7 +1,7 @@ fn addTemperatures(temp1, temp2) { - let toReturn = temp1 + temp2; + return temp1 + temp2; }; -addTemperatures(15c, 120c); -addTemperatures(1500f, 20c); +let newTemp1 = addTemperatures(15c, 120c); +let newTemp2 = addTemperatures(50c, 20c); diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index ece1be2..9e2a635 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod test; + pub mod sys_call; pub mod tree_node; @@ -5,7 +8,7 @@ use quick_error::quick_error; use std::io::SeekFrom; use sys_call::SysCall; use tokenizer::{ - Tokenizer, TokenizerBuffer, TokenizerError, + self, Tokenizer, TokenizerBuffer, token::{Keyword, Symbol, Token, TokenType}, }; use tree_node::*; @@ -20,8 +23,8 @@ macro_rules! boxed { quick_error! { #[derive(Debug)] - pub enum ParseError { - TokenizerError(err: TokenizerError) { + pub enum Error { + TokenizerError(err: tokenizer::Error) { from() display("Tokenizer Error: {}", err) source(err) @@ -57,7 +60,7 @@ macro_rules! token_from_option { ($token:expr) => { match $token { Some(ref token) => token.clone(), - None => return Err(ParseError::UnexpectedEOF), + None => return Err(Error::UnexpectedEOF), } }; } @@ -66,14 +69,14 @@ macro_rules! extract_token_data { ($token:ident, $pattern:pat, $extraction:expr) => { match $token.token_type { $pattern => $extraction, - _ => return Err(ParseError::UnexpectedToken($token.clone())), + _ => return Err(Error::UnexpectedToken($token.clone())), } }; ($token:expr, $pattern:pat, $extraction:expr) => { match $token.token_type { $pattern => $extraction, _ => { - return Err(ParseError::UnexpectedToken($token.clone())); + return Err(Error::UnexpectedToken($token.clone())); } } }; @@ -118,7 +121,7 @@ impl Parser { /// Parses all the input from the tokenizer buffer and returns the resulting expression /// Expressions are returned in a root block expression node - pub fn parse_all(&mut self) -> Result, ParseError> { + pub fn parse_all(&mut self) -> Result, Error> { let mut expressions = Vec::::new(); while let Some(expression) = self.parse()? { @@ -129,7 +132,7 @@ impl Parser { } /// Parses the input from the tokenizer buffer and returns the resulting expression - pub fn parse(&mut self) -> Result, ParseError> { + pub fn parse(&mut self) -> Result, Error> { self.assign_next()?; let expr = self.expression()?; @@ -141,18 +144,44 @@ impl Parser { } /// Assigns the next token in the tokenizer buffer to the current token - fn assign_next(&mut self) -> Result<(), ParseError> { + fn assign_next(&mut self) -> Result<(), Error> { self.current_token = self.tokenizer.next_token()?; Ok(()) } /// Calls `assign_next` and returns the next token in the tokenizer buffer - fn get_next(&mut self) -> Result, ParseError> { + fn get_next(&mut self) -> Result, Error> { self.assign_next()?; Ok(self.current_token.as_ref()) } - fn expression(&mut self) -> Result, ParseError> { + /// Parses an expression, handling binary operations with correct precedence. + fn expression(&mut self) -> Result, 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, Error> { macro_rules! matches_keyword { ($keyword:expr, $($pattern:pat),+) => { matches!($keyword, $($pattern)|+) @@ -167,12 +196,12 @@ impl Parser { return Ok(None); } - let expr = Some(match current_token.token_type { + let expr = match current_token.token_type { // match unsupported keywords TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => { - return Err(ParseError::UnsupportedKeyword(current_token.clone())); + return Err(Error::UnsupportedKeyword(current_token.clone())); } // match declarations with a `let` keyword @@ -214,30 +243,26 @@ impl Parser { // match priority expressions with a left parenthesis TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?), - _ => { - return Err(ParseError::UnexpectedToken(current_token.clone())); + // match minus symbols to handle negative numbers or negated expressions + TokenType::Symbol(Symbol::Minus) => { + self.assign_next()?; // consume the `-` symbol + // IMPORTANT: We call `unary()` here, NOT `expression()`. + // This ensures negation binds tightly to the operand and doesn't consume binary ops. + // e.g. `-1 + 2` parses as `(-1) + 2` + let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?; + + Expression::Negation(boxed!(inner_expr)) } - }); - 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)) } - fn get_binary_child_node(&mut self) -> Result { + fn get_binary_child_node(&mut self) -> Result { let current_token = token_from_option!(self.current_token); match current_token.token_type { @@ -257,16 +282,23 @@ impl Parser { { self.invocation().map(Expression::Invocation) } - _ => Err(ParseError::UnexpectedToken(current_token.clone())), + // Handle Negation + TokenType::Symbol(Symbol::Minus) => { + self.assign_next()?; + // recurse to handle double negation or simple negation of atoms + let inner = self.get_binary_child_node()?; + Ok(Expression::Negation(boxed!(inner))) + } + _ => Err(Error::UnexpectedToken(current_token.clone())), } } - fn device(&mut self) -> Result { + fn device(&mut self) -> Result { // sanity check, make sure current token is a `device` keyword let current_token = token_from_option!(self.current_token); if !self_matches_current!(self, TokenType::Keyword(Keyword::Device)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let identifier = extract_token_data!( @@ -277,7 +309,7 @@ impl Parser { let current_token = token_from_option!(self.get_next()?).clone(); if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(ParseError::UnexpectedToken(current_token)); + return Err(Error::UnexpectedToken(current_token)); } let device = extract_token_data!( @@ -292,7 +324,7 @@ impl Parser { }) } - fn assignment(&mut self) -> Result { + fn assignment(&mut self) -> Result { let identifier = extract_token_data!( token_from_option!(self.current_token), TokenType::Identifier(ref id), @@ -301,11 +333,11 @@ impl Parser { let current_token = token_from_option!(self.get_next()?).clone(); if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(ParseError::UnexpectedToken(current_token)); + return Err(Error::UnexpectedToken(current_token)); } self.assign_next()?; - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; Ok(AssignmentExpression { identifier, @@ -314,7 +346,7 @@ impl Parser { } /// Handles mathmatical expressions in the explicit order of PEMDAS - fn binary(&mut self, previous: Expression) -> Result { + fn binary(&mut self, previous: Expression) -> Result { // We cannot use recursion here, as we need to handle the precedence of the operators // We need to use a loop to parse the binary expressions. @@ -330,7 +362,7 @@ impl Parser { | Expression::Negation(_) // -1 + 2 => {} _ => { - return Err(ParseError::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation"))) + return Err(Error::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation"))) } } @@ -352,7 +384,7 @@ impl Parser { // validate the vectors and make sure operators.len() == expressions.len() - 1 if operators.len() != expressions.len() - 1 { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( current_token.clone(), String::from("Invalid number of operators"), )); @@ -361,29 +393,26 @@ impl Parser { // Every time we find a valid operator, we pop 2 off the expressions and add one back. // This means that we need to keep track of the current iteration to ensure we are // removing the correct expressions from the vector - let mut current_iteration = 0; // Loop through operators, and build the binary expressions for exponential operators only - for (i, operator) in operators.iter().enumerate() { + for (i, operator) in operators.iter().enumerate().rev() { if operator == &Symbol::Exp { - let index = i - current_iteration; - let left = expressions.remove(index); - let right = expressions.remove(index); + let right = expressions.remove(i + 1); + let left = expressions.remove(i); expressions.insert( - index, + i, Expression::Binary(BinaryExpression::Exponent(boxed!(left), boxed!(right))), ); - current_iteration += 1; } } // remove all the exponential operators from the operators vector operators.retain(|symbol| symbol != &Symbol::Exp); - current_iteration = 0; + let mut current_iteration = 0; // Loop through operators, and build the binary expressions for multiplication and division operators for (i, operator) in operators.iter().enumerate() { - if operator == &Symbol::Asterisk || operator == &Symbol::Slash { + if matches!(operator, Symbol::Slash | Symbol::Asterisk | Symbol::Percent) { let index = i - current_iteration; let left = expressions.remove(index); let right = expressions.remove(index); @@ -397,6 +426,10 @@ impl Parser { index, Expression::Binary(BinaryExpression::Divide(boxed!(left), boxed!(right))), ), + Symbol::Percent => expressions.insert( + index, + Expression::Binary(BinaryExpression::Modulo(boxed!(left), boxed!(right))), + ), // safety: we have already checked for the operator _ => unreachable!(), } @@ -405,7 +438,8 @@ impl Parser { } // remove all the multiplication and division operators from the operators vector - operators.retain(|symbol| symbol != &Symbol::Asterisk && symbol != &Symbol::Slash); + operators + .retain(|symbol| !matches!(symbol, Symbol::Asterisk | Symbol::Percent | Symbol::Slash)); current_iteration = 0; // Loop through operators, and build the binary expressions for addition and subtraction operators @@ -432,11 +466,11 @@ impl Parser { } // remove all the addition and subtraction operators from the operators vector - operators.retain(|symbol| symbol != &Symbol::Plus && symbol != &Symbol::Minus); + operators.retain(|symbol| !matches!(symbol, Symbol::Plus | Symbol::Minus)); // Ensure there is only one expression left in the expressions vector, and no operators left if expressions.len() != 1 || !operators.is_empty() { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( current_token.clone(), String::from("Invalid number of operators"), )); @@ -457,24 +491,24 @@ impl Parser { } } - fn priority(&mut self) -> Result, ParseError> { + fn priority(&mut self) -> Result, Error> { let current_token = token_from_option!(self.current_token); if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } self.assign_next()?; - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::RParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } Ok(boxed!(expression)) } - fn invocation(&mut self) -> Result { + fn invocation(&mut self) -> Result { let identifier = extract_token_data!( token_from_option!(self.current_token), TokenType::Identifier(ref id), @@ -484,7 +518,7 @@ impl Parser { // Ensure the next token is a left parenthesis let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let mut arguments = Vec::::new(); @@ -495,10 +529,10 @@ impl Parser { TokenType::Symbol(Symbol::RParen) ) { let current_token = token_from_option!(self.current_token); - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; if let Expression::Block(_) = expression { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( current_token, String::from("Block expressions are not allowed in function invocations"), )); @@ -510,7 +544,7 @@ impl Parser { if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma)) && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.get_next()?).clone(), )); } @@ -530,20 +564,20 @@ impl Parser { }) } - fn block(&mut self) -> Result { + fn block(&mut self) -> Result { let mut expressions = Vec::::new(); let current_token = token_from_option!(self.current_token); // sanity check: make sure the current token is a left brace if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } while !self_matches_peek!( self, TokenType::Symbol(Symbol::RBrace) | TokenType::Keyword(Keyword::Return) ) { - let expression = self.parse()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.parse()?.ok_or(Error::UnexpectedEOF)?; expressions.push(expression); } @@ -552,7 +586,7 @@ impl Parser { if token_matches!(current_token, TokenType::Keyword(Keyword::Return)) { self.assign_next()?; - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; let return_expr = Expression::Return(boxed!(expression)); expressions.push(return_expr); self.assign_next()?; @@ -563,10 +597,10 @@ impl Parser { Ok(BlockExpression(expressions)) } - fn declaration(&mut self) -> Result { + fn declaration(&mut self) -> Result { let current_token = token_from_option!(self.current_token); if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let identifier = extract_token_data!( token_from_option!(self.get_next()?), @@ -577,16 +611,16 @@ impl Parser { let current_token = token_from_option!(self.get_next()?).clone(); if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } self.assign_next()?; - let assignment_expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let assignment_expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; // make sure the next token is a semi-colon let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::Semicolon)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } Ok(Expression::Declaration( @@ -595,22 +629,22 @@ impl Parser { )) } - fn literal(&mut self) -> Result { + fn literal(&mut self) -> Result { let current_token = token_from_option!(self.current_token); let literal = match current_token.token_type { TokenType::Number(num) => Literal::Number(num), TokenType::String(string) => Literal::String(string), - _ => return Err(ParseError::UnexpectedToken(current_token.clone())), + _ => return Err(Error::UnexpectedToken(current_token.clone())), }; Ok(literal) } - fn function(&mut self) -> Result { + fn function(&mut self) -> Result { let current_token = token_from_option!(self.current_token); // Sanify check that the current token is a `fn` keyword if !self_matches_current!(self, TokenType::Keyword(Keyword::Fn)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let fn_ident = extract_token_data!( @@ -622,7 +656,7 @@ impl Parser { // make sure next token is a left parenthesis let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let mut arguments = Vec::::new(); @@ -638,7 +672,7 @@ impl Parser { extract_token_data!(current_token, TokenType::Identifier(ref id), id.clone()); if arguments.contains(&argument) { - return Err(ParseError::DuplicateIdentifier(current_token.clone())); + return Err(Error::DuplicateIdentifier(current_token.clone())); } arguments.push(argument); @@ -647,7 +681,7 @@ impl Parser { if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma)) && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.get_next()?).clone(), )); } @@ -664,7 +698,7 @@ impl Parser { // make sure the next token is a left brace let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); }; Ok(FunctionExpression { @@ -674,15 +708,15 @@ impl Parser { }) } - fn syscall(&mut self) -> Result { + fn syscall(&mut self) -> Result { /// Checks the length of the arguments and returns an error if the length is not equal to the expected length fn check_length( parser: &Parser, arguments: &[Expression], length: usize, - ) -> Result<(), ParseError> { + ) -> Result<(), Error> { if arguments.len() != length { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( token_from_option!(parser.current_token).clone(), format!("Expected {} arguments", length), )); @@ -698,7 +732,7 @@ impl Parser { } Some(Expression::Variable(ident)) => LiteralOrVariable::Variable(ident.clone()), _ => { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.current_token).clone(), )) } @@ -712,7 +746,7 @@ impl Parser { match $arg { LiteralOrVariable::$matcher(i) => i, _ => { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( token_from_option!(self.current_token).clone(), String::from("Expected a variable"), )) @@ -726,7 +760,10 @@ impl Parser { match invocation.name.as_str() { // system calls - "yield" => Ok(SysCall::System(sys_call::System::Yield)), + "yield" => { + check_length(self, &invocation.arguments, 0)?; + Ok(SysCall::System(sys_call::System::Yield)) + } "sleep" => { check_length(self, &invocation.arguments, 1)?; let mut arg = invocation.arguments.iter(); @@ -740,14 +777,44 @@ impl Parser { let device = literal_or_variable!(args.next()); let Some(Expression::Literal(Literal::String(variable))) = args.next() else { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.current_token).clone(), )); }; Ok(SysCall::System(sys_call::System::LoadFromDevice( device, - variable.clone(), + LiteralOrVariable::Variable(variable.clone()), + ))) + } + "loadBatch" => { + check_length(self, &invocation.arguments, 3)?; + let mut args = invocation.arguments.iter(); + + let device_hash = literal_or_variable!(args.next()); + let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let batch_mode = get_arg!(Literal, literal_or_variable!(args.next())); + + Ok(SysCall::System(sys_call::System::LoadBatch( + device_hash, + logic_type, + batch_mode, + ))) + } + "loadBatchNamed" => { + check_length(self, &invocation.arguments, 4)?; + let mut args = invocation.arguments.iter(); + + let device_hash = literal_or_variable!(args.next()); + let name_hash = get_arg!(Literal, literal_or_variable!(args.next())); + let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let batch_mode = get_arg!(Literal, literal_or_variable!(args.next())); + + Ok(SysCall::System(sys_call::System::LoadBatchNamed( + device_hash, + name_hash, + logic_type, + batch_mode, ))) } "setOnDevice" => { @@ -759,7 +826,7 @@ impl Parser { let Literal::String(logic_type) = get_arg!(Literal, literal_or_variable!(args.next())) else { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.current_token).clone(), )); }; @@ -767,7 +834,9 @@ impl Parser { let variable = literal_or_variable!(args.next()); Ok(SysCall::System(sys_call::System::SetOnDevice( - device, logic_type, variable, + device, + Literal::String(logic_type), + variable, ))) } // math calls @@ -860,134 +929,3 @@ impl Parser { } } } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - - macro_rules! parser { - ($input:expr) => { - Parser::new(Tokenizer::from($input.to_owned())) - }; - } - - #[test] - fn test_unsupported_keywords() -> Result<()> { - let mut parser = parser!("enum x;"); - assert!(parser.parse().is_err()); - - let mut parser = parser!("if x {}"); - assert!(parser.parse().is_err()); - - let mut parser = parser!("else {}"); - assert!(parser.parse().is_err()); - - Ok(()) - } - - #[test] - fn test_declarations() -> Result<()> { - let input = r#" - let x = 5; - // The below line should fail - let y = 234 - "#; - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("(let x = 5)", expression.to_string()); - - assert!(parser.parse().is_err()); - - Ok(()) - } - - #[test] - fn test_block() -> Result<()> { - let input = r#" - { - let x = 5; - let y = 10; - } - "#; - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("{ (let x = 5); (let y = 10); }", expression.to_string()); - - Ok(()) - } - - #[test] - fn test_function_expression() -> Result<()> { - let input = r#" - // This is a function. The parser is starting to get more complex - fn add(x, y) { - let z = x; - } - "#; - - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!( - "(fn add(x, y) { { (let z = x); } })", - expression.to_string() - ); - - Ok(()) - } - - #[test] - fn test_function_invocation() -> Result<()> { - let input = r#" - add(); - "#; - - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("add()", expression.to_string()); - - Ok(()) - } - - #[test] - fn test_priority_expression() -> Result<()> { - let input = r#" - let x = (4); - "#; - - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("(let x = (4))", expression.to_string()); - - Ok(()) - } - - #[test] - fn test_binary_expression() -> Result<()> { - let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap(); - assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string()); - - let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap(); - assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string()); - - let expr = parser!("(5 - 2) * 10").parse()?.unwrap(); - assert_eq!("(((5 - 2)) * 10)", expr.to_string()); - - Ok(()) - } -} diff --git a/libs/parser/src/sys_call.rs b/libs/parser/src/sys_call.rs index eec5ade..cb306ca 100644 --- a/libs/parser/src/sys_call.rs +++ b/libs/parser/src/sys_call.rs @@ -1,3 +1,5 @@ +use crate::tree_node::Literal; + use super::LiteralOrVariable; #[derive(Debug, PartialEq, Eq)] @@ -111,13 +113,27 @@ pub enum System { /// ## Examples /// `l r0 d0 Setting` /// `l r1 d5 Pressure` - LoadFromDevice(LiteralOrVariable, String), + LoadFromDevice(LiteralOrVariable, LiteralOrVariable), + /// Function which gets a LogicType from all connected network devices that match + /// the provided device hash and name, aggregating them via a batchMode + /// ## In Game + /// lbn r? deviceHash nameHash logicType batchMode + /// ## Examples + /// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum + LoadBatchNamed(LiteralOrVariable, Literal, Literal, Literal), + /// Loads a LogicType from all connected network devices, aggregating them via a + /// batchMode + /// ## In Game + /// lb r? deviceHash loggicType batchMode + /// ## Examples + /// lb r0 HASH("StructureWallLight") On Minimum + LoadBatch(LiteralOrVariable, Literal, Literal), /// Represents a function which stores a setting into a specific device. /// ## In Game /// `s d? logicType r?` /// ## Example /// `s d0 Setting r0` - SetOnDevice(LiteralOrVariable, String, LiteralOrVariable), + SetOnDevice(LiteralOrVariable, Literal, LiteralOrVariable), } impl std::fmt::Display for System { @@ -127,6 +143,10 @@ impl std::fmt::Display for System { System::Sleep(a) => write!(f, "sleep({})", a), System::Hash(a) => write!(f, "HASH({})", a), System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b), + System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c), + System::LoadBatchNamed(a, b, c, d) => { + write!(f, "loadBatchNamed({}, {}, {}, {})", a, b, c, d) + } System::SetOnDevice(a, b, c) => write!(f, "setOnDevice({}, {}, {})", a, b, c), } } diff --git a/libs/parser/src/test/blocks.rs b/libs/parser/src/test/blocks.rs new file mode 100644 index 0000000..39e06c6 --- /dev/null +++ b/libs/parser/src/test/blocks.rs @@ -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(()) +} diff --git a/libs/parser/src/test/mod.rs b/libs/parser/src/test/mod.rs new file mode 100644 index 0000000..5822da9 --- /dev/null +++ b/libs/parser/src/test/mod.rs @@ -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(()) +} diff --git a/libs/parser/src/tree_node.rs b/libs/parser/src/tree_node.rs index 4cfc112..456a9c4 100644 --- a/libs/parser/src/tree_node.rs +++ b/libs/parser/src/tree_node.rs @@ -23,6 +23,7 @@ pub enum BinaryExpression { Divide(Box, Box), Subtract(Box, Box), Exponent(Box, Box), + Modulo(Box, Box), } impl std::fmt::Display for BinaryExpression { @@ -33,6 +34,7 @@ impl std::fmt::Display for BinaryExpression { BinaryExpression::Divide(l, r) => write!(f, "({} / {})", l, r), BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r), BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r), + BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r), } } } diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index 47b2c17..ad767f6 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -12,7 +12,7 @@ use token::{Keyword, Number, Symbol, Temperature, Token, TokenType}; quick_error! { #[derive(Debug)] - pub enum TokenizerError { + pub enum Error { IOError(err: std::io::Error) { from() display("IO Error: {}", err) @@ -48,7 +48,7 @@ pub struct Tokenizer { } impl Tokenizer { - pub fn from_path(input_file: impl Into) -> Result { + pub fn from_path(input_file: impl Into) -> Result { let file = std::fs::File::open(input_file.into())?; let reader = BufReader::new(Box::new(file) as Box); @@ -83,7 +83,7 @@ impl Tokenizer { /// /// # Important /// This function will increment the line and column counters - fn next_char(&mut self) -> Result, TokenizerError> { + fn next_char(&mut self) -> Result, Error> { let bytes_read = self.reader.read(&mut self.char_buffer)?; if bytes_read == 0 { @@ -106,7 +106,7 @@ impl Tokenizer { /// /// # Important /// This does not increment the line or column counters - fn peek_next_char(&mut self) -> Result, TokenizerError> { + fn peek_next_char(&mut self) -> Result, Error> { let current_pos = self.reader.stream_position()?; let to_return = if self.reader.read(&mut self.char_buffer)? == 0 { @@ -126,7 +126,7 @@ impl Tokenizer { /// /// # Important /// This function will increment the line and column counters - fn skip_line(&mut self) -> Result<(), TokenizerError> { + fn skip_line(&mut self) -> Result<(), Error> { while let Some(next_char) = self.next_char()? { if next_char == '\n' { break; @@ -137,7 +137,7 @@ impl Tokenizer { /// Consumes the tokenizer and returns the next token in the stream /// If there are no more tokens in the stream, this function returns None - pub fn next_token(&mut self) -> Result, TokenizerError> { + pub fn next_token(&mut self) -> Result, Error> { while let Some(next_char) = self.next_char()? { // skip whitespace if next_char.is_whitespace() { @@ -165,11 +165,7 @@ impl Tokenizer { return self.tokenize_keyword_or_identifier(next_char).map(Some); } _ => { - return Err(TokenizerError::UnknownSymbolError( - next_char, - self.line, - self.column, - )); + return Err(Error::UnknownSymbolError(next_char, self.line, self.column)); } } } @@ -183,7 +179,7 @@ impl Tokenizer { /// Peeks the next token in the stream without consuming it /// If there are no more tokens in the stream, this function returns None - pub fn peek_next(&mut self) -> Result, TokenizerError> { + pub fn peek_next(&mut self) -> Result, Error> { let current_pos = self.reader.stream_position()?; let column = self.column; let line = self.line; @@ -196,7 +192,7 @@ impl Tokenizer { } /// Tokenizes a symbol - fn tokenize_symbol(&mut self, first_symbol: char) -> Result { + fn tokenize_symbol(&mut self, first_symbol: char) -> Result { /// Helper macro to create a symbol token macro_rules! symbol { ($symbol:ident) => { @@ -225,6 +221,7 @@ impl Tokenizer { '.' => symbol!(Dot), '^' => symbol!(Caret), + '%' => symbol!(Percent), // multi-character symbols '<' if self.peek_next_char()? == Some('=') => { @@ -266,7 +263,7 @@ impl Tokenizer { symbol!(LogicalOr) } - _ => Err(TokenizerError::UnknownSymbolError( + _ => Err(Error::UnknownSymbolError( first_symbol, self.line, self.column, @@ -275,7 +272,7 @@ impl Tokenizer { } /// Tokenizes a number literal. Also handles temperatures with a suffix of `c`, `f`, or `k`. - fn tokenize_number(&mut self, first_char: char) -> Result { + fn tokenize_number(&mut self, first_char: char) -> Result { let mut primary = String::with_capacity(16); let mut decimal: Option = None; let mut reading_decimal = false; @@ -319,16 +316,16 @@ impl Tokenizer { let decimal_scale = decimal.len() as u32; let number = format!("{}{}", primary, decimal) .parse::() - .map_err(|e| TokenizerError::NumberParseError(e, self.line, self.column))?; + .map_err(|e| Error::NumberParseError(e, self.line, self.column))?; Number::Decimal( Decimal::try_from_i128_with_scale(number, decimal_scale) - .map_err(|e| TokenizerError::DecimalParseError(e, line, column))?, + .map_err(|e| Error::DecimalParseError(e, line, column))?, ) } else { Number::Integer( primary .parse() - .map_err(|e| TokenizerError::NumberParseError(e, line, column))?, + .map_err(|e| Error::NumberParseError(e, line, column))?, ) }; @@ -350,7 +347,7 @@ impl Tokenizer { } /// Tokenizes a string literal - fn tokenize_string(&mut self, beginning_quote: char) -> Result { + fn tokenize_string(&mut self, beginning_quote: char) -> Result { let mut buffer = String::with_capacity(16); let column = self.column; @@ -368,10 +365,7 @@ impl Tokenizer { } /// Tokenizes a keyword or an identifier. Also handles boolean literals - fn tokenize_keyword_or_identifier( - &mut self, - first_char: char, - ) -> Result { + fn tokenize_keyword_or_identifier(&mut self, first_char: char) -> Result { macro_rules! keyword { ($keyword:ident) => {{ return Ok(Token::new( @@ -441,9 +435,7 @@ impl Tokenizer { looped_char = self.next_char()?; } - Err(TokenizerError::UnknownKeywordOrIdentifierError( - buffer, line, column, - )) + Err(Error::UnknownKeywordOrIdentifierError(buffer, line, column)) } } @@ -464,7 +456,7 @@ impl TokenizerBuffer { /// Reads the next token from the tokenizer, pushing the value to the back of the history /// and returning the token - pub fn next_token(&mut self) -> Result, TokenizerError> { + pub fn next_token(&mut self) -> Result, Error> { if let Some(token) = self.buffer.pop_front() { self.history.push_back(token.clone()); return Ok(Some(token)); @@ -478,7 +470,7 @@ impl TokenizerBuffer { } /// Peeks the next token in the stream without adding to the history stack - pub fn peek(&mut self) -> Result, TokenizerError> { + pub fn peek(&mut self) -> Result, Error> { if let Some(token) = self.buffer.front() { return Ok(Some(token.clone())); } @@ -487,7 +479,7 @@ impl TokenizerBuffer { Ok(token) } - fn seek_from_current(&mut self, seek_to: i64) -> Result<(), TokenizerError> { + fn seek_from_current(&mut self, seek_to: i64) -> Result<(), Error> { use Ordering::*; // if seek_to > 0 then we need to check if the buffer has enough tokens to pop, otherwise we need to read from the tokenizer // if seek_to < 0 then we need to pop from the history and push to the front of the buffer. If not enough, then we throw (we reached the front of the history) @@ -500,7 +492,7 @@ impl TokenizerBuffer { if let Some(token) = self.tokenizer.next_token()? { tokens.push(token); } else { - return Err(TokenizerError::IOError(std::io::Error::new( + return Err(Error::IOError(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "Unexpected EOF", ))); @@ -515,7 +507,7 @@ impl TokenizerBuffer { if let Some(token) = self.history.pop_back() { tokens.push(token); } else { - return Err(TokenizerError::IOError(std::io::Error::new( + return Err(Error::IOError(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "Unexpected EOF", ))); @@ -530,7 +522,7 @@ impl TokenizerBuffer { } /// Adds to or removes from the History stack, allowing the user to move back and forth in the stream - pub fn seek(&mut self, from: SeekFrom) -> Result<(), TokenizerError> { + pub fn seek(&mut self, from: SeekFrom) -> Result<(), Error> { match from { SeekFrom::Current(seek_to) => self.seek_from_current(seek_to)?, SeekFrom::End(_) => unimplemented!("SeekFrom::End will not be implemented"), @@ -745,7 +737,7 @@ mod tests { #[test] fn test_symbol_parse() -> Result<()> { let mut tokenizer = Tokenizer::from(String::from( - "^ ! () [] {} , . ; : + - * / < > = != && || >= <=**", + "^ ! () [] {} , . ; : + - * / < > = != && || >= <=**%", )); let expected_tokens = vec![ @@ -774,6 +766,7 @@ mod tests { TokenType::Symbol(Symbol::GreaterThanOrEqual), TokenType::Symbol(Symbol::LessThanOrEqual), TokenType::Symbol(Symbol::Exp), + TokenType::Symbol(Symbol::Percent), ]; for expected_token in expected_tokens { diff --git a/libs/tokenizer/src/token.rs b/libs/tokenizer/src/token.rs index 9e31ca5..93a090c 100644 --- a/libs/tokenizer/src/token.rs +++ b/libs/tokenizer/src/token.rs @@ -111,6 +111,12 @@ impl std::fmt::Display for Number { } } +impl std::convert::From for String { + fn from(value: Number) -> Self { + value.to_string() + } +} + #[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)] pub enum Symbol { // Single Character Symbols @@ -152,6 +158,8 @@ pub enum Symbol { Dot, /// Represents the `^` symbol Caret, + /// Represents the `%` symbol + Percent, // Double Character Symbols /// Represents the `==` symbol @@ -174,7 +182,12 @@ impl Symbol { pub fn is_operator(&self) -> bool { matches!( self, - Symbol::Plus | Symbol::Minus | Symbol::Asterisk | Symbol::Slash | Symbol::Exp + Symbol::Plus + | Symbol::Minus + | Symbol::Asterisk + | Symbol::Slash + | Symbol::Exp + | Symbol::Percent ) } diff --git a/src/lib.rs b/src/lib.rs index c023443..68a5650 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,14 +9,12 @@ fn compile_from_string( input: &safer_ffi::string::String, output: &mut safer_ffi::string::String, ) -> bool { - let buffer = Vec::::new(); - - let mut writer = BufWriter::new(buffer); + let mut writer = BufWriter::new(Vec::new()); let tokenizer = Tokenizer::from(input.to_string()); let parser = Parser::new(tokenizer); - let compiler = Compiler::new(parser, &mut writer); + let compiler = Compiler::new(parser, &mut writer, None); let Ok(()) = compiler.compile() else { return false; diff --git a/src/main.rs b/src/main.rs index a549747..f93734a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,20 +9,20 @@ use std::{ io::{BufWriter, Read, Write}, path::PathBuf, }; -use tokenizer::{Tokenizer, TokenizerError}; +use tokenizer::{self, Tokenizer}; quick_error! { #[derive(Debug)] enum StationlangError { - TokenizerError(err: TokenizerError) { + TokenizerError(err: tokenizer::Error) { from() display("Tokenizer error: {}", err) } - ParserError(err: parser::ParseError) { + ParserError(err: parser::Error) { from() display("Parser error: {}", err) } - CompileError(err: compiler::CompileError) { + CompileError(err: compiler::Error) { from() display("Compile error: {}", err) } @@ -71,7 +71,7 @@ fn run_logic() -> Result<(), StationlangError> { None => BufWriter::new(Box::new(std::io::stdout())), }; - let compiler = Compiler::new(parser, &mut writer); + let compiler = Compiler::new(parser, &mut writer, None); compiler.compile()?; writer.flush()?;