diff --git a/Cargo.lock b/Cargo.lock index b534c4b..6348b0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ "borsh-derive", "cfg_aliases", @@ -138,15 +138,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -226,7 +226,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -265,41 +265,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "ext-trait" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "extension-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" -dependencies = [ - "ext-trait", -] - -[[package]] -name = "extern-c" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23" - [[package]] name = "funty" version = "2.0.0" @@ -363,15 +328,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "inventory" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" -dependencies = [ - "rustversion", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -400,22 +356,6 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" -[[package]] -name = "macro_rules_attribute" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" -dependencies = [ - "macro_rules_attribute-proc_macro", - "paste", -] - -[[package]] -name = "macro_rules_attribute-proc_macro" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" - [[package]] name = "memchr" version = "2.7.6" @@ -470,12 +410,6 @@ dependencies = [ "tokenizer", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -495,16 +429,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -654,15 +578,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -675,56 +590,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "safer-ffi" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd" -dependencies = [ - "extern-c", - "inventory", - "libc", - "macro_rules_attribute", - "paste", - "safer_ffi-proc_macros", - "scopeguard", - "stabby", - "uninit", - "unwind_safe", - "with_builtin_macros", -] - -[[package]] -name = "safer_ffi-proc_macros" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156" -dependencies = [ - "macro_rules_attribute", - "prettyplease", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -751,7 +622,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -767,53 +638,12 @@ dependencies = [ "serde_core", ] -[[package]] -name = "sha2-const-stable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" - [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "stabby" -version = "36.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8" -dependencies = [ - "rustversion", - "stabby-abi", -] - -[[package]] -name = "stabby-abi" -version = "36.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a" -dependencies = [ - "rustc_version", - "rustversion", - "sha2-const-stable", - "stabby-macros", -] - -[[package]] -name = "stabby-macros" -version = "36.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "rand", - "syn 1.0.109", -] - [[package]] name = "stationlang" version = "0.1.0" @@ -824,7 +654,6 @@ dependencies = [ "parser", "quick-error", "rust_decimal", - "safer-ffi", "tokenizer", ] @@ -847,9 +676,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -922,21 +751,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "uninit" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" -dependencies = [ - "extension-traits", -] - -[[package]] -name = "unwind_safe" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" - [[package]] name = "utf8parse" version = "0.2.2" @@ -997,7 +811,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "wasm-bindgen-shared", ] @@ -1034,26 +848,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "with_builtin_macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" -dependencies = [ - "with_builtin_macros-proc_macros", -] - -[[package]] -name = "with_builtin_macros-proc_macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "wyz" version = "0.5.1" @@ -1071,20 +865,20 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.8.28" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.28" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] diff --git a/Cargo.toml b/Cargo.toml index dd79ed3..5939f95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = ["libs/*"] [workspace.dependencies] quick-error = "2" rust_decimal = "1" -safer-ffi = { version = "^0.1" } [profile.release] strip = true @@ -30,10 +29,7 @@ rust_decimal = { workspace = true } tokenizer = { path = "libs/tokenizer" } parser = { path = "libs/parser" } compiler = { path = "libs/compiler" } -safer-ffi = { workspace = true } -[features] -headers = ["safer-ffi/headers"] [dev-dependencies] anyhow = { version = "^1.0", features = ["backtrace"] } diff --git a/libs/compiler/src/test/loops.rs b/libs/compiler/src/test/loops.rs index c421a99..b9c38e3 100644 --- a/libs/compiler/src/test/loops.rs +++ b/libs/compiler/src/test/loops.rs @@ -111,7 +111,7 @@ fn test_while_loop() -> anyhow::Result<()> { fn test_loop_continue() -> anyhow::Result<()> { let compiled = compile! { debug - " + r#" let a = 0; loop { a = a + 1; @@ -120,7 +120,7 @@ fn test_loop_continue() -> anyhow::Result<()> { } break; } - " + "# }; // Labels: L1 (start), L2 (end), L3 (if end) diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 47893fc..6a4ba07 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -47,3 +47,4 @@ mod declaration_literal; mod function_declaration; mod logic_expression; mod loops; +mod syscall; diff --git a/libs/compiler/src/test/syscall.rs b/libs/compiler/src/test/syscall.rs new file mode 100644 index 0000000..7433880 --- /dev/null +++ b/libs/compiler/src/test/syscall.rs @@ -0,0 +1,159 @@ +use crate::compile; +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn test_yield() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + yield(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + yield + " + } + ); + + Ok(()) +} + +#[test] +fn test_sleep() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + sleep(3); + let sleepAmount = 15; + sleep(sleepAmount); + sleep(sleepAmount * 2); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + sleep 3 + move r8 15 #sleepAmount + sleep r8 + mul r1 r8 2 + sleep r1 + " + } + ); + + Ok(()) +} + +#[test] +fn test_set_on_device() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + device airConditioner = "d0"; + let internalTemp = 20c; + + setOnDevice(airConditioner, "On", internalTemp > 25c); + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 293.15 #internalTemp + sgt r1 r8 298.15 + s d0 On r1 + " + } + ); + + Ok(()) +} + +#[test] +fn test_set_on_device_batched() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + let doorHash = hash("Door"); + setOnDeviceBatched(doorHash, "Lock", true); + "# + }; + + assert_eq!( + compiled, + indoc! { + r#" + j main + main: + move r15 HASH("Door") #hash_ret + move r8 r15 #doorHash + sb r8 Lock 1 + "# + } + ); + Ok(()) +} + +#[test] +fn test_load_from_device() -> anyhow::Result<()> { + let compiled = compile! { + debug + r#" + device airCon = "d0"; + + let setting = loadFromDevice(airCon, "On"); + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r15 d0 On + move r8 r15 #setting + " + } + ); + + 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/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index 7329091..2b1709d 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -1,10 +1,11 @@ use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use parser::{ Parser as ASTParser, + sys_call::{SysCall, System}, tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, InvocationExpression, Literal, - LogicalExpression, LoopExpression, WhileExpression, + LiteralOrVariable, LogicalExpression, LoopExpression, WhileExpression, }, }; use quick_error::quick_error; @@ -152,6 +153,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.expression_loop(expr_loop, scope)?; Ok(None) } + Expression::Syscall(SysCall::System(system_syscall)) => { + self.expression_syscall_system(system_syscall, scope) + } Expression::While(expr_while) => { self.expression_while(expr_while, scope)?; Ok(None) @@ -169,11 +173,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(None) } Expression::Declaration(var_name, expr) => { - let loc = self.expression_declaration(var_name, *expr, scope)?; - Ok(loc.map(|l| CompilationResult { - location: l, - temp_name: None, - })) + self.expression_declaration(var_name, *expr, scope) } Expression::Assignment(assign_expr) => { self.expression_assignment(assign_expr, scope)?; @@ -284,23 +284,26 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { var_name: String, expr: Expression, scope: &mut VariableScope<'v>, - ) -> Result, Error> { + ) -> Result, Error> { // optimization. Check for a negated numeric literal if let Expression::Negation(box_expr) = &expr && let Expression::Literal(Literal::Number(neg_num)) = &**box_expr { let loc = scope.add_variable(&var_name, LocationRequest::Persist)?; self.emit_variable_assignment(&var_name, &loc, format!("-{neg_num}"))?; - return Ok(Some(loc)); + return Ok(Some(CompilationResult { + location: loc, + temp_name: None, + })); } - let loc = match expr { + let (loc, temp_name) = match expr { Expression::Literal(Literal::Number(num)) => { let var_location = scope.add_variable(var_name.clone(), LocationRequest::Persist)?; self.emit_variable_assignment(&var_name, &var_location, num)?; - var_location + (var_location, None) } Expression::Literal(Literal::Boolean(b)) => { let val = if b { "1" } else { "0" }; @@ -308,7 +311,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { scope.add_variable(var_name.clone(), LocationRequest::Persist)?; self.emit_variable_assignment(&var_name, &var_location, val)?; - var_location + (var_location, None) } Expression::Invocation(invoke_expr) => { self.expression_function_invocation(invoke_expr, scope)?; @@ -319,7 +322,21 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { &loc, format!("r{}", VariableScope::RETURN_REGISTER), )?; - loc + (loc, None) + } + Expression::Syscall(SysCall::System(call)) => { + if self.expression_syscall_system(call, scope)?.is_none() { + return Err(Error::Unknown("SysCall did not return a value".into())); + }; + + let loc = scope.add_variable(&var_name, LocationRequest::Persist)?; + self.emit_variable_assignment( + &var_name, + &loc, + format!("r{}", VariableScope::RETURN_REGISTER), + )?; + + (loc, None) } // Support assigning binary expressions to variables directly Expression::Binary(bin_expr) => { @@ -334,7 +351,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { if let Some(name) = result.temp_name { scope.free_temp(name)?; } - var_loc + (var_loc, None) } Expression::Logical(log_expr) => { let result = self.expression_logical(log_expr, scope)?; @@ -348,7 +365,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { if let Some(name) = result.temp_name { scope.free_temp(name)?; } - var_loc + (var_loc, None) } Expression::Variable(name) => { let src_loc = scope.get_location_of(&name)?; @@ -372,7 +389,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } }; self.emit_variable_assignment(&var_name, &var_loc, src_str)?; - var_loc + (var_loc, None) } Expression::Priority(inner) => { return self.expression_declaration(var_name, *inner, scope); @@ -384,7 +401,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } }; - Ok(Some(loc)) + Ok(Some(CompilationResult { + location: loc, + temp_name, + })) } fn expression_assignment<'v>( @@ -740,6 +760,18 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } } + fn compile_literal_or_variable( + &mut self, + val: LiteralOrVariable, + scope: &mut VariableScope, + ) -> Result<(String, Option), Error> { + let expr = match val { + LiteralOrVariable::Literal(l) => Expression::Literal(l), + LiteralOrVariable::Variable(v) => Expression::Variable(v), + }; + self.compile_operand(expr, scope) + } + fn expression_binary<'v>( &mut self, expr: BinaryExpression, @@ -978,6 +1010,127 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)) } + // syscalls that return values will be stored in the VariableScope::RETURN_REGISTER + // register + fn expression_syscall_system<'v>( + &mut self, + expr: System, + scope: &mut VariableScope<'v>, + ) -> Result, Error> { + match expr { + System::Yield => { + self.write_output("yield")?; + Ok(None) + } + System::Sleep(amt) => { + let (var, cleanup) = self.compile_operand(*amt, scope)?; + self.write_output(format!("sleep {var}"))?; + if let Some(temp) = cleanup { + scope.free_temp(temp)?; + } + + Ok(None) + } + System::Hash(hash_arg) => { + let Literal::String(str_lit) = hash_arg else { + return Err(Error::AgrumentMismatch( + "Arg1 expected to be a string literal.".into(), + )); + }; + + let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER); + self.emit_variable_assignment("hash_ret", &loc, format!(r#"HASH("{}")"#, str_lit))?; + + Ok(Some(CompilationResult { + location: loc, + temp_name: None, + })) + } + System::SetOnDevice(device, logic_type, variable) => { + let (variable, var_cleanup) = self.compile_operand(*variable, scope)?; + + let LiteralOrVariable::Variable(device) = device else { + return Err(Error::AgrumentMismatch( + "Arg1 expected to be a variable".into(), + )); + }; + + let Some(device) = self.devices.get(&device) else { + return Err(Error::InvalidDevice(device)); + }; + + let Literal::String(logic_type) = logic_type else { + return Err(Error::AgrumentMismatch( + "Arg2 expected to be a string".into(), + )); + }; + + self.write_output(format!("s {} {} {}", device, logic_type, variable))?; + + if let Some(temp_var) = var_cleanup { + scope.free_temp(temp_var)?; + } + + Ok(None) + } + System::SetOnDeviceBatched(device_hash, logic_type, variable) => { + let (var, var_cleanup) = self.compile_operand(*variable, scope)?; + let (device_hash, device_hash_cleanup) = + self.compile_literal_or_variable(device_hash, scope)?; + let Literal::String(logic_type) = logic_type else { + return Err(Error::AgrumentMismatch( + "Arg2 expected to be a string".into(), + )); + }; + + self.write_output(format!("sb {} {} {}", device_hash, 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)?; + } + + Ok(None) + } + System::LoadFromDevice(device, logic_type) => { + let LiteralOrVariable::Variable(device) = device else { + return Err(Error::AgrumentMismatch( + "Arg1 expected to be a variable".into(), + )); + }; + + let Some(device) = self.devices.get(&device) else { + return Err(Error::InvalidDevice(device)); + }; + + let Literal::String(logic_type) = logic_type else { + return Err(Error::AgrumentMismatch( + "Arg2 expected to be a string".into(), + )); + }; + + self.write_output(format!( + "l r{} {} {}", + VariableScope::RETURN_REGISTER, + device, + logic_type + ))?; + + Ok(Some(CompilationResult { + location: VariableLocation::Temporary(VariableScope::RETURN_REGISTER), + temp_name: None, + })) + } + + _ => { + todo!() + } + } + } + /// Compile a function declaration. /// Calees are responsible for backing up any registers they wish to use. fn expression_function<'v>( @@ -1092,4 +1245,3 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } } - diff --git a/libs/compiler/test_files/deviceIo.slang b/libs/compiler/test_files/deviceIo.slang new file mode 100644 index 0000000..dcbe855 --- /dev/null +++ b/libs/compiler/test_files/deviceIo.slang @@ -0,0 +1,9 @@ +device airConditioner = "d0"; +device gasSensor = "d1"; + +loop { + yield(); + let indoorTemp = loadFromDevice(gasSensor, "Temperature"); + let shouldSet = indoorTemp > 30c; + setOnDevice(airConditioner, "On", shouldSet); +} diff --git a/libs/compiler/test_files/math.slang b/libs/compiler/test_files/math.slang deleted file mode 100644 index 1d9adb2..0000000 --- a/libs/compiler/test_files/math.slang +++ /dev/null @@ -1,7 +0,0 @@ -fn addTemperatures(temp1, temp2) { - return temp1 + temp2; -}; - - -let newTemp1 = addTemperatures(15c, 120c); -let newTemp2 = addTemperatures(50c, 20c); diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 308a4d6..8eca23a 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -13,6 +13,8 @@ use tokenizer::{ }; use tree_node::*; +use crate::sys_call::System; + #[macro_export] /// A macro to create a boxed value. macro_rules! boxed { @@ -63,6 +65,12 @@ macro_rules! token_from_option { None => return Err(Error::UnexpectedEOF), } }; + (owned $token:expr) => { + match $token { + Some(token) => token, + None => return Err(Error::UnexpectedEOF), + } + }; } macro_rules! extract_token_data { @@ -1039,9 +1047,22 @@ impl Parser { } "sleep" => { check_length(self, &invocation.arguments, 1)?; - let mut arg = invocation.arguments.iter(); - let argument = literal_or_variable!(arg.next()); - Ok(SysCall::System(sys_call::System::Sleep(argument))) + let mut arg = invocation.arguments.into_iter(); + let expr = token_from_option!(owned arg.next()); + Ok(SysCall::System(System::Sleep(boxed!(expr)))) + } + "hash" => { + check_length(self, &invocation.arguments, 1)?; + 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( + token_from_option!(self.current_token).clone(), + )); + }; + + Ok(SysCall::System(System::Hash(lit_str))) } "loadFromDevice" => { check_length(self, &invocation.arguments, 2)?; @@ -1057,7 +1078,7 @@ impl Parser { Ok(SysCall::System(sys_call::System::LoadFromDevice( device, - LiteralOrVariable::Variable(variable.clone()), + Literal::String(variable.clone()), ))) } "loadBatch" => { @@ -1076,23 +1097,23 @@ impl Parser { } "loadBatchNamed" => { check_length(self, &invocation.arguments, 4)?; - let mut args = invocation.arguments.iter(); + let mut args = invocation.arguments.into_iter(); let device_hash = literal_or_variable!(args.next()); - let name_hash = get_arg!(Literal, literal_or_variable!(args.next())); + let name_hash = token_from_option!(owned args.next()); let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); let batch_mode = get_arg!(Literal, literal_or_variable!(args.next())); Ok(SysCall::System(sys_call::System::LoadBatchNamed( device_hash, - name_hash, + boxed!(name_hash), logic_type, batch_mode, ))) } "setOnDevice" => { check_length(self, &invocation.arguments, 3)?; - let mut args = invocation.arguments.iter(); + let mut args = invocation.arguments.into_iter(); let device = literal_or_variable!(args.next()); @@ -1104,12 +1125,54 @@ impl Parser { )); }; - let variable = literal_or_variable!(args.next()); + let variable = token_from_option!(owned args.next()); Ok(SysCall::System(sys_call::System::SetOnDevice( device, Literal::String(logic_type), - variable, + boxed!(variable), + ))) + } + "setOnDeviceBatched" => { + check_length(self, &invocation.arguments, 3)?; + let mut args = invocation.arguments.into_iter(); + + let device = literal_or_variable!(args.next()); + let Literal::String(logic_type) = + get_arg!(Literal, literal_or_variable!(args.next())) + else { + return Err(Error::UnexpectedToken( + token_from_option!(self.current_token).clone(), + )); + }; + let variable = token_from_option!(owned args.next()); + + Ok(SysCall::System(System::SetOnDeviceBatched( + device, + Literal::String(logic_type), + boxed!(variable), + ))) + } + "setOnDeviceBatchedNamed" => { + check_length(self, &invocation.arguments, 4)?; + let mut args = invocation.arguments.into_iter(); + + let device = literal_or_variable!(args.next()); + let name = literal_or_variable!(args.next()); + let Literal::String(logic_type) = + get_arg!(Literal, literal_or_variable!(args.next())) + else { + return Err(Error::UnexpectedToken( + token_from_option!(self.current_token).clone(), + )); + }; + let variable = token_from_option!(owned args.next()); + + Ok(SysCall::System(System::SetOnDeviceBatchedNamed( + device, + name, + Literal::String(logic_type), + boxed!(variable), ))) } // math calls @@ -1202,4 +1265,3 @@ impl Parser { } } } - diff --git a/libs/parser/src/sys_call.rs b/libs/parser/src/sys_call.rs index cb306ca..bb2c882 100644 --- a/libs/parser/src/sys_call.rs +++ b/libs/parser/src/sys_call.rs @@ -1,4 +1,4 @@ -use crate::tree_node::Literal; +use crate::tree_node::{Expression, Literal}; use super::LiteralOrVariable; @@ -102,25 +102,25 @@ pub enum System { /// Represents a function that can be called to sleep for a certain amount of time. /// ## In Game /// `sleep a(r?|num)` - Sleep(LiteralOrVariable), + Sleep(Box), /// Gets the in-game hash for a specific prefab name. /// ## In Game /// `HASH("prefabName")` - Hash(LiteralOrVariable), + Hash(Literal), /// Represents a function which loads a device variable into a register. /// ## In Game /// `l r? d? var` /// ## Examples /// `l r0 d0 Setting` /// `l r1 d5 Pressure` - LoadFromDevice(LiteralOrVariable, LiteralOrVariable), + LoadFromDevice(LiteralOrVariable, Literal), /// Function which gets a LogicType from all connected network devices that match /// the provided device hash and name, aggregating them via a batchMode /// ## In Game /// lbn r? deviceHash nameHash logicType batchMode /// ## Examples /// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum - LoadBatchNamed(LiteralOrVariable, Literal, Literal, Literal), + LoadBatchNamed(LiteralOrVariable, Box, Literal, Literal), /// Loads a LogicType from all connected network devices, aggregating them via a /// batchMode /// ## In Game @@ -133,7 +133,26 @@ pub enum System { /// `s d? logicType r?` /// ## Example /// `s d0 Setting r0` - SetOnDevice(LiteralOrVariable, Literal, LiteralOrVariable), + SetOnDevice(LiteralOrVariable, Literal, Box), + /// Represents a function which stores a setting to all devices that match + /// the given deviceHash + /// ## In Game + /// `sb deviceHash logictype r?` + /// ## Example + /// `sb HASH("Doors") Lock 1` + SetOnDeviceBatched(LiteralOrVariable, Literal, Box), + /// Represents a function which stores a setting to all devices that match + /// both the given deviceHash AND the given nameHash + /// ## In Game + /// `sbn deviceHash nameHash logicType r?` + /// ## Example + /// `sbn HASH("Doors") HASH("Exterior") Lock 1` + SetOnDeviceBatchedNamed( + LiteralOrVariable, + LiteralOrVariable, + Literal, + Box, + ), } impl std::fmt::Display for System { @@ -141,13 +160,19 @@ impl std::fmt::Display for System { match self { System::Yield => write!(f, "yield()"), System::Sleep(a) => write!(f, "sleep({})", a), - System::Hash(a) => write!(f, "HASH({})", a), + System::Hash(a) => write!(f, "hash({})", a), System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b), System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c), System::LoadBatchNamed(a, b, c, d) => { write!(f, "loadBatchNamed({}, {}, {}, {})", a, b, c, d) } System::SetOnDevice(a, b, c) => write!(f, "setOnDevice({}, {}, {})", a, b, c), + System::SetOnDeviceBatched(a, b, c) => { + write!(f, "setOnDeviceBatched({}, {}, {})", a, b, c) + } + System::SetOnDeviceBatchedNamed(a, b, c, d) => { + write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d) + } } } } @@ -175,9 +200,11 @@ impl SysCall { identifier, "yield" | "sleep" - | "HASH" + | "hash" | "loadFromDevice" | "setOnDevice" + | "setOnDeviceBatched" + | "setOnDeviceBatchedNamed" | "acos" | "asin" | "atan" diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index 9e0bfb8..d360bc0 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -76,6 +76,12 @@ impl From for Tokenizer { } } +impl From<&str> for Tokenizer { + fn from(value: &str) -> Self { + Self::from(value.to_string()) + } +} + impl Tokenizer { /// Consumes the tokenizer and returns the next token in the stream /// If there are no more tokens in the stream, this function returns None diff --git a/src/lib.rs b/src/lib.rs index 68a5650..e7c09ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,58 @@ use compiler::Compiler; use parser::Parser; -use safer_ffi::ffi_export; -use std::io::BufWriter; +use std::{ + ffi::{CStr, CString}, + io::BufWriter, +}; use tokenizer::Tokenizer; -#[ffi_export] -fn compile_from_string( - input: &safer_ffi::string::String, - output: &mut safer_ffi::string::String, -) -> bool { - let mut writer = BufWriter::new(Vec::new()); +/// Takes a raw pointer to a string and compiles the `slang` code into valid IC10 +/// # Safety +/// This must be called with a valid string pointer from C# (or wherever is calling this function) +#[no_mangle] +pub unsafe extern "C" fn compile_from_string( + input_ptr: *const std::os::raw::c_char, +) -> *mut std::os::raw::c_char { + if input_ptr.is_null() { + return std::ptr::null_mut(); + } - let tokenizer = Tokenizer::from(input.to_string()); + let c_str = unsafe { CStr::from_ptr(input_ptr) }; + + let Ok(input_str) = c_str.to_str() else { + return std::ptr::null_mut(); + }; + + let mut writer = BufWriter::new(Vec::new()); + let tokenizer = Tokenizer::from(input_str); let parser = Parser::new(tokenizer); let compiler = Compiler::new(parser, &mut writer, None); let Ok(()) = compiler.compile() else { - return false; + return std::ptr::null_mut(); }; let Ok(buffer) = writer.into_inner() else { - return false; + return std::ptr::null_mut(); }; - let Ok(output_string) = String::from_utf8(buffer) else { - return false; - }; + let c_string = CString::from_vec_unchecked(buffer); - *output = output_string.into(); - - return true; + c_string.into_raw() +} + +/// Takes ownership of the string pointer and drops it, freeing the memory +/// # Safety +/// Must be called with a valid string pointer +#[no_mangle] +pub unsafe extern "C" fn free_slang_string(input_ptr: *mut std::os::raw::c_char) { + if input_ptr.is_null() { + return; + } + + unsafe { + // Takes ownership of the input string, and then drops it immediately + let _ = CString::from_raw(input_ptr); + } }