From 9993bff574f056645188c944d894c19b7caa6229 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 21:34:51 -0700 Subject: [PATCH 1/6] Added support for compile-time constant hash expressions --- rust_compiler/Cargo.lock | 10 ++++ rust_compiler/Cargo.toml | 5 +- rust_compiler/libs/compiler/Cargo.toml | 1 + .../compiler/src/test/declaration_literal.rs | 22 +++++++++ rust_compiler/libs/compiler/src/v1.rs | 30 ++++++++++-- rust_compiler/libs/parser/src/lib.rs | 48 +++++++++++++++---- rust_compiler/libs/parser/src/test/mod.rs | 7 +++ rust_compiler/libs/parser/src/tree_node.rs | 26 +++++++++- rust_compiler/libs/tokenizer/src/lib.rs | 20 ++++++-- rust_compiler/libs/tokenizer/src/token.rs | 2 +- 10 files changed, 150 insertions(+), 21 deletions(-) diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index f12b00b..d6d7361 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -252,6 +252,7 @@ name = "compiler" version = "0.1.0" dependencies = [ "anyhow", + "crc32fast", "indoc", "lsp-types", "parser", @@ -260,6 +261,15 @@ dependencies = [ "tokenizer", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "diff" version = "0.1.13" diff --git a/rust_compiler/Cargo.toml b/rust_compiler/Cargo.toml index c0b0744..ee900f1 100644 --- a/rust_compiler/Cargo.toml +++ b/rust_compiler/Cargo.toml @@ -9,8 +9,9 @@ members = ["libs/*"] [workspace.dependencies] quick-error = "2" rust_decimal = "1" -safer-ffi = { version = "0.1" } -lsp-types = { version = "0.97" } +safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust +lsp-types = { version = "0.97" } # Allows for LSP style reporting to the frontend +crc32fast = "1.5" # This is for `HASH(..)` calls to be optimized away [features] headers = ["safer-ffi/headers"] diff --git a/rust_compiler/libs/compiler/Cargo.toml b/rust_compiler/libs/compiler/Cargo.toml index a820e20..940ba19 100644 --- a/rust_compiler/libs/compiler/Cargo.toml +++ b/rust_compiler/libs/compiler/Cargo.toml @@ -8,6 +8,7 @@ quick-error = { workspace = true } parser = { path = "../parser" } tokenizer = { path = "../tokenizer" } lsp-types = { workspace = true } +crc32fast = { workspace = true } [dev-dependencies] anyhow = { version = "1.0" } diff --git a/rust_compiler/libs/compiler/src/test/declaration_literal.rs b/rust_compiler/libs/compiler/src/test/declaration_literal.rs index c42624c..984bf63 100644 --- a/rust_compiler/libs/compiler/src/test/declaration_literal.rs +++ b/rust_compiler/libs/compiler/src/test/declaration_literal.rs @@ -146,3 +146,25 @@ fn test_boolean_return() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_const_hash_expr() -> anyhow::Result<()> { + let compiled = compile!(debug r#" + const nameHash = hash("AccessCard"); + device self = "db"; + + self.Setting = nameHash; + "#); + + assert_eq!( + compiled, + indoc! { + " + j main + main: + s db Setting -732925934 + " + } + ); + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 6633554..7f5b553 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -1,13 +1,14 @@ #![allow(clippy::result_large_err)] use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; +use crc32fast::hash as crc32_hash; use parser::{ Parser as ASTParser, sys_call::{SysCall, System}, tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, - InvocationExpression, Literal, LiteralOrVariable, LogicalExpression, LoopExpression, - MemberAccessExpression, Span, Spanned, WhileExpression, + InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression, + LoopExpression, MemberAccessExpression, Span, Spanned, WhileExpression, }, }; use quick_error::quick_error; @@ -15,6 +16,7 @@ use std::{ collections::HashMap, io::{BufWriter, Write}, }; +use tokenizer::token::Number; macro_rules! debug { ($self: expr, $debug_value: expr) => { @@ -679,8 +681,30 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { value: const_value, } = expr; + // check for a hash expression or a literal + let value = match const_value { + LiteralOr::Or(Spanned { + node: SysCall::System(System::Hash(Literal::String(str_to_hash))), + .. + }) => { + let hash = crc32_hash(str_to_hash.as_bytes()); + + // in stationeers, crc32 is a SIGNED int. + let hash_value_i32 = i32::from_le_bytes(hash.to_le_bytes()); + + Literal::Number(Number::Integer(hash_value_i32 as i128)) + } + LiteralOr::Or(Spanned { span, .. }) => { + return Err(Error::Unknown( + "hash only supports string literals in this context.".into(), + Some(span), + )); + } + LiteralOr::Literal(Spanned { node, .. }) => node, + }; + Ok(CompilationResult { - location: scope.define_const(const_name.node, const_value.node)?, + location: scope.define_const(const_name.node, value)?, temp_name: None, }) } diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index dd9f582..e2f5690 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -1256,17 +1256,47 @@ impl<'a> Parser<'a> { )); } - // literal value + // literal or syscall, making sure the syscall is supported in hash self.assign_next()?; - let lit = self.spanned(|p| p.literal())?; + // cache the current token location + let current_token_index = self.tokenizer.loc(); - Ok(ConstDeclarationExpression { - name: Spanned { - span: ident_span, - node: ident, - }, - value: lit, - }) + if let Ok(lit) = self.spanned(|p| p.literal()) { + Ok(ConstDeclarationExpression { + name: Spanned { + span: ident_span, + node: ident, + }, + value: LiteralOr::Literal(lit), + }) + } else { + // we need to rewind our tokenizer to our previous location + self.tokenizer.seek(SeekFrom::Current( + self.tokenizer.loc() - current_token_index, + ))?; + let syscall = self.spanned(|p| p.syscall())?; + + if !matches!( + syscall, + Spanned { + node: SysCall::System(sys_call::System::Hash(_)), + .. + } + ) { + return Err(Error::UnexpectedToken( + syscall.span, + self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + )); + } + + Ok(ConstDeclarationExpression { + name: Spanned { + span: ident_span, + node: ident, + }, + value: LiteralOr::Or(syscall), + }) + } } fn declaration(&mut self) -> Result { diff --git a/rust_compiler/libs/parser/src/test/mod.rs b/rust_compiler/libs/parser/src/test/mod.rs index beb874c..a0fd10d 100644 --- a/rust_compiler/libs/parser/src/test/mod.rs +++ b/rust_compiler/libs/parser/src/test/mod.rs @@ -144,3 +144,10 @@ fn test_binary_expression() -> Result<()> { Ok(()) } + +#[test] +fn test_const_hash_expression() -> Result<()> { + let expr = parser!(r#"const i = hash("item")"#).parse()?.unwrap(); + assert_eq!("(const i = hash(\"item\"))", expr.to_string()); + Ok(()) +} diff --git a/rust_compiler/libs/parser/src/tree_node.rs b/rust_compiler/libs/parser/src/tree_node.rs index daca733..350e4e6 100644 --- a/rust_compiler/libs/parser/src/tree_node.rs +++ b/rust_compiler/libs/parser/src/tree_node.rs @@ -1,5 +1,7 @@ use std::ops::Deref; +use crate::sys_call; + use super::sys_call::SysCall; use tokenizer::token::Number; @@ -10,6 +12,21 @@ pub enum Literal { Boolean(bool), } +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum LiteralOr { + Literal(Spanned), + Or(Spanned), +} + +impl std::fmt::Display for LiteralOr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Literal(l) => write!(f, "{l}"), + Self::Or(o) => write!(f, "{o}"), + } + } +} + impl std::fmt::Display for Literal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -198,7 +215,14 @@ impl std::fmt::Display for LiteralOrVariable { #[derive(Debug, PartialEq, Eq)] pub struct ConstDeclarationExpression { pub name: Spanned, - pub value: Spanned, + pub value: LiteralOr, +} + +impl ConstDeclarationExpression { + pub fn is_syscall_supported(call: &SysCall) -> bool { + use sys_call::System; + matches!(call, SysCall::System(sys) if matches!(sys, System::Hash(_))) + } } impl std::fmt::Display for ConstDeclarationExpression { diff --git a/rust_compiler/libs/tokenizer/src/lib.rs b/rust_compiler/libs/tokenizer/src/lib.rs index b73c514..3d8dabb 100644 --- a/rust_compiler/libs/tokenizer/src/lib.rs +++ b/rust_compiler/libs/tokenizer/src/lib.rs @@ -519,6 +519,7 @@ pub struct TokenizerBuffer<'a> { tokenizer: Tokenizer<'a>, buffer: VecDeque, history: VecDeque, + index: i64, } impl<'a> TokenizerBuffer<'a> { @@ -527,17 +528,22 @@ impl<'a> TokenizerBuffer<'a> { tokenizer, buffer: VecDeque::new(), history: VecDeque::with_capacity(128), + index: 0, } } pub fn next_token(&mut self) -> Result, Error> { if let Some(token) = self.buffer.pop_front() { self.history.push_back(token.clone()); + self.index += 1; return Ok(Some(token)); } let token = self.tokenizer.next_token()?; + if let Some(ref token) = token { self.history.push_back(token.clone()); } + + self.index += 1; Ok(token) } pub fn peek(&mut self) -> Result, Error> { @@ -547,12 +553,15 @@ impl<'a> TokenizerBuffer<'a> { let token = self.tokenizer.peek_next()?; Ok(token) } - fn seek_from_current(&mut self, seek_to: i64) -> Result<(), Error> { + pub fn loc(&self) -> i64 { + self.index + } + fn seek_from_current(&mut self, seek_to_int: i64) -> Result<(), Error> { use Ordering::*; - match seek_to.cmp(&0) { + match seek_to_int.cmp(&0) { Greater => { - let mut tokens = Vec::with_capacity(seek_to as usize); - for _ in 0..seek_to { + let mut tokens = Vec::with_capacity(seek_to_int as usize); + for _ in 0..seek_to_int { if let Some(token) = self.tokenizer.next_token()? { tokens.push(token); } else { @@ -565,7 +574,7 @@ impl<'a> TokenizerBuffer<'a> { self.history.extend(tokens); } Less => { - let seek_to = seek_to.unsigned_abs() as usize; + let seek_to = seek_to_int.unsigned_abs() as usize; let mut tokens = Vec::with_capacity(seek_to); for _ in 0..seek_to { if let Some(token) = self.history.pop_back() { @@ -577,6 +586,7 @@ impl<'a> TokenizerBuffer<'a> { ))); } } + self.index -= seek_to_int; self.buffer.extend(tokens.into_iter().rev()); } _ => {} diff --git a/rust_compiler/libs/tokenizer/src/token.rs b/rust_compiler/libs/tokenizer/src/token.rs index 3befb9f..7637bfe 100644 --- a/rust_compiler/libs/tokenizer/src/token.rs +++ b/rust_compiler/libs/tokenizer/src/token.rs @@ -168,7 +168,7 @@ impl std::fmt::Display for TokenType { #[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)] pub enum Number { /// Represents an integer number - Integer(u128), + Integer(i128), /// Represents a decimal type number with a precision of 64 bits Decimal(Decimal), } From a60e9d7dce3438c67f159ed8c7b7df516fa71b73 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 23:25:23 -0700 Subject: [PATCH 2/6] fold nested literal binary expressions --- rust_compiler/Cargo.lock | 6 +- rust_compiler/libs/compiler/Cargo.toml | 3 +- .../compiler/src/test/binary_expression.rs | 12 +-- .../compiler/src/test/logic_expression.rs | 5 +- .../libs/compiler/src/test/syscall.rs | 30 +----- rust_compiler/libs/compiler/src/v1.rs | 91 +++++++++++++++---- rust_compiler/libs/helpers/Cargo.toml | 1 + .../libs/helpers/src/helper_funcs.rs | 11 +++ rust_compiler/libs/helpers/src/lib.rs | 2 + rust_compiler/libs/tokenizer/src/token.rs | 75 +++++++++++++++ 10 files changed, 176 insertions(+), 60 deletions(-) create mode 100644 rust_compiler/libs/helpers/src/helper_funcs.rs diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index d6d7361..664669d 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -252,12 +252,13 @@ name = "compiler" version = "0.1.0" dependencies = [ "anyhow", - "crc32fast", + "helpers", "indoc", "lsp-types", "parser", "pretty_assertions", "quick-error", + "rust_decimal", "tokenizer", ] @@ -373,6 +374,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helpers" version = "0.1.0" +dependencies = [ + "crc32fast", +] [[package]] name = "indexmap" diff --git a/rust_compiler/libs/compiler/Cargo.toml b/rust_compiler/libs/compiler/Cargo.toml index 940ba19..a21718c 100644 --- a/rust_compiler/libs/compiler/Cargo.toml +++ b/rust_compiler/libs/compiler/Cargo.toml @@ -7,8 +7,9 @@ edition = "2024" quick-error = { workspace = true } parser = { path = "../parser" } tokenizer = { path = "../tokenizer" } +helpers = { path = "../helpers" } lsp-types = { workspace = true } -crc32fast = { workspace = true } +rust_decimal = { workspace = true } [dev-dependencies] anyhow = { version = "1.0" } diff --git a/rust_compiler/libs/compiler/src/test/binary_expression.rs b/rust_compiler/libs/compiler/src/test/binary_expression.rs index 8f890d4..8254eaa 100644 --- a/rust_compiler/libs/compiler/src/test/binary_expression.rs +++ b/rust_compiler/libs/compiler/src/test/binary_expression.rs @@ -17,8 +17,7 @@ fn simple_binary_expression() -> anyhow::Result<()> { " j main main: - add r1 1 2 - move r8 r1 #i + move r8 3 #i " } ); @@ -72,7 +71,7 @@ fn nested_binary_expressions() -> anyhow::Result<()> { } #[test] -fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> { +fn stress_test_constant_folding() -> anyhow::Result<()> { let compiled = compile! { debug " @@ -86,12 +85,7 @@ fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> { " 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 + move r8 -123 #negationHell " } ); diff --git a/rust_compiler/libs/compiler/src/test/logic_expression.rs b/rust_compiler/libs/compiler/src/test/logic_expression.rs index 55bdda6..1f65d4b 100644 --- a/rust_compiler/libs/compiler/src/test/logic_expression.rs +++ b/rust_compiler/libs/compiler/src/test/logic_expression.rs @@ -112,9 +112,8 @@ fn test_math_with_logic() -> anyhow::Result<()> { " j main main: - add r1 1 2 - sgt r2 r1 1 - move r8 r2 #logic + sgt r1 3 1 + move r8 r1 #logic " } ); diff --git a/rust_compiler/libs/compiler/src/test/syscall.rs b/rust_compiler/libs/compiler/src/test/syscall.rs index 7433880..ec323e5 100644 --- a/rust_compiler/libs/compiler/src/test/syscall.rs +++ b/rust_compiler/libs/compiler/src/test/syscall.rs @@ -88,7 +88,7 @@ fn test_set_on_device_batched() -> anyhow::Result<()> { let compiled = compile! { debug r#" - let doorHash = hash("Door"); + const doorHash = hash("Door"); setOnDeviceBatched(doorHash, "Lock", true); "# }; @@ -99,9 +99,7 @@ fn test_set_on_device_batched() -> anyhow::Result<()> { r#" j main main: - move r15 HASH("Door") #hash_ret - move r8 r15 #doorHash - sb r8 Lock 1 + sb 718797587 Lock 1 "# } ); @@ -133,27 +131,3 @@ fn test_load_from_device() -> anyhow::Result<()> { Ok(()) } - -#[test] -fn test_hash() -> anyhow::Result<()> { - let compiled = compile! { - debug - r#" - let nameHash = hash("testValue"); - "# - }; - - assert_eq!( - compiled, - indoc! { - r#" - j main - main: - move r15 HASH("testValue") #hash_ret - move r8 r15 #nameHash - "# - } - ); - - Ok(()) -} diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 7f5b553..236e20f 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -1,6 +1,6 @@ #![allow(clippy::result_large_err)] use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; -use crc32fast::hash as crc32_hash; +use helpers::prelude::*; use parser::{ Parser as ASTParser, sys_call::{SysCall, System}, @@ -559,15 +559,24 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let result = self.expression_binary(bin_expr, scope)?; let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?; - // Move result from temp to new persistent variable - let result_reg = self.resolve_register(&result.location)?; - self.emit_variable_assignment(&name_str, &var_loc, result_reg)?; + if let CompilationResult { + location: VariableLocation::Constant(Literal::Number(num)), + .. + } = result + { + self.emit_variable_assignment(&name_str, &var_loc, num)?; + (var_loc, None) + } else { + // Move result from temp to new persistent variable + let result_reg = self.resolve_register(&result.location)?; + self.emit_variable_assignment(&name_str, &var_loc, result_reg)?; - // Free the temp result - if let Some(name) = result.temp_name { - scope.free_temp(name)?; + // Free the temp result + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + (var_loc, None) } - (var_loc, None) } Expression::Logical(log_expr) => { let result = self.expression_logical(log_expr, scope)?; @@ -686,14 +695,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { LiteralOr::Or(Spanned { node: SysCall::System(System::Hash(Literal::String(str_to_hash))), .. - }) => { - let hash = crc32_hash(str_to_hash.as_bytes()); - - // in stationeers, crc32 is a SIGNED int. - let hash_value_i32 = i32::from_le_bytes(hash.to_le_bytes()); - - Literal::Number(Number::Integer(hash_value_i32 as i128)) - } + }) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash))), LiteralOr::Or(Spanned { span, .. }) => { return Err(Error::Unknown( "hash only supports string literals in this context.".into(), @@ -1232,6 +1234,58 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { expr: Spanned, scope: &mut VariableScope<'v>, ) -> Result { + fn fold_binary_expression(expr: &BinaryExpression) -> Option { + let (lhs, rhs) = match &expr { + BinaryExpression::Add(l, r) + | BinaryExpression::Subtract(l, r) + | BinaryExpression::Multiply(l, r) + | BinaryExpression::Divide(l, r) + | BinaryExpression::Exponent(l, r) + | BinaryExpression::Modulo(l, r) => (fold_expression(l)?, fold_expression(r)?), + }; + + match expr { + BinaryExpression::Add(..) => Some(lhs + rhs), + BinaryExpression::Subtract(..) => Some(lhs - rhs), + BinaryExpression::Multiply(..) => Some(lhs * rhs), + BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics! + BinaryExpression::Modulo(..) => Some(lhs % rhs), + _ => None, // Handle Exponent separately or implement pow + } + } + + fn fold_expression(expr: &Expression) -> Option { + match expr { + // 1. Base Case: It's already a number + Expression::Literal(lit) => match lit.node { + Literal::Number(n) => Some(n), + _ => None, + }, + + // 2. Handle Parentheses: Just recurse deeper + Expression::Priority(inner) => fold_expression(&inner.node), + + // 3. Handle Negation: Recurse, then negate + Expression::Negation(inner) => { + let val = fold_expression(&inner.node)?; + Some(-val) // Requires impl Neg for Number + } + + // 4. Handle Binary Ops: Recurse BOTH sides, then combine + Expression::Binary(bin) => fold_binary_expression(&bin.node), + + // 5. Anything else (Variables, Function Calls) cannot be compile-time folded + _ => None, + } + } + + if let Some(const_lit) = fold_binary_expression(&expr.node) { + return Ok(CompilationResult { + location: VariableLocation::Constant(Literal::Number(const_lit)), + temp_name: None, + }); + }; + let (op_str, left_expr, right_expr) = match expr.node { BinaryExpression::Add(l, r) => ("add", l, r), BinaryExpression::Multiply(l, r) => ("mul", l, r), @@ -1553,8 +1607,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { )); }; - let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER); - self.emit_variable_assignment("hash_ret", &loc, format!(r#"HASH("{}")"#, str_lit))?; + let loc = VariableLocation::Constant(Literal::Number(Number::Integer( + crc_hash_signed(&str_lit), + ))); Ok(Some(CompilationResult { location: loc, diff --git a/rust_compiler/libs/helpers/Cargo.toml b/rust_compiler/libs/helpers/Cargo.toml index cb9df2c..97e7548 100644 --- a/rust_compiler/libs/helpers/Cargo.toml +++ b/rust_compiler/libs/helpers/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +crc32fast = { workspace = true } diff --git a/rust_compiler/libs/helpers/src/helper_funcs.rs b/rust_compiler/libs/helpers/src/helper_funcs.rs new file mode 100644 index 0000000..3e68894 --- /dev/null +++ b/rust_compiler/libs/helpers/src/helper_funcs.rs @@ -0,0 +1,11 @@ +use crc32fast::hash as crc32_hash; +/// This function takes an input which is meant to be hashed via the CRC32 algorithm, but it then +/// converts the generated UNSIGNED number into it's SIGNED counterpart. +pub fn crc_hash_signed(input: &str) -> i128 { + let hash = crc32_hash(input.as_bytes()); + + // in stationeers, crc32 is a SIGNED int. + let hash_value_i32 = i32::from_le_bytes(hash.to_le_bytes()); + + hash_value_i32 as i128 +} diff --git a/rust_compiler/libs/helpers/src/lib.rs b/rust_compiler/libs/helpers/src/lib.rs index 40b9b5a..680897e 100644 --- a/rust_compiler/libs/helpers/src/lib.rs +++ b/rust_compiler/libs/helpers/src/lib.rs @@ -1,3 +1,4 @@ +mod helper_funcs; mod macros; mod syscall; @@ -11,5 +12,6 @@ pub trait Documentation { } pub mod prelude { + pub use super::helper_funcs::*; pub use super::{Documentation, documented, with_syscalls}; } diff --git a/rust_compiler/libs/tokenizer/src/token.rs b/rust_compiler/libs/tokenizer/src/token.rs index 7637bfe..9745ecd 100644 --- a/rust_compiler/libs/tokenizer/src/token.rs +++ b/rust_compiler/libs/tokenizer/src/token.rs @@ -173,6 +173,81 @@ pub enum Number { Decimal(Decimal), } +impl From for Decimal { + fn from(value: Number) -> Self { + match value { + Number::Decimal(d) => d, + Number::Integer(i) => Decimal::from(i), + } + } +} + +impl std::ops::Neg for Number { + type Output = Number; + + fn neg(self) -> Self::Output { + match self { + Self::Integer(i) => Self::Integer(-i), + Self::Decimal(d) => Self::Decimal(-d), + } + } +} + +impl std::ops::Add for Number { + type Output = Number; + + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(l), Self::Integer(r)) => Number::Integer(l + r), + (Self::Decimal(l), Self::Decimal(r)) => Number::Decimal(l + r), + (Self::Integer(l), Self::Decimal(r)) => Number::Decimal(Decimal::from(l) + r), + (Self::Decimal(l), Self::Integer(r)) => Number::Decimal(l + Decimal::from(r)), + } + } +} + +impl std::ops::Sub for Number { + type Output = Number; + + fn sub(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(l), Self::Integer(r)) => Self::Integer(l - r), + (Self::Decimal(l), Self::Integer(r)) => Self::Decimal(l - Decimal::from(r)), + (Self::Integer(l), Self::Decimal(r)) => Self::Decimal(Decimal::from(l) - r), + (Self::Decimal(l), Self::Decimal(r)) => Self::Decimal(l - r), + } + } +} + +impl std::ops::Mul for Number { + type Output = Number; + + fn mul(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Number::Integer(l), Number::Integer(r)) => Number::Integer(l * r), + (Number::Integer(l), Number::Decimal(r)) => Number::Decimal(Decimal::from(l) * r), + (Number::Decimal(l), Number::Integer(r)) => Number::Decimal(l * Decimal::from(r)), + (Number::Decimal(l), Number::Decimal(r)) => Number::Decimal(l * r), + } + } +} + +impl std::ops::Div for Number { + type Output = Number; + + fn div(self, rhs: Self) -> Self::Output { + Number::Decimal(Decimal::from(self) / Decimal::from(rhs)) + } +} + +impl std::ops::Rem for Number { + type Output = Number; + + fn rem(self, rhs: Self) -> Self::Output { + Number::Decimal(Decimal::from(self) % Decimal::from(rhs)) + } +} + impl std::fmt::Display for Number { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From ee8f5daece3532342c98bee320fe5c4429a3e72b Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 23:39:58 -0700 Subject: [PATCH 3/6] add test for constant folding with temporary variables --- .../compiler/src/test/binary_expression.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/rust_compiler/libs/compiler/src/test/binary_expression.rs b/rust_compiler/libs/compiler/src/test/binary_expression.rs index 8254eaa..757a0e0 100644 --- a/rust_compiler/libs/compiler/src/test/binary_expression.rs +++ b/rust_compiler/libs/compiler/src/test/binary_expression.rs @@ -92,3 +92,31 @@ fn stress_test_constant_folding() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + device self = "db"; + let i = 1 - 3 * (1 + 123.4) * self.Setting + 245c; + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 db Setting + mul r2 373.2 r1 + sub r3 1 r2 + add r4 r3 518.15 + move r8 r4 #i + " + } + ); + + Ok(()) +} From 7fb153572d62fbf3e9ab259738be866bd60c5d85 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 6 Dec 2025 01:19:30 -0700 Subject: [PATCH 4/6] syscall aliases and more syscalls --- .../libs/compiler/src/test/syscall.rs | 30 ++++ rust_compiler/libs/compiler/src/v1.rs | 165 +++++++++++++++--- rust_compiler/libs/helpers/src/syscall.rs | 22 ++- rust_compiler/libs/parser/src/lib.rs | 114 +++++++++--- rust_compiler/libs/parser/src/sys_call.rs | 50 +++--- 5 files changed, 306 insertions(+), 75 deletions(-) diff --git a/rust_compiler/libs/compiler/src/test/syscall.rs b/rust_compiler/libs/compiler/src/test/syscall.rs index ec323e5..a36e672 100644 --- a/rust_compiler/libs/compiler/src/test/syscall.rs +++ b/rust_compiler/libs/compiler/src/test/syscall.rs @@ -106,6 +106,36 @@ fn test_set_on_device_batched() -> anyhow::Result<()> { Ok(()) } +#[test] +fn test_set_on_device_batched_named() -> anyhow::Result<()> { + let compiled = compile! { + result + r#" + device dev = "d0"; + const devName = hash("test"); + + let myVar = lbn(dev, devName, "On", 12); + "# + }; + + println!("{compiled:?}"); + + assert!(compiled.is_empty()); + + // assert_eq!( + // compiled, + // indoc! { + // " + // j main + // main: + // lbn r8 d0 + // " + // } + // ); + + Ok(()) +} + #[test] fn test_load_from_device() -> anyhow::Result<()> { let compiled = compile! { diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 236e20f..0a69f7f 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -693,7 +693,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { // check for a hash expression or a literal let value = match const_value { LiteralOr::Or(Spanned { - node: SysCall::System(System::Hash(Literal::String(str_to_hash))), + node: + SysCall::System(System::Hash(Spanned { + node: Literal::String(str_to_hash), + .. + })), .. }) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash))), LiteralOr::Or(Spanned { span, .. }) => { @@ -1148,6 +1152,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { if let Literal::Boolean(b) = spanned_lit.node { return Ok((if b { "1".to_string() } else { "0".to_string() }, None)); } + if let Literal::String(ref s) = spanned_lit.node { + return Ok((s.to_string(), None)); + } } // Optimization for negated literals used as operands. @@ -1585,22 +1592,34 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { span: Span, scope: &mut VariableScope<'v>, ) -> Result, Error> { + macro_rules! cleanup { + ($($to_clean:expr),*) => { + $( + if let Some(to_clean) = $to_clean { + scope.free_temp(to_clean)?; + } + )* + }; + } match expr { System::Yield => { self.write_output("yield")?; Ok(None) } System::Sleep(amt) => { - let (var, cleanup) = self.compile_operand(*amt, scope)?; + let (var, var_cleanup) = self.compile_operand(*amt, scope)?; self.write_output(format!("sleep {var}"))?; - if let Some(temp) = cleanup { - scope.free_temp(temp)?; - } + + cleanup!(var_cleanup); Ok(None) } System::Hash(hash_arg) => { - let Literal::String(str_lit) = hash_arg else { + let Spanned { + node: Literal::String(str_lit), + .. + } = hash_arg + else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a string literal.".into(), span, @@ -1619,7 +1638,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { System::SetOnDevice(device, logic_type, variable) => { let (variable, var_cleanup) = self.compile_operand(*variable, scope)?; - let LiteralOrVariable::Variable(device_spanned) = device else { + let Spanned { + node: LiteralOrVariable::Variable(device_spanned), + .. + } = device + else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a variable".into(), span, @@ -1641,7 +1664,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { .cloned() .unwrap_or("d0".to_string()); - let Literal::String(logic_type) = logic_type else { + let Spanned { + node: Literal::String(logic_type), + .. + } = logic_type + else { return Err(Error::AgrumentMismatch( "Arg2 expected to be a string".into(), span, @@ -1650,17 +1677,19 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.write_output(format!("s {} {} {}", device_val, logic_type, variable))?; - if let Some(temp_var) = var_cleanup { - scope.free_temp(temp_var)?; - } + cleanup!(var_cleanup); Ok(None) } System::SetOnDeviceBatched(device_hash, logic_type, variable) => { let (var, var_cleanup) = self.compile_operand(*variable, scope)?; let (device_hash_val, device_hash_cleanup) = - self.compile_literal_or_variable(device_hash, scope)?; - let Literal::String(logic_type) = logic_type else { + self.compile_literal_or_variable(device_hash.node, scope)?; + let Spanned { + node: Literal::String(logic_type), + .. + } = logic_type + else { return Err(Error::AgrumentMismatch( "Arg2 expected to be a string".into(), span, @@ -1669,18 +1698,41 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.write_output(format!("sb {} {} {}", device_hash_val, logic_type, var))?; - if let Some(var_cleanup) = var_cleanup { - scope.free_temp(var_cleanup)?; - } - - if let Some(device_cleanup) = device_hash_cleanup { - scope.free_temp(device_cleanup)?; - } + cleanup!(var_cleanup, device_hash_cleanup); Ok(None) } + System::SetOnDeviceBatchedNamed(device_hash, name_hash, logic_type, val_expr) => { + let (value, value_cleanup) = self.compile_operand(*val_expr, scope)?; + let (device_hash, device_hash_cleanup) = + self.compile_literal_or_variable(device_hash.node, scope)?; + let (name_hash, name_hash_cleanup) = + self.compile_literal_or_variable(name_hash.node, scope)?; + let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(logic_type.node), + scope, + )?; + + self.write_output(format!( + "snb {} {} {} {}", + device_hash, name_hash, logic_type, value + ))?; + + cleanup!( + value_cleanup, + device_hash_cleanup, + name_hash_cleanup, + logic_type_cleanup + ); + + todo!() + } System::LoadFromDevice(device, logic_type) => { - let LiteralOrVariable::Variable(device_spanned) = device else { + let Spanned { + node: LiteralOrVariable::Variable(device_spanned), + .. + } = device + else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a variable".into(), span, @@ -1702,7 +1754,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { .cloned() .unwrap_or("d0".to_string()); - let Literal::String(logic_type) = logic_type else { + let Spanned { + node: Literal::String(logic_type), + .. + } = logic_type + else { return Err(Error::AgrumentMismatch( "Arg2 expected to be a string".into(), span, @@ -1721,11 +1777,68 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { temp_name: None, })) } + System::LoadBatch(device_hash, logic_type, batch_mode) => { + let (device_hash, device_hash_cleanup) = + self.compile_literal_or_variable(device_hash.node, scope)?; + let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(logic_type.node), + scope, + )?; + let (batch_mode, batch_mode_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(batch_mode.node), + scope, + )?; - t => Err(Error::Unknown( - format!("{t:?}\n\nNot yet implemented"), - Some(span), - )), + self.write_output(format!( + "lb r{} {} {} {}", + VariableScope::RETURN_REGISTER, + device_hash, + logic_type, + batch_mode + ))?; + + cleanup!(device_hash_cleanup, logic_type_cleanup, batch_mode_cleanup); + + Ok(Some(CompilationResult { + location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + temp_name: None, + })) + } + System::LoadBatchNamed(device_hash, name_hash, logic_type, batch_mode) => { + let (device_hash, device_hash_cleanup) = + self.compile_literal_or_variable(device_hash.node, scope)?; + let (name_hash, name_hash_cleanup) = + self.compile_literal_or_variable(name_hash.node, scope)?; + let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(logic_type.node), + scope, + )?; + let (batch_mode, batch_mode_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(batch_mode.node), + scope, + )?; + + self.write_output(format!( + "lbn r{} {} {} {} {}", + VariableScope::RETURN_REGISTER, + device_hash, + name_hash, + logic_type, + batch_mode + ))?; + + cleanup!( + device_hash_cleanup, + name_hash_cleanup, + logic_type_cleanup, + batch_mode_cleanup + ); + + Ok(Some(CompilationResult { + location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + temp_name: None, + })) + } } } diff --git a/rust_compiler/libs/helpers/src/syscall.rs b/rust_compiler/libs/helpers/src/syscall.rs index 37daaa9..e859c75 100644 --- a/rust_compiler/libs/helpers/src/syscall.rs +++ b/rust_compiler/libs/helpers/src/syscall.rs @@ -2,15 +2,16 @@ macro_rules! with_syscalls { ($matcher:ident) => { $matcher!( + // Big names "yield", "sleep", "hash", - "loadFromDevice", - "loadBatchNamed", - "loadBatch", - "setOnDevice", - "setOnDeviceBatched", - "setOnDeviceBatchedNamed", + "load", + "loadBatched", + "loadBatchedNamed", + "set", + "setBatched", + "setBatchedNamed", "acos", "asin", "atan", @@ -26,7 +27,14 @@ macro_rules! with_syscalls { "sin", "sqrt", "tan", - "trunc" + "trunc", + // Lil' names + "l", + "lb", + "lbn", + "s", + "sb", + "sbn" ); }; } diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index e2f5690..76c4e04 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -1587,18 +1587,25 @@ impl<'a> Parser<'a> { macro_rules! literal_or_variable { ($iter:expr) => { match $iter { - Some(expr) => match &expr.node { - Expression::Literal(literal) => { - LiteralOrVariable::Literal(literal.node.clone()) + Some(expr) => { + let span = expr.span; + match &expr.node { + Expression::Literal(literal) => Spanned { + span, + node: LiteralOrVariable::Literal(literal.node.clone()), + }, + Expression::Variable(ident) => Spanned { + span, + node: LiteralOrVariable::Variable(ident.clone()), + }, + _ => { + return Err(Error::UnexpectedToken( + self.current_span(), + self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + )); + } } - Expression::Variable(ident) => LiteralOrVariable::Variable(ident.clone()), - _ => { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, - )) - } - }, + } _ => { return Err(Error::UnexpectedToken( self.current_span(), @@ -1611,8 +1618,11 @@ impl<'a> Parser<'a> { macro_rules! get_arg { ($matcher: ident, $arg: expr) => { - match $arg { - LiteralOrVariable::$matcher(i) => i, + match $arg.node { + LiteralOrVariable::$matcher(i) => Spanned { + node: i, + span: $arg.span, + }, _ => { return Err(Error::InvalidSyntax( self.current_span(), @@ -1641,16 +1651,23 @@ impl<'a> Parser<'a> { let mut args = invocation.arguments.into_iter(); let lit_str = literal_or_variable!(args.next()); - let LiteralOrVariable::Literal(lit_str) = lit_str else { + let Spanned { + node: LiteralOrVariable::Literal(lit_str), + span, + } = lit_str + else { return Err(Error::UnexpectedToken( self.current_span(), self.current_token.clone().ok_or(Error::UnexpectedEOF)?, )); }; - Ok(SysCall::System(System::Hash(lit_str))) + Ok(SysCall::System(System::Hash(Spanned { + node: lit_str, + span, + }))) } - "loadFromDevice" => { + "load" | "l" => { check_length(self, &invocation.arguments, 2)?; let mut args = invocation.arguments.into_iter(); @@ -1660,7 +1677,10 @@ impl<'a> Parser<'a> { let variable = match next_arg { Some(expr) => match expr.node { Expression::Literal(spanned_lit) => match spanned_lit.node { - Literal::String(s) => s, + Literal::String(s) => Spanned { + node: s, + span: spanned_lit.span, + }, _ => { return Err(Error::UnexpectedToken( self.current_span(), @@ -1685,10 +1705,38 @@ impl<'a> Parser<'a> { Ok(SysCall::System(sys_call::System::LoadFromDevice( device, - Literal::String(variable), + Spanned { + node: Literal::String(variable.node), + span: variable.span, + }, ))) } - "setOnDevice" => { + "loadBatched" | "lb" => { + check_length(self, &invocation.arguments, 3)?; + let mut args = invocation.arguments.into_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(System::LoadBatch( + device_hash, + logic_type, + batch_mode, + ))) + } + "loadBatchedNamed" | "lbn" => { + check_length(self, &invocation.arguments, 4)?; + let mut args = invocation.arguments.into_iter(); + let dev_hash = literal_or_variable!(args.next()); + let name_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(System::LoadBatchNamed( + dev_hash, name_hash, logic_type, batch_mode, + ))) + } + "set" | "s" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.into_iter(); let device = literal_or_variable!(args.next()); @@ -1696,22 +1744,44 @@ impl<'a> Parser<'a> { let variable = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::System(sys_call::System::SetOnDevice( device, - Literal::String(logic_type.to_string().replace("\"", "")), + Spanned { + node: Literal::String(logic_type.node.to_string().replace("\"", "")), + span: logic_type.span, + }, boxed!(variable), ))) } - "setOnDeviceBatched" => { + "setBatched" | "sb" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.into_iter(); let device_hash = literal_or_variable!(args.next()); let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); let variable = args.next().ok_or(Error::UnexpectedEOF)?; + Ok(SysCall::System(sys_call::System::SetOnDeviceBatched( device_hash, - Literal::String(logic_type.to_string().replace("\"", "")), + Spanned { + node: Literal::String(logic_type.to_string().replace("\"", "")), + span: logic_type.span, + }, boxed!(variable), ))) } + "setBatchedNamed" | "sbn" => { + check_length(self, &invocation.arguments, 4)?; + let mut args = invocation.arguments.into_iter(); + let device_hash = literal_or_variable!(args.next()); + let name_hash = literal_or_variable!(args.next()); + let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let expr = Box::new(args.next().ok_or(Error::UnexpectedEOF)?); + + Ok(SysCall::System(System::SetOnDeviceBatchedNamed( + device_hash, + name_hash, + logic_type, + expr, + ))) + } _ => Err(Error::UnsupportedKeyword( self.current_span(), self.current_token.clone().ok_or(Error::UnexpectedEOF)?, diff --git a/rust_compiler/libs/parser/src/sys_call.rs b/rust_compiler/libs/parser/src/sys_call.rs index 6a4aa88..8102644 100644 --- a/rust_compiler/libs/parser/src/sys_call.rs +++ b/rust_compiler/libs/parser/src/sys_call.rs @@ -142,58 +142,68 @@ documented! { /// ## Slang /// `sleep(number|var);` Sleep(Box>), - /// Gets the in-game hash for a specific prefab name. + /// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY + /// optimized away unless you bind it to a `let` variable. If you use a `const` variable + /// however, the hash is correctly computed at compile time and substitued automatically. /// ## IC10 /// `HASH("prefabName")` /// ## Slang - /// `HASH("prefabName");` - Hash(Literal), + /// `hash("prefabName");` + /// + /// ## Example + /// ``` + /// const compDoor = hash("StructureCompositeDoor"); + /// setOnDeviceBatched(compDoor, "Lock", true); + /// ``` + Hash(Spanned), /// Represents a function which loads a device variable into a register. /// ## IC10 /// `l r? d? var` /// ## Slang - /// `loadFromDevice(deviceType, "LogicType");` - LoadFromDevice(LiteralOrVariable, Literal), + /// `load(deviceType, "LogicType");` + LoadFromDevice(Spanned, Spanned), /// Function which gets a LogicType from all connected network devices that match /// the provided device hash and name, aggregating them via a batchMode /// ## IC10 /// `lbn r? deviceHash nameHash logicType batchMode` /// ## Slang - /// `loadFromDeviceBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");` + /// `loadBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");` LoadBatchNamed( - LiteralOrVariable, - Box>, - Literal, - Literal, + Spanned, + Spanned, + Spanned, + Spanned, ), /// Loads a LogicType from all connected network devices, aggregating them via a - /// batchMode + /// BatchMode /// ## IC10 /// `lb r? deviceHash logicType batchMode` /// ## Slang - /// `loadFromDeviceBatched(deviceHash, "Variable", "LogicType");` - LoadBatch(LiteralOrVariable, Literal, Literal), + /// `loadBatched(deviceHash, "Variable", "LogicType");` + LoadBatch(Spanned, Spanned, Spanned), /// Represents a function which stores a setting into a specific device. /// ## IC10 /// `s d? logicType r?` /// ## Slang - /// `setOnDevice(deviceType, "Variable", (number|var));` - SetOnDevice(LiteralOrVariable, Literal, Box>), + /// `set(deviceType, "Variable", (number|var));` + SetOnDevice(Spanned, Spanned, Box>), /// Represents a function which stores a setting to all devices that match /// the given deviceHash /// ## IC10 /// `sb deviceHash logicType r?` - SetOnDeviceBatched(LiteralOrVariable, Literal, Box>), + /// ## Slang + /// `setBatched(deviceHash, "LogicType", (number|var))` + SetOnDeviceBatched(Spanned, Spanned, Box>), /// Represents a function which stores a setting to all devices that match /// both the given deviceHash AND the given nameHash /// ## IC10 /// `sbn deviceHash nameHash logicType r?` /// ## Slang - /// `setOnDeviceBatchedNamed(deviceType, nameHash, "LogicType", (number|var))` + /// `setBatchedNamed(deviceHash, nameHash, "LogicType", (number|var))` SetOnDeviceBatchedNamed( - LiteralOrVariable, - LiteralOrVariable, - Literal, + Spanned, + Spanned, + Spanned, Box>, ), } From 5d92586dba18811b5545a2587275bb11c0cc06f1 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 6 Dec 2025 02:09:12 -0700 Subject: [PATCH 5/6] Fixed compiler bug that causes args.next to be invoked more than the 1 expected time --- .../libs/compiler/src/test/syscall.rs | 34 +++++----- rust_compiler/libs/compiler/src/v1.rs | 60 +++++++++++++++--- .../libs/compiler/src/variable_manager.rs | 2 + rust_compiler/libs/parser/src/lib.rs | 63 ++++++++++++------- 4 files changed, 107 insertions(+), 52 deletions(-) diff --git a/rust_compiler/libs/compiler/src/test/syscall.rs b/rust_compiler/libs/compiler/src/test/syscall.rs index a36e672..9455cb1 100644 --- a/rust_compiler/libs/compiler/src/test/syscall.rs +++ b/rust_compiler/libs/compiler/src/test/syscall.rs @@ -63,7 +63,7 @@ fn test_set_on_device() -> anyhow::Result<()> { device airConditioner = "d0"; let internalTemp = 20c; - setOnDevice(airConditioner, "On", internalTemp > 25c); + set(airConditioner, "On", internalTemp > 25c); "# }; @@ -89,7 +89,7 @@ fn test_set_on_device_batched() -> anyhow::Result<()> { debug r#" const doorHash = hash("Door"); - setOnDeviceBatched(doorHash, "Lock", true); + setBatched(doorHash, "Lock", true); "# }; @@ -109,29 +109,25 @@ fn test_set_on_device_batched() -> anyhow::Result<()> { #[test] fn test_set_on_device_batched_named() -> anyhow::Result<()> { let compiled = compile! { - result + debug r#" device dev = "d0"; const devName = hash("test"); - let myVar = lbn(dev, devName, "On", 12); + sbn(dev, devName, "On", 12); "# }; - println!("{compiled:?}"); - - assert!(compiled.is_empty()); - - // assert_eq!( - // compiled, - // indoc! { - // " - // j main - // main: - // lbn r8 d0 - // " - // } - // ); + assert_eq!( + compiled, + indoc! { + " + j main + main: + sbn d0 -662733300 On 12 + " + } + ); Ok(()) } @@ -143,7 +139,7 @@ fn test_load_from_device() -> anyhow::Result<()> { r#" device airCon = "d0"; - let setting = loadFromDevice(airCon, "On"); + let setting = load(airCon, "On"); "# }; diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 0a69f7f..7b4dffa 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -77,6 +77,9 @@ quick_error! { ConstAssignment(ident: String, span: Span) { display("Attempted to re-assign a value to const variable `{ident}`") } + DeviceAssignment(ident: String, span: Span) { + display("Attempted to re-assign a value to a device const `{ident}`") + } Unknown(reason: String, span: Option) { display("{reason}") } @@ -104,6 +107,7 @@ impl From for lsp_types::Diagnostic { | UnknownIdentifier(_, span) | InvalidDevice(_, span) | ConstAssignment(_, span) + | DeviceAssignment(_, span) | AgrumentMismatch(_, span) => Diagnostic { range: span.into(), message: value.to_string(), @@ -348,12 +352,20 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { temp_name: None, // User variable, do not free })), Err(_) => { - self.errors - .push(Error::UnknownIdentifier(name.node.clone(), name.span)); - Ok(Some(CompilationResult { - location: VariableLocation::Temporary(0), - temp_name: None, - })) + // fallback, check devices + if let Some(device) = self.devices.get(&name.node) { + Ok(Some(CompilationResult { + location: VariableLocation::Device(device.clone()), + temp_name: None, + })) + } else { + self.errors + .push(Error::UnknownIdentifier(name.node.clone(), name.span)); + Ok(Some(CompilationResult { + location: VariableLocation::Temporary(0), + temp_name: None, + })) + } } } } @@ -467,6 +479,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { None, )); } + VariableLocation::Device(_) => { + return Err(Error::Unknown( + r#"Attempted to emit a variable assignent for device. + This is a Compiler bug and should be reported to the developer."# + .into(), + None, + )); + } } Ok(()) @@ -622,7 +642,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { ))?; format!("r{}", VariableScope::TEMP_STACK_REGISTER) } - VariableLocation::Constant(_) => unreachable!(), + VariableLocation::Constant(_) | VariableLocation::Device(_) => unreachable!(), }; self.emit_variable_assignment(&name_str, &var_loc, src_str)?; (var_loc, None) @@ -765,6 +785,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { VariableLocation::Constant(_) => { return Err(Error::ConstAssignment(identifier.node, identifier.span)); } + VariableLocation::Device(_) => { + return Err(Error::DeviceAssignment(identifier.node, identifier.span)); + } } if let Some(name) = cleanup { @@ -879,6 +902,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { VariableScope::TEMP_STACK_REGISTER ))?; } + VariableLocation::Device(_) => { + return Err(Error::Unknown( + r#"Attempted to pass a device contant into a function argument. These values can be used without scope."#.into(), + Some(arg.span), + )); + } } } Expression::Binary(bin_expr) => { @@ -1128,6 +1157,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { "Cannot resolve a constant value to register".into(), None, )), + VariableLocation::Device(_) => Err(Error::Unknown( + "Cannot resolve a device to a register".into(), + None, + )), VariableLocation::Stack(_) => Err(Error::Unknown( "Cannot resolve Stack location directly to register string without context".into(), None, @@ -1205,6 +1238,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { // We return the NEW temp name to be freed. Ok((temp_reg, Some(temp_name))) } + VariableLocation::Device(d) => Ok((d, None)), } } @@ -1505,6 +1539,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { VariableScope::TEMP_STACK_REGISTER ))?; } + VariableLocation::Device(_) => { + return Err(Error::Unknown( + "You can not return a device from a function.".into(), + Some(var_name.span), + )); + } }, Err(_) => { self.errors.push(Error::UnknownIdentifier( @@ -1706,15 +1746,17 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let (value, value_cleanup) = self.compile_operand(*val_expr, scope)?; let (device_hash, device_hash_cleanup) = self.compile_literal_or_variable(device_hash.node, scope)?; + let (name_hash, name_hash_cleanup) = self.compile_literal_or_variable(name_hash.node, scope)?; + let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; self.write_output(format!( - "snb {} {} {} {}", + "sbn {} {} {} {}", device_hash, name_hash, logic_type, value ))?; @@ -1725,7 +1767,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { logic_type_cleanup ); - todo!() + Ok(None) } System::LoadFromDevice(device, logic_type) => { let Spanned { diff --git a/rust_compiler/libs/compiler/src/variable_manager.rs b/rust_compiler/libs/compiler/src/variable_manager.rs index 61dde65..0c5696c 100644 --- a/rust_compiler/libs/compiler/src/variable_manager.rs +++ b/rust_compiler/libs/compiler/src/variable_manager.rs @@ -46,6 +46,8 @@ pub enum VariableLocation { Stack(u16), /// Represents a constant value and should be directly substituted as such. Constant(Literal), + /// Represents a device pin. This will contain the exact `d0-d5` string + Device(String), } pub struct VariableScope<'a> { diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index 76c4e04..6a8a587 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -1586,7 +1586,7 @@ impl<'a> Parser<'a> { macro_rules! literal_or_variable { ($iter:expr) => { - match $iter { + match &$iter { Some(expr) => { let span = expr.span; match &expr.node { @@ -1599,9 +1599,9 @@ impl<'a> Parser<'a> { node: LiteralOrVariable::Variable(ident.clone()), }, _ => { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + return Err(Error::InvalidSyntax( + expr.span, + "Expected a literal or variable".to_string(), )); } } @@ -1625,8 +1625,8 @@ impl<'a> Parser<'a> { }, _ => { return Err(Error::InvalidSyntax( - self.current_span(), - String::from("Expected a variable"), + $arg.span, + format!("Expected a {}", stringify!($matcher).to_lowercase()), )) } } @@ -1656,9 +1656,9 @@ impl<'a> Parser<'a> { span, } = lit_str else { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + return Err(Error::InvalidSyntax( + lit_str.span, + "Expected a string literal".to_string(), )); }; @@ -1671,7 +1671,8 @@ impl<'a> Parser<'a> { check_length(self, &invocation.arguments, 2)?; let mut args = invocation.arguments.into_iter(); - let device = literal_or_variable!(args.next()); + let tmp = args.next(); + let device = literal_or_variable!(tmp); let next_arg = args.next(); let variable = match next_arg { @@ -1682,16 +1683,16 @@ impl<'a> Parser<'a> { span: spanned_lit.span, }, _ => { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + return Err(Error::InvalidSyntax( + spanned_lit.span, + "Expected a string literal".to_string(), )); } }, _ => { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + return Err(Error::InvalidSyntax( + expr.span, + "Expected a string literal".to_string(), )); } }, @@ -1739,8 +1740,12 @@ impl<'a> Parser<'a> { "set" | "s" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.into_iter(); - let device = literal_or_variable!(args.next()); - let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let tmp = args.next(); + let device = literal_or_variable!(tmp); + + let tmp = args.next(); + let logic_type = get_arg!(Literal, literal_or_variable!(tmp)); + let variable = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::System(sys_call::System::SetOnDevice( device, @@ -1754,8 +1759,11 @@ impl<'a> Parser<'a> { "setBatched" | "sb" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.into_iter(); - let device_hash = literal_or_variable!(args.next()); - let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let tmp = args.next(); + let device_hash = literal_or_variable!(tmp); + + let tmp = args.next(); + let logic_type = get_arg!(Literal, literal_or_variable!(tmp)); let variable = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::System(sys_call::System::SetOnDeviceBatched( @@ -1770,10 +1778,17 @@ impl<'a> Parser<'a> { "setBatchedNamed" | "sbn" => { check_length(self, &invocation.arguments, 4)?; let mut args = invocation.arguments.into_iter(); - let device_hash = literal_or_variable!(args.next()); - let name_hash = literal_or_variable!(args.next()); - let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); - let expr = Box::new(args.next().ok_or(Error::UnexpectedEOF)?); + let tmp = args.next(); + let device_hash = literal_or_variable!(tmp); + + let tmp = args.next(); + let name_hash = literal_or_variable!(tmp); + + let tmp = args.next(); + let logic_type = get_arg!(Literal, literal_or_variable!(tmp)); + + let tmp = args.next(); + let expr = Box::new(tmp.ok_or(Error::UnexpectedEOF)?); Ok(SysCall::System(System::SetOnDeviceBatchedNamed( device_hash, From 0ac5661d7f62e88a64bb51a235a80042df0f1718 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 6 Dec 2025 02:12:16 -0700 Subject: [PATCH 6/6] Fixed last 2 implemented syscalls --- rust_compiler/libs/parser/src/lib.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index 6a8a587..a973494 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -1715,9 +1715,14 @@ impl<'a> Parser<'a> { "loadBatched" | "lb" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.into_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())); + let tmp = args.next(); + let device_hash = literal_or_variable!(tmp); + + let tmp = args.next(); + let logic_type = get_arg!(Literal, literal_or_variable!(tmp)); + + let tmp = args.next(); + let batch_mode = get_arg!(Literal, literal_or_variable!(tmp)); Ok(SysCall::System(System::LoadBatch( device_hash, @@ -1728,10 +1733,17 @@ impl<'a> Parser<'a> { "loadBatchedNamed" | "lbn" => { check_length(self, &invocation.arguments, 4)?; let mut args = invocation.arguments.into_iter(); - let dev_hash = literal_or_variable!(args.next()); - let name_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())); + let tmp = args.next(); + let dev_hash = literal_or_variable!(tmp); + + let tmp = args.next(); + let name_hash = literal_or_variable!(tmp); + + let tmp = args.next(); + let logic_type = get_arg!(Literal, literal_or_variable!(tmp)); + + let tmp = args.next(); + let batch_mode = get_arg!(Literal, literal_or_variable!(tmp)); Ok(SysCall::System(System::LoadBatchNamed( dev_hash, name_hash, logic_type, batch_mode,