diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index f12b00b..664669d 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -252,14 +252,25 @@ name = "compiler" version = "0.1.0" dependencies = [ "anyhow", + "helpers", "indoc", "lsp-types", "parser", "pretty_assertions", "quick-error", + "rust_decimal", "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" @@ -363,6 +374,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helpers" version = "0.1.0" +dependencies = [ + "crc32fast", +] [[package]] name = "indexmap" 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..a21718c 100644 --- a/rust_compiler/libs/compiler/Cargo.toml +++ b/rust_compiler/libs/compiler/Cargo.toml @@ -7,7 +7,9 @@ edition = "2024" quick-error = { workspace = true } parser = { path = "../parser" } tokenizer = { path = "../tokenizer" } +helpers = { path = "../helpers" } lsp-types = { 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..757a0e0 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,35 @@ 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 + " + } + ); + + 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 " } ); 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/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..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); "# }; @@ -88,8 +88,8 @@ fn test_set_on_device_batched() -> anyhow::Result<()> { let compiled = compile! { debug r#" - let doorHash = hash("Door"); - setOnDeviceBatched(doorHash, "Lock", true); + const doorHash = hash("Door"); + setBatched(doorHash, "Lock", true); "# }; @@ -99,15 +99,39 @@ 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 "# } ); Ok(()) } +#[test] +fn test_set_on_device_batched_named() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + device dev = "d0"; + const devName = hash("test"); + + sbn(dev, devName, "On", 12); + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + sbn d0 -662733300 On 12 + " + } + ); + + Ok(()) +} + #[test] fn test_load_from_device() -> anyhow::Result<()> { let compiled = compile! { @@ -115,7 +139,7 @@ fn test_load_from_device() -> anyhow::Result<()> { r#" device airCon = "d0"; - let setting = loadFromDevice(airCon, "On"); + let setting = load(airCon, "On"); "# }; @@ -133,27 +157,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 6633554..7b4dffa 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 helpers::prelude::*; 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) => { @@ -75,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}") } @@ -102,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(), @@ -346,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, + })) + } } } } @@ -465,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(()) @@ -557,15 +579,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)?; @@ -611,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) @@ -679,8 +710,27 @@ 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(Spanned { + node: Literal::String(str_to_hash), + .. + })), + .. + }) => 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(), + 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, }) } @@ -735,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 { @@ -849,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) => { @@ -1098,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, @@ -1122,6 +1185,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. @@ -1172,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)), } } @@ -1208,6 +1275,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), @@ -1420,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( @@ -1507,30 +1632,43 @@ 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, )); }; - 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, @@ -1540,7 +1678,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, @@ -1562,7 +1704,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, @@ -1571,17 +1717,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, @@ -1590,18 +1738,43 @@ 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)?; - } + cleanup!(var_cleanup, device_hash_cleanup); - if let Some(device_cleanup) = device_hash_cleanup { - scope.free_temp(device_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!( + "sbn {} {} {} {}", + device_hash, name_hash, logic_type, value + ))?; + + cleanup!( + value_cleanup, + device_hash_cleanup, + name_hash_cleanup, + logic_type_cleanup + ); Ok(None) } 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, @@ -1623,7 +1796,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, @@ -1642,11 +1819,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/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/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/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 dd9f582..a973494 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 { @@ -1556,19 +1586,26 @@ 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()) + match &$iter { + 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::InvalidSyntax( + expr.span, + "Expected a literal or variable".to_string(), + )); + } } - 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(), @@ -1581,12 +1618,15 @@ 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(), - String::from("Expected a variable"), + $arg.span, + format!("Expected a {}", stringify!($matcher).to_lowercase()), )) } } @@ -1611,37 +1651,48 @@ 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 { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + let Spanned { + node: LiteralOrVariable::Literal(lit_str), + span, + } = lit_str + else { + return Err(Error::InvalidSyntax( + lit_str.span, + "Expected a string literal".to_string(), )); }; - 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(); - 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 { 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(), - 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(), )); } }, @@ -1655,33 +1706,109 @@ 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 = 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 tmp = args.next(); + let batch_mode = get_arg!(Literal, literal_or_variable!(tmp)); + + 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 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, + ))) + } + "set" | "s" => { + check_length(self, &invocation.arguments, 3)?; + let mut args = invocation.arguments.into_iter(); + 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, - 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 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( 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 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, + 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>, ), } 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..9745ecd 100644 --- a/rust_compiler/libs/tokenizer/src/token.rs +++ b/rust_compiler/libs/tokenizer/src/token.rs @@ -168,11 +168,86 @@ 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), } +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 {