Merge pull request #5 from dbidwell94/syscalls

Syscalls
This commit is contained in:
2025-11-26 15:35:22 -07:00
committed by GitHub
12 changed files with 510 additions and 287 deletions

236
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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"] }

View File

@@ -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)

View File

@@ -47,3 +47,4 @@ mod declaration_literal;
mod function_declaration;
mod logic_expression;
mod loops;
mod syscall;

View File

@@ -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(())
}

View File

@@ -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<Option<VariableLocation>, Error> {
) -> Result<Option<CompilationResult>, 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<String>), 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<Option<CompilationResult>, 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(())
}
}

View File

@@ -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);
}

View File

@@ -1,7 +0,0 @@
fn addTemperatures(temp1, temp2) {
return temp1 + temp2;
};
let newTemp1 = addTemperatures(15c, 120c);
let newTemp2 = addTemperatures(50c, 20c);

View File

@@ -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 {
}
}
}

View File

@@ -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<Expression>),
/// 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<Expression>, 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<Expression>),
/// 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<Expression>),
/// 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<Expression>,
),
}
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"

View File

@@ -76,6 +76,12 @@ impl From<String> 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

View File

@@ -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);
}
}