diff --git a/Changelog.md b/Changelog.md index a493374..e34a201 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,10 @@ # Changelog +[0.2.1] + +- Added support for `loadSlot` and `setSlot` +- Fixed bug where syscalls like `max(1, 2)` were not allowed in assignment expressions + [0.2.0] - Completely re-wrote the tokenizer to use `logos` diff --git a/ModData/About/About.xml b/ModData/About/About.xml index 98e8f46..8c0bca7 100644 --- a/ModData/About/About.xml +++ b/ModData/About/About.xml @@ -2,7 +2,7 @@ Slang JoeDiertay - 0.2.0 + 0.2.1 [h1]Slang: High-Level Programming for Stationeers[/h1] diff --git a/csharp_mod/stationeersSlang.csproj b/csharp_mod/stationeersSlang.csproj index f519032..91024c0 100644 --- a/csharp_mod/stationeersSlang.csproj +++ b/csharp_mod/stationeersSlang.csproj @@ -5,7 +5,7 @@ enable StationeersSlang Slang Compiler Bridge - 0.2.0 + 0.2.1 true latest diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index 6867837..2c1e541 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -909,7 +909,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slang" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "clap", diff --git a/rust_compiler/Cargo.toml b/rust_compiler/Cargo.toml index 8969880..6cd02a7 100644 --- a/rust_compiler/Cargo.toml +++ b/rust_compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "slang" -version = "0.2.0" +version = "0.2.1" edition = "2021" [workspace] diff --git a/rust_compiler/libs/compiler/src/test/math_syscall.rs b/rust_compiler/libs/compiler/src/test/math_syscall.rs index 966a1e3..f102135 100644 --- a/rust_compiler/libs/compiler/src/test/math_syscall.rs +++ b/rust_compiler/libs/compiler/src/test/math_syscall.rs @@ -243,6 +243,32 @@ fn test_max() -> Result<()> { Ok(()) } +#[test] +fn test_max_from_game() -> Result<()> { + let compiled = compile! { + debug + r#" + let item = 0; + item = max(1 + 2, 2); + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #item + max r15 3 2 + move r8 r15 #item + " + } + ); + + Ok(()) +} + #[test] fn test_min() -> Result<()> { let compiled = compile! { diff --git a/rust_compiler/libs/compiler/src/test/syscall.rs b/rust_compiler/libs/compiler/src/test/syscall.rs index 9455cb1..a8e5b9e 100644 --- a/rust_compiler/libs/compiler/src/test/syscall.rs +++ b/rust_compiler/libs/compiler/src/test/syscall.rs @@ -157,3 +157,54 @@ fn test_load_from_device() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_load_from_slot() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + device airCon = "d0"; + + let setting = ls(airCon, 0, "Occupied"); + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + ls r15 d0 0 Occupied + move r8 r15 #setting + " + } + ); + + Ok(()) +} + +#[test] +fn test_set_slot() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + device airCon = "d0"; + + ss(airCon, 0, "Occupied", true); + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + ss d0 0 Occupied 1 + " + } + ); + + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index ac3acf2..aab52ff 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -865,6 +865,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.free_temp(c, None)?; } } + _ => { return Err(Error::Unknown( "Invalid assignment target. Only variables and member access are supported." @@ -1952,6 +1953,55 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { temp_name: None, })) } + System::LoadSlot(dev_name, slot_index, logic_type) => { + let (dev_hash, hash_cleanup) = + self.compile_literal_or_variable(dev_name.node, scope)?; + let (slot_index, slot_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(slot_index.node), + scope, + )?; + let (logic_type, logic_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(logic_type.node), + scope, + )?; + + self.write_output(format!( + "ls r{} {} {} {}", + VariableScope::RETURN_REGISTER, + dev_hash, + slot_index, + logic_type + ))?; + + cleanup!(hash_cleanup, slot_cleanup, logic_cleanup); + + Ok(Some(CompilationResult { + location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + temp_name: None, + })) + } + System::SetSlot(dev_name, slot_index, logic_type, var) => { + let (dev_name, name_cleanup) = + self.compile_literal_or_variable(dev_name.node, scope)?; + let (slot_index, index_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(slot_index.node), + scope, + )?; + let (logic_type, type_cleanup) = self.compile_literal_or_variable( + LiteralOrVariable::Literal(logic_type.node), + scope, + )?; + let (var, var_cleanup) = self.compile_operand(*var, scope)?; + + self.write_output(format!( + "ss {} {} {} {}", + dev_name, slot_index, logic_type, var + ))?; + + cleanup!(name_cleanup, index_cleanup, type_cleanup, var_cleanup); + + Ok(None) + } } } diff --git a/rust_compiler/libs/compiler/src/variable_manager.rs b/rust_compiler/libs/compiler/src/variable_manager.rs index 77a5c24..faf8a1a 100644 --- a/rust_compiler/libs/compiler/src/variable_manager.rs +++ b/rust_compiler/libs/compiler/src/variable_manager.rs @@ -52,7 +52,7 @@ pub enum LocationRequest { Stack, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum VariableLocation<'a> { /// Represents a temporary register (r1 - r7) Temporary(u8), @@ -66,7 +66,6 @@ pub enum VariableLocation<'a> { Device(Cow<'a, str>), } -// FIX: Added 'b lifetime for the parent reference pub struct VariableScope<'a, 'b> { temporary_vars: VecDeque, persistant_vars: VecDeque, @@ -75,7 +74,6 @@ pub struct VariableScope<'a, 'b> { parent: Option<&'b VariableScope<'a, 'b>>, } -// FIX: Updated Default impl to include 'b impl<'a, 'b> Default for VariableScope<'a, 'b> { fn default() -> Self { Self { @@ -88,7 +86,6 @@ impl<'a, 'b> Default for VariableScope<'a, 'b> { } } -// FIX: Updated impl block to include 'b impl<'a, 'b> VariableScope<'a, 'b> { #[allow(dead_code)] pub const TEMP_REGISTER_COUNT: u8 = 7; @@ -112,7 +109,6 @@ impl<'a, 'b> VariableScope<'a, 'b> { }) } - // FIX: parent is now &'b VariableScope<'a, 'b> pub fn scoped(parent: &'b VariableScope<'a, 'b>) -> Self { Self { parent: Option::Some(parent), diff --git a/rust_compiler/libs/helpers/src/syscall.rs b/rust_compiler/libs/helpers/src/syscall.rs index e859c75..5ec38c5 100644 --- a/rust_compiler/libs/helpers/src/syscall.rs +++ b/rust_compiler/libs/helpers/src/syscall.rs @@ -9,9 +9,11 @@ macro_rules! with_syscalls { "load", "loadBatched", "loadBatchedNamed", + "loadSlot", "set", "setBatched", "setBatchedNamed", + "setSlot", "acos", "asin", "atan", @@ -32,9 +34,11 @@ macro_rules! with_syscalls { "l", "lb", "lbn", + "ls", "s", "sb", - "sbn" + "sbn", + "ss" ); }; } diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index 8a99e9c..bcba95d 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -645,6 +645,15 @@ impl<'a> Parser<'a> { .node .ok_or(Error::UnexpectedEOF)?, + TokenType::Identifier(ref id) if SysCall::is_syscall(id) => { + let spanned_call = self.spanned(|p| p.syscall())?; + + Spanned { + span: spanned_call.span, + node: Expression::Syscall(spanned_call), + } + } + TokenType::Identifier(_) if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => { @@ -1050,7 +1059,9 @@ impl<'a> Parser<'a> { if token_matches!( temp_token, - TokenType::Symbol(Symbol::Semicolon) | TokenType::Symbol(Symbol::RParen) + TokenType::Symbol(Symbol::Semicolon) + | TokenType::Symbol(Symbol::RParen) + | TokenType::Symbol(Symbol::Comma) ) { self.tokenizer.seek(SeekFrom::Current(-1))?; } @@ -1518,18 +1529,23 @@ impl<'a> Parser<'a> { } fn syscall(&mut self) -> Result, Error<'a>> { - fn check_length<'a>( - span: Span, - arguments: &[Spanned>], - length: usize, - ) -> Result<(), Error<'a>> { - if arguments.len() != length { + let invocation = self.invocation()?; + + let check_length = |len: usize| -> Result<(), Error> { + if invocation.arguments.len() != len { return Err(Error::InvalidSyntax( - span, - format!("Expected {} arguments", length), + self.current_span(), + format!("Expected {} arguments", len), )); } Ok(()) + }; + + macro_rules! args { + ($count:expr) => {{ + check_length($count)?; + invocation.arguments.into_iter() + }}; } macro_rules! literal_or_variable { @@ -1581,23 +1597,19 @@ impl<'a> Parser<'a> { }; } - let invocation = self.invocation()?; - match invocation.name.node.as_ref() { // System SysCalls "yield" => { - check_length(self.current_span(), &invocation.arguments, 0)?; + check_length(0)?; Ok(SysCall::System(sys_call::System::Yield)) } "sleep" => { - check_length(self.current_span(), &invocation.arguments, 1)?; - let mut arg = invocation.arguments.into_iter(); - let expr = arg.next().ok_or(Error::UnexpectedEOF)?; + let mut args = args!(1); + let expr = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::System(System::Sleep(boxed!(expr)))) } "hash" => { - check_length(self.current_span(), &invocation.arguments, 1)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(1); let lit_str = literal_or_variable!(args.next()); let Spanned { @@ -1617,8 +1629,7 @@ impl<'a> Parser<'a> { }))) } "load" | "l" => { - check_length(self.current_span(), &invocation.arguments, 2)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(2); let tmp = args.next(); let device = literal_or_variable!(tmp); @@ -1662,8 +1673,7 @@ impl<'a> Parser<'a> { ))) } "loadBatched" | "lb" => { - check_length(self.current_span(), &invocation.arguments, 3)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(3); let tmp = args.next(); let device_hash = literal_or_variable!(tmp); @@ -1680,8 +1690,7 @@ impl<'a> Parser<'a> { ))) } "loadBatchedNamed" | "lbn" => { - check_length(self.current_span(), &invocation.arguments, 4)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(4); let tmp = args.next(); let dev_hash = literal_or_variable!(tmp); @@ -1699,8 +1708,7 @@ impl<'a> Parser<'a> { ))) } "set" | "s" => { - check_length(self.current_span(), &invocation.arguments, 3)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(3); let tmp = args.next(); let device = literal_or_variable!(tmp); @@ -1720,8 +1728,7 @@ impl<'a> Parser<'a> { ))) } "setBatched" | "sb" => { - check_length(self.current_span(), &invocation.arguments, 3)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(3); let tmp = args.next(); let device_hash = literal_or_variable!(tmp); @@ -1739,8 +1746,7 @@ impl<'a> Parser<'a> { ))) } "setBatchedNamed" | "sbn" => { - check_length(self.current_span(), &invocation.arguments, 4)?; - let mut args = invocation.arguments.into_iter(); + let mut args = args!(4); let tmp = args.next(); let device_hash = literal_or_variable!(tmp); @@ -1760,30 +1766,110 @@ impl<'a> Parser<'a> { expr, ))) } + "loadSlot" | "ls" => { + let mut args = args!(3); + let next = args.next(); + let dev_name = literal_or_variable!(next); + let next = args.next(); + let slot_index = get_arg!(Literal, literal_or_variable!(next)); + if !matches!( + slot_index, + Spanned { + node: Literal::Number(_), + .. + }, + ) { + return Err(Error::InvalidSyntax( + slot_index.span, + "Expected a number".to_string(), + )); + } + let next = args.next(); + let slot_logic = get_arg!(Literal, literal_or_variable!(next)); + if !matches!( + slot_logic, + Spanned { + node: Literal::String(_), + .. + } + ) { + return Err(Error::InvalidSyntax( + slot_logic.span, + "Expected a String".into(), + )); + } + + Ok(SysCall::System(System::LoadSlot( + dev_name, slot_index, slot_logic, + ))) + } + "setSlot" | "ss" => { + let mut args = args!(4); + let next = args.next(); + let dev_name = literal_or_variable!(next); + let next = args.next(); + let slot_index = get_arg!(Literal, literal_or_variable!(next)); + if !matches!( + slot_index, + Spanned { + node: Literal::Number(_), + .. + } + ) { + return Err(Error::InvalidSyntax( + slot_index.span, + "Expected a number".into(), + )); + } + let next = args.next(); + let slot_logic = get_arg!(Literal, literal_or_variable!(next)); + if !matches!( + slot_logic, + Spanned { + node: Literal::String(_), + .. + } + ) { + return Err(Error::InvalidSyntax( + slot_logic.span, + "Expected a string".into(), + )); + } + let next = args.next(); + let expr = next.ok_or(Error::UnexpectedEOF)?; + + Ok(SysCall::System(System::SetSlot( + dev_name, + slot_index, + slot_logic, + Box::new(expr), + ))) + } + // Math SysCalls "acos" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let tmp = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Acos(boxed!(tmp)))) } "asin" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let tmp = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Asin(boxed!(tmp)))) } "atan" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let expr = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Atan(boxed!(expr)))) } "atan2" => { - check_length(self.current_span(), &invocation.arguments, 2)?; + check_length(2)?; let mut args = invocation.arguments.into_iter(); let arg1 = args.next().ok_or(Error::UnexpectedEOF)?; let arg2 = args.next().ok_or(Error::UnexpectedEOF)?; @@ -1791,42 +1877,42 @@ impl<'a> Parser<'a> { Ok(SysCall::Math(Math::Atan2(boxed!(arg1), boxed!(arg2)))) } "abs" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let expr = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Abs(boxed!(expr)))) } "ceil" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Ceil(boxed!(arg)))) } "cos" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Cos(boxed!(arg)))) } "floor" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Floor(boxed!(arg)))) } "log" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Log(boxed!(arg)))) } "max" => { - check_length(self.current_span(), &invocation.arguments, 2)?; + check_length(2)?; let mut args = invocation.arguments.into_iter(); let arg1 = args.next().ok_or(Error::UnexpectedEOF)?; let arg2 = args.next().ok_or(Error::UnexpectedEOF)?; @@ -1834,7 +1920,7 @@ impl<'a> Parser<'a> { Ok(SysCall::Math(Math::Max(boxed!(arg1), boxed!(arg2)))) } "min" => { - check_length(self.current_span(), &invocation.arguments, 2)?; + check_length(2)?; let mut args = invocation.arguments.into_iter(); let arg1 = args.next().ok_or(Error::UnexpectedEOF)?; let arg2 = args.next().ok_or(Error::UnexpectedEOF)?; @@ -1842,32 +1928,32 @@ impl<'a> Parser<'a> { Ok(SysCall::Math(Math::Min(boxed!(arg1), boxed!(arg2)))) } "rand" => { - check_length(self.current_span(), &invocation.arguments, 0)?; + check_length(0)?; Ok(SysCall::Math(Math::Rand)) } "sin" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Sin(boxed!(arg)))) } "sqrt" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Sqrt(boxed!(arg)))) } "tan" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().ok_or(Error::UnexpectedEOF)?; Ok(SysCall::Math(Math::Tan(boxed!(arg)))) } "trunc" => { - check_length(self.current_span(), &invocation.arguments, 1)?; + check_length(1)?; let mut args = invocation.arguments.into_iter(); let arg = args.next().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 e90e837..00a48ef 100644 --- a/rust_compiler/libs/parser/src/sys_call.rs +++ b/rust_compiler/libs/parser/src/sys_call.rs @@ -214,6 +214,30 @@ documented! { Spanned>, Box>>, ), + /// Loads slot LogicSlotType from device into a variable + /// + /// ## IC10 + /// `ls r0 d0 2 Occupied` + /// ## Slang + /// `let isOccupied = loadSlot(deviceHash, 2, "Occupied");` + /// `let isOccupied = ls(deviceHash, 2, "Occupied");` + LoadSlot( + Spanned>, + Spanned>, + Spanned> + ), + /// Stores a value of LogicType on a device by the index value + /// ## IC10 + /// `ss d0 0 "Open" 1` + /// ## Slang + /// `setSlot(deviceHash, 0, "Open", true);` + /// `ss(deviceHash, 0, "Open", true);` + SetSlot( + Spanned>, + Spanned>, + Spanned>, + Box>> + ) } } @@ -235,6 +259,8 @@ impl<'a> std::fmt::Display for System<'a> { System::SetOnDeviceBatchedNamed(a, b, c, d) => { write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d) } + System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c), + System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d), } } } diff --git a/rust_compiler/libs/tokenizer/src/token.rs b/rust_compiler/libs/tokenizer/src/token.rs index bfda737..7127d95 100644 --- a/rust_compiler/libs/tokenizer/src/token.rs +++ b/rust_compiler/libs/tokenizer/src/token.rs @@ -39,7 +39,7 @@ impl From for Diagnostic { ..Default::default() } } - _ => todo!(), + _ => Diagnostic::default(), } } }