From 9993bff574f056645188c944d894c19b7caa6229 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 21:34:51 -0700 Subject: [PATCH] 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), }