Merge pull request #18 from dbidwell94/syscalls
More Syscalls and Optimizations
This commit is contained in:
14
rust_compiler/Cargo.lock
generated
14
rust_compiler/Cargo.lock
generated
@@ -252,14 +252,25 @@ name = "compiler"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"helpers",
|
||||||
"indoc",
|
"indoc",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"parser",
|
"parser",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
|
"rust_decimal",
|
||||||
"tokenizer",
|
"tokenizer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diff"
|
name = "diff"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@@ -363,6 +374,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "helpers"
|
name = "helpers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ members = ["libs/*"]
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
quick-error = "2"
|
quick-error = "2"
|
||||||
rust_decimal = "1"
|
rust_decimal = "1"
|
||||||
safer-ffi = { version = "0.1" }
|
safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust
|
||||||
lsp-types = { version = "0.97" }
|
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]
|
[features]
|
||||||
headers = ["safer-ffi/headers"]
|
headers = ["safer-ffi/headers"]
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ edition = "2024"
|
|||||||
quick-error = { workspace = true }
|
quick-error = { workspace = true }
|
||||||
parser = { path = "../parser" }
|
parser = { path = "../parser" }
|
||||||
tokenizer = { path = "../tokenizer" }
|
tokenizer = { path = "../tokenizer" }
|
||||||
|
helpers = { path = "../helpers" }
|
||||||
lsp-types = { workspace = true }
|
lsp-types = { workspace = true }
|
||||||
|
rust_decimal = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { version = "1.0" }
|
anyhow = { version = "1.0" }
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ fn simple_binary_expression() -> anyhow::Result<()> {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
main:
|
main:
|
||||||
add r1 1 2
|
move r8 3 #i
|
||||||
move r8 r1 #i
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -72,7 +71,7 @@ fn nested_binary_expressions() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> {
|
fn stress_test_constant_folding() -> anyhow::Result<()> {
|
||||||
let compiled = compile! {
|
let compiled = compile! {
|
||||||
debug
|
debug
|
||||||
"
|
"
|
||||||
@@ -86,12 +85,35 @@ fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
main:
|
main:
|
||||||
add r1 -1 -2
|
move r8 -123 #negationHell
|
||||||
add r2 -5 -6
|
"
|
||||||
mul r3 -4 r2
|
}
|
||||||
add r4 -3 r3
|
);
|
||||||
mul r5 r1 r4
|
|
||||||
move r8 r5 #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
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -146,3 +146,25 @@ fn test_boolean_return() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,9 +112,8 @@ fn test_math_with_logic() -> anyhow::Result<()> {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
main:
|
main:
|
||||||
add r1 1 2
|
sgt r1 3 1
|
||||||
sgt r2 r1 1
|
move r8 r1 #logic
|
||||||
move r8 r2 #logic
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ fn test_set_on_device() -> anyhow::Result<()> {
|
|||||||
device airConditioner = "d0";
|
device airConditioner = "d0";
|
||||||
let internalTemp = 20c;
|
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! {
|
let compiled = compile! {
|
||||||
debug
|
debug
|
||||||
r#"
|
r#"
|
||||||
let doorHash = hash("Door");
|
const doorHash = hash("Door");
|
||||||
setOnDeviceBatched(doorHash, "Lock", true);
|
setBatched(doorHash, "Lock", true);
|
||||||
"#
|
"#
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,15 +99,39 @@ fn test_set_on_device_batched() -> anyhow::Result<()> {
|
|||||||
r#"
|
r#"
|
||||||
j main
|
j main
|
||||||
main:
|
main:
|
||||||
move r15 HASH("Door") #hash_ret
|
sb 718797587 Lock 1
|
||||||
move r8 r15 #doorHash
|
|
||||||
sb r8 Lock 1
|
|
||||||
"#
|
"#
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn test_load_from_device() -> anyhow::Result<()> {
|
fn test_load_from_device() -> anyhow::Result<()> {
|
||||||
let compiled = compile! {
|
let compiled = compile! {
|
||||||
@@ -115,7 +139,7 @@ fn test_load_from_device() -> anyhow::Result<()> {
|
|||||||
r#"
|
r#"
|
||||||
device airCon = "d0";
|
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(())
|
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(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#![allow(clippy::result_large_err)]
|
#![allow(clippy::result_large_err)]
|
||||||
use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope};
|
use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope};
|
||||||
|
use helpers::prelude::*;
|
||||||
use parser::{
|
use parser::{
|
||||||
Parser as ASTParser,
|
Parser as ASTParser,
|
||||||
sys_call::{SysCall, System},
|
sys_call::{SysCall, System},
|
||||||
tree_node::{
|
tree_node::{
|
||||||
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
||||||
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
||||||
InvocationExpression, Literal, LiteralOrVariable, LogicalExpression, LoopExpression,
|
InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression,
|
||||||
MemberAccessExpression, Span, Spanned, WhileExpression,
|
LoopExpression, MemberAccessExpression, Span, Spanned, WhileExpression,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use quick_error::quick_error;
|
use quick_error::quick_error;
|
||||||
@@ -15,6 +16,7 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{BufWriter, Write},
|
io::{BufWriter, Write},
|
||||||
};
|
};
|
||||||
|
use tokenizer::token::Number;
|
||||||
|
|
||||||
macro_rules! debug {
|
macro_rules! debug {
|
||||||
($self: expr, $debug_value: expr) => {
|
($self: expr, $debug_value: expr) => {
|
||||||
@@ -75,6 +77,9 @@ quick_error! {
|
|||||||
ConstAssignment(ident: String, span: Span) {
|
ConstAssignment(ident: String, span: Span) {
|
||||||
display("Attempted to re-assign a value to const variable `{ident}`")
|
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<Span>) {
|
Unknown(reason: String, span: Option<Span>) {
|
||||||
display("{reason}")
|
display("{reason}")
|
||||||
}
|
}
|
||||||
@@ -102,6 +107,7 @@ impl From<Error> for lsp_types::Diagnostic {
|
|||||||
| UnknownIdentifier(_, span)
|
| UnknownIdentifier(_, span)
|
||||||
| InvalidDevice(_, span)
|
| InvalidDevice(_, span)
|
||||||
| ConstAssignment(_, span)
|
| ConstAssignment(_, span)
|
||||||
|
| DeviceAssignment(_, span)
|
||||||
| AgrumentMismatch(_, span) => Diagnostic {
|
| AgrumentMismatch(_, span) => Diagnostic {
|
||||||
range: span.into(),
|
range: span.into(),
|
||||||
message: value.to_string(),
|
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
|
temp_name: None, // User variable, do not free
|
||||||
})),
|
})),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.errors
|
// fallback, check devices
|
||||||
.push(Error::UnknownIdentifier(name.node.clone(), name.span));
|
if let Some(device) = self.devices.get(&name.node) {
|
||||||
Ok(Some(CompilationResult {
|
Ok(Some(CompilationResult {
|
||||||
location: VariableLocation::Temporary(0),
|
location: VariableLocation::Device(device.clone()),
|
||||||
temp_name: None,
|
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,
|
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(())
|
Ok(())
|
||||||
@@ -557,15 +579,24 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
let result = self.expression_binary(bin_expr, scope)?;
|
let result = self.expression_binary(bin_expr, scope)?;
|
||||||
let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?;
|
let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?;
|
||||||
|
|
||||||
// Move result from temp to new persistent variable
|
if let CompilationResult {
|
||||||
let result_reg = self.resolve_register(&result.location)?;
|
location: VariableLocation::Constant(Literal::Number(num)),
|
||||||
self.emit_variable_assignment(&name_str, &var_loc, result_reg)?;
|
..
|
||||||
|
} = 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
|
// Free the temp result
|
||||||
if let Some(name) = result.temp_name {
|
if let Some(name) = result.temp_name {
|
||||||
scope.free_temp(name)?;
|
scope.free_temp(name)?;
|
||||||
|
}
|
||||||
|
(var_loc, None)
|
||||||
}
|
}
|
||||||
(var_loc, None)
|
|
||||||
}
|
}
|
||||||
Expression::Logical(log_expr) => {
|
Expression::Logical(log_expr) => {
|
||||||
let result = self.expression_logical(log_expr, scope)?;
|
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)
|
format!("r{}", VariableScope::TEMP_STACK_REGISTER)
|
||||||
}
|
}
|
||||||
VariableLocation::Constant(_) => unreachable!(),
|
VariableLocation::Constant(_) | VariableLocation::Device(_) => unreachable!(),
|
||||||
};
|
};
|
||||||
self.emit_variable_assignment(&name_str, &var_loc, src_str)?;
|
self.emit_variable_assignment(&name_str, &var_loc, src_str)?;
|
||||||
(var_loc, None)
|
(var_loc, None)
|
||||||
@@ -679,8 +710,27 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
value: const_value,
|
value: const_value,
|
||||||
} = expr;
|
} = 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 {
|
Ok(CompilationResult {
|
||||||
location: scope.define_const(const_name.node, const_value.node)?,
|
location: scope.define_const(const_name.node, value)?,
|
||||||
temp_name: None,
|
temp_name: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -735,6 +785,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
VariableLocation::Constant(_) => {
|
VariableLocation::Constant(_) => {
|
||||||
return Err(Error::ConstAssignment(identifier.node, identifier.span));
|
return Err(Error::ConstAssignment(identifier.node, identifier.span));
|
||||||
}
|
}
|
||||||
|
VariableLocation::Device(_) => {
|
||||||
|
return Err(Error::DeviceAssignment(identifier.node, identifier.span));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = cleanup {
|
if let Some(name) = cleanup {
|
||||||
@@ -849,6 +902,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
VariableScope::TEMP_STACK_REGISTER
|
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) => {
|
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(),
|
"Cannot resolve a constant value to register".into(),
|
||||||
None,
|
None,
|
||||||
)),
|
)),
|
||||||
|
VariableLocation::Device(_) => Err(Error::Unknown(
|
||||||
|
"Cannot resolve a device to a register".into(),
|
||||||
|
None,
|
||||||
|
)),
|
||||||
VariableLocation::Stack(_) => Err(Error::Unknown(
|
VariableLocation::Stack(_) => Err(Error::Unknown(
|
||||||
"Cannot resolve Stack location directly to register string without context".into(),
|
"Cannot resolve Stack location directly to register string without context".into(),
|
||||||
None,
|
None,
|
||||||
@@ -1122,6 +1185,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
if let Literal::Boolean(b) = spanned_lit.node {
|
if let Literal::Boolean(b) = spanned_lit.node {
|
||||||
return Ok((if b { "1".to_string() } else { "0".to_string() }, None));
|
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.
|
// 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.
|
// We return the NEW temp name to be freed.
|
||||||
Ok((temp_reg, Some(temp_name)))
|
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<BinaryExpression>,
|
expr: Spanned<BinaryExpression>,
|
||||||
scope: &mut VariableScope<'v>,
|
scope: &mut VariableScope<'v>,
|
||||||
) -> Result<CompilationResult, Error> {
|
) -> Result<CompilationResult, Error> {
|
||||||
|
fn fold_binary_expression(expr: &BinaryExpression) -> Option<Number> {
|
||||||
|
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<Number> {
|
||||||
|
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 {
|
let (op_str, left_expr, right_expr) = match expr.node {
|
||||||
BinaryExpression::Add(l, r) => ("add", l, r),
|
BinaryExpression::Add(l, r) => ("add", l, r),
|
||||||
BinaryExpression::Multiply(l, r) => ("mul", 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
|
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(_) => {
|
Err(_) => {
|
||||||
self.errors.push(Error::UnknownIdentifier(
|
self.errors.push(Error::UnknownIdentifier(
|
||||||
@@ -1507,30 +1632,43 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
span: Span,
|
span: Span,
|
||||||
scope: &mut VariableScope<'v>,
|
scope: &mut VariableScope<'v>,
|
||||||
) -> Result<Option<CompilationResult>, Error> {
|
) -> Result<Option<CompilationResult>, Error> {
|
||||||
|
macro_rules! cleanup {
|
||||||
|
($($to_clean:expr),*) => {
|
||||||
|
$(
|
||||||
|
if let Some(to_clean) = $to_clean {
|
||||||
|
scope.free_temp(to_clean)?;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
match expr {
|
match expr {
|
||||||
System::Yield => {
|
System::Yield => {
|
||||||
self.write_output("yield")?;
|
self.write_output("yield")?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
System::Sleep(amt) => {
|
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}"))?;
|
self.write_output(format!("sleep {var}"))?;
|
||||||
if let Some(temp) = cleanup {
|
|
||||||
scope.free_temp(temp)?;
|
cleanup!(var_cleanup);
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
System::Hash(hash_arg) => {
|
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(
|
return Err(Error::AgrumentMismatch(
|
||||||
"Arg1 expected to be a string literal.".into(),
|
"Arg1 expected to be a string literal.".into(),
|
||||||
span,
|
span,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER);
|
let loc = VariableLocation::Constant(Literal::Number(Number::Integer(
|
||||||
self.emit_variable_assignment("hash_ret", &loc, format!(r#"HASH("{}")"#, str_lit))?;
|
crc_hash_signed(&str_lit),
|
||||||
|
)));
|
||||||
|
|
||||||
Ok(Some(CompilationResult {
|
Ok(Some(CompilationResult {
|
||||||
location: loc,
|
location: loc,
|
||||||
@@ -1540,7 +1678,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
System::SetOnDevice(device, logic_type, variable) => {
|
System::SetOnDevice(device, logic_type, variable) => {
|
||||||
let (variable, var_cleanup) = self.compile_operand(*variable, scope)?;
|
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(
|
return Err(Error::AgrumentMismatch(
|
||||||
"Arg1 expected to be a variable".into(),
|
"Arg1 expected to be a variable".into(),
|
||||||
span,
|
span,
|
||||||
@@ -1562,7 +1704,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or("d0".to_string());
|
.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(
|
return Err(Error::AgrumentMismatch(
|
||||||
"Arg2 expected to be a string".into(),
|
"Arg2 expected to be a string".into(),
|
||||||
span,
|
span,
|
||||||
@@ -1571,17 +1717,19 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
|
|
||||||
self.write_output(format!("s {} {} {}", device_val, logic_type, variable))?;
|
self.write_output(format!("s {} {} {}", device_val, logic_type, variable))?;
|
||||||
|
|
||||||
if let Some(temp_var) = var_cleanup {
|
cleanup!(var_cleanup);
|
||||||
scope.free_temp(temp_var)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
System::SetOnDeviceBatched(device_hash, logic_type, variable) => {
|
System::SetOnDeviceBatched(device_hash, logic_type, variable) => {
|
||||||
let (var, var_cleanup) = self.compile_operand(*variable, scope)?;
|
let (var, var_cleanup) = self.compile_operand(*variable, scope)?;
|
||||||
let (device_hash_val, device_hash_cleanup) =
|
let (device_hash_val, device_hash_cleanup) =
|
||||||
self.compile_literal_or_variable(device_hash, scope)?;
|
self.compile_literal_or_variable(device_hash.node, scope)?;
|
||||||
let Literal::String(logic_type) = logic_type else {
|
let Spanned {
|
||||||
|
node: Literal::String(logic_type),
|
||||||
|
..
|
||||||
|
} = logic_type
|
||||||
|
else {
|
||||||
return Err(Error::AgrumentMismatch(
|
return Err(Error::AgrumentMismatch(
|
||||||
"Arg2 expected to be a string".into(),
|
"Arg2 expected to be a string".into(),
|
||||||
span,
|
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))?;
|
self.write_output(format!("sb {} {} {}", device_hash_val, logic_type, var))?;
|
||||||
|
|
||||||
if let Some(var_cleanup) = var_cleanup {
|
cleanup!(var_cleanup, device_hash_cleanup);
|
||||||
scope.free_temp(var_cleanup)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(device_cleanup) = device_hash_cleanup {
|
Ok(None)
|
||||||
scope.free_temp(device_cleanup)?;
|
}
|
||||||
}
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
System::LoadFromDevice(device, logic_type) => {
|
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(
|
return Err(Error::AgrumentMismatch(
|
||||||
"Arg1 expected to be a variable".into(),
|
"Arg1 expected to be a variable".into(),
|
||||||
span,
|
span,
|
||||||
@@ -1623,7 +1796,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or("d0".to_string());
|
.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(
|
return Err(Error::AgrumentMismatch(
|
||||||
"Arg2 expected to be a string".into(),
|
"Arg2 expected to be a string".into(),
|
||||||
span,
|
span,
|
||||||
@@ -1642,11 +1819,68 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
temp_name: None,
|
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(
|
self.write_output(format!(
|
||||||
format!("{t:?}\n\nNot yet implemented"),
|
"lb r{} {} {} {}",
|
||||||
Some(span),
|
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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ pub enum VariableLocation {
|
|||||||
Stack(u16),
|
Stack(u16),
|
||||||
/// Represents a constant value and should be directly substituted as such.
|
/// Represents a constant value and should be directly substituted as such.
|
||||||
Constant(Literal),
|
Constant(Literal),
|
||||||
|
/// Represents a device pin. This will contain the exact `d0-d5` string
|
||||||
|
Device(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VariableScope<'a> {
|
pub struct VariableScope<'a> {
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
crc32fast = { workspace = true }
|
||||||
|
|||||||
11
rust_compiler/libs/helpers/src/helper_funcs.rs
Normal file
11
rust_compiler/libs/helpers/src/helper_funcs.rs
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod helper_funcs;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod syscall;
|
mod syscall;
|
||||||
|
|
||||||
@@ -11,5 +12,6 @@ pub trait Documentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
pub use super::helper_funcs::*;
|
||||||
pub use super::{Documentation, documented, with_syscalls};
|
pub use super::{Documentation, documented, with_syscalls};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,16 @@
|
|||||||
macro_rules! with_syscalls {
|
macro_rules! with_syscalls {
|
||||||
($matcher:ident) => {
|
($matcher:ident) => {
|
||||||
$matcher!(
|
$matcher!(
|
||||||
|
// Big names
|
||||||
"yield",
|
"yield",
|
||||||
"sleep",
|
"sleep",
|
||||||
"hash",
|
"hash",
|
||||||
"loadFromDevice",
|
"load",
|
||||||
"loadBatchNamed",
|
"loadBatched",
|
||||||
"loadBatch",
|
"loadBatchedNamed",
|
||||||
"setOnDevice",
|
"set",
|
||||||
"setOnDeviceBatched",
|
"setBatched",
|
||||||
"setOnDeviceBatchedNamed",
|
"setBatchedNamed",
|
||||||
"acos",
|
"acos",
|
||||||
"asin",
|
"asin",
|
||||||
"atan",
|
"atan",
|
||||||
@@ -26,7 +27,14 @@ macro_rules! with_syscalls {
|
|||||||
"sin",
|
"sin",
|
||||||
"sqrt",
|
"sqrt",
|
||||||
"tan",
|
"tan",
|
||||||
"trunc"
|
"trunc",
|
||||||
|
// Lil' names
|
||||||
|
"l",
|
||||||
|
"lb",
|
||||||
|
"lbn",
|
||||||
|
"s",
|
||||||
|
"sb",
|
||||||
|
"sbn"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1256,17 +1256,47 @@ impl<'a> Parser<'a> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// literal value
|
// literal or syscall, making sure the syscall is supported in hash
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
let lit = self.spanned(|p| p.literal())?;
|
// cache the current token location
|
||||||
|
let current_token_index = self.tokenizer.loc();
|
||||||
|
|
||||||
Ok(ConstDeclarationExpression {
|
if let Ok(lit) = self.spanned(|p| p.literal()) {
|
||||||
name: Spanned {
|
Ok(ConstDeclarationExpression {
|
||||||
span: ident_span,
|
name: Spanned {
|
||||||
node: ident,
|
span: ident_span,
|
||||||
},
|
node: ident,
|
||||||
value: lit,
|
},
|
||||||
})
|
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<Expression, Error> {
|
fn declaration(&mut self) -> Result<Expression, Error> {
|
||||||
@@ -1556,19 +1586,26 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
macro_rules! literal_or_variable {
|
macro_rules! literal_or_variable {
|
||||||
($iter:expr) => {
|
($iter:expr) => {
|
||||||
match $iter {
|
match &$iter {
|
||||||
Some(expr) => match &expr.node {
|
Some(expr) => {
|
||||||
Expression::Literal(literal) => {
|
let span = expr.span;
|
||||||
LiteralOrVariable::Literal(literal.node.clone())
|
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(
|
return Err(Error::UnexpectedToken(
|
||||||
self.current_span(),
|
self.current_span(),
|
||||||
@@ -1581,12 +1618,15 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
macro_rules! get_arg {
|
macro_rules! get_arg {
|
||||||
($matcher: ident, $arg: expr) => {
|
($matcher: ident, $arg: expr) => {
|
||||||
match $arg {
|
match $arg.node {
|
||||||
LiteralOrVariable::$matcher(i) => i,
|
LiteralOrVariable::$matcher(i) => Spanned {
|
||||||
|
node: i,
|
||||||
|
span: $arg.span,
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidSyntax(
|
return Err(Error::InvalidSyntax(
|
||||||
self.current_span(),
|
$arg.span,
|
||||||
String::from("Expected a variable"),
|
format!("Expected a {}", stringify!($matcher).to_lowercase()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1611,37 +1651,48 @@ impl<'a> Parser<'a> {
|
|||||||
let mut args = invocation.arguments.into_iter();
|
let mut args = invocation.arguments.into_iter();
|
||||||
let lit_str = literal_or_variable!(args.next());
|
let lit_str = literal_or_variable!(args.next());
|
||||||
|
|
||||||
let LiteralOrVariable::Literal(lit_str) = lit_str else {
|
let Spanned {
|
||||||
return Err(Error::UnexpectedToken(
|
node: LiteralOrVariable::Literal(lit_str),
|
||||||
self.current_span(),
|
span,
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
} = 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)?;
|
check_length(self, &invocation.arguments, 2)?;
|
||||||
let mut args = invocation.arguments.into_iter();
|
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 next_arg = args.next();
|
||||||
|
|
||||||
let variable = match next_arg {
|
let variable = match next_arg {
|
||||||
Some(expr) => match expr.node {
|
Some(expr) => match expr.node {
|
||||||
Expression::Literal(spanned_lit) => match spanned_lit.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(
|
return Err(Error::InvalidSyntax(
|
||||||
self.current_span(),
|
spanned_lit.span,
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
"Expected a string literal".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::UnexpectedToken(
|
return Err(Error::InvalidSyntax(
|
||||||
self.current_span(),
|
expr.span,
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
"Expected a string literal".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1655,33 +1706,109 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
Ok(SysCall::System(sys_call::System::LoadFromDevice(
|
Ok(SysCall::System(sys_call::System::LoadFromDevice(
|
||||||
device,
|
device,
|
||||||
Literal::String(variable),
|
Spanned {
|
||||||
|
node: Literal::String(variable.node),
|
||||||
|
span: variable.span,
|
||||||
|
},
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
"setOnDevice" => {
|
"loadBatched" | "lb" => {
|
||||||
check_length(self, &invocation.arguments, 3)?;
|
check_length(self, &invocation.arguments, 3)?;
|
||||||
let mut args = invocation.arguments.into_iter();
|
let mut args = invocation.arguments.into_iter();
|
||||||
let device = literal_or_variable!(args.next());
|
let tmp = args.next();
|
||||||
let logic_type = get_arg!(Literal, literal_or_variable!(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)?;
|
let variable = args.next().ok_or(Error::UnexpectedEOF)?;
|
||||||
Ok(SysCall::System(sys_call::System::SetOnDevice(
|
Ok(SysCall::System(sys_call::System::SetOnDevice(
|
||||||
device,
|
device,
|
||||||
Literal::String(logic_type.to_string().replace("\"", "")),
|
Spanned {
|
||||||
|
node: Literal::String(logic_type.node.to_string().replace("\"", "")),
|
||||||
|
span: logic_type.span,
|
||||||
|
},
|
||||||
boxed!(variable),
|
boxed!(variable),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
"setOnDeviceBatched" => {
|
"setBatched" | "sb" => {
|
||||||
check_length(self, &invocation.arguments, 3)?;
|
check_length(self, &invocation.arguments, 3)?;
|
||||||
let mut args = invocation.arguments.into_iter();
|
let mut args = invocation.arguments.into_iter();
|
||||||
let device_hash = literal_or_variable!(args.next());
|
let tmp = args.next();
|
||||||
let logic_type = get_arg!(Literal, literal_or_variable!(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)?;
|
let variable = args.next().ok_or(Error::UnexpectedEOF)?;
|
||||||
|
|
||||||
Ok(SysCall::System(sys_call::System::SetOnDeviceBatched(
|
Ok(SysCall::System(sys_call::System::SetOnDeviceBatched(
|
||||||
device_hash,
|
device_hash,
|
||||||
Literal::String(logic_type.to_string().replace("\"", "")),
|
Spanned {
|
||||||
|
node: Literal::String(logic_type.to_string().replace("\"", "")),
|
||||||
|
span: logic_type.span,
|
||||||
|
},
|
||||||
boxed!(variable),
|
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(
|
_ => Err(Error::UnsupportedKeyword(
|
||||||
self.current_span(),
|
self.current_span(),
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
||||||
|
|||||||
@@ -142,58 +142,68 @@ documented! {
|
|||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `sleep(number|var);`
|
/// `sleep(number|var);`
|
||||||
Sleep(Box<Spanned<Expression>>),
|
Sleep(Box<Spanned<Expression>>),
|
||||||
/// 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
|
/// ## IC10
|
||||||
/// `HASH("prefabName")`
|
/// `HASH("prefabName")`
|
||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `HASH("prefabName");`
|
/// `hash("prefabName");`
|
||||||
Hash(Literal),
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// const compDoor = hash("StructureCompositeDoor");
|
||||||
|
/// setOnDeviceBatched(compDoor, "Lock", true);
|
||||||
|
/// ```
|
||||||
|
Hash(Spanned<Literal>),
|
||||||
/// Represents a function which loads a device variable into a register.
|
/// Represents a function which loads a device variable into a register.
|
||||||
/// ## IC10
|
/// ## IC10
|
||||||
/// `l r? d? var`
|
/// `l r? d? var`
|
||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `loadFromDevice(deviceType, "LogicType");`
|
/// `load(deviceType, "LogicType");`
|
||||||
LoadFromDevice(LiteralOrVariable, Literal),
|
LoadFromDevice(Spanned<LiteralOrVariable>, Spanned<Literal>),
|
||||||
/// Function which gets a LogicType from all connected network devices that match
|
/// Function which gets a LogicType from all connected network devices that match
|
||||||
/// the provided device hash and name, aggregating them via a batchMode
|
/// the provided device hash and name, aggregating them via a batchMode
|
||||||
/// ## IC10
|
/// ## IC10
|
||||||
/// `lbn r? deviceHash nameHash logicType batchMode`
|
/// `lbn r? deviceHash nameHash logicType batchMode`
|
||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `loadFromDeviceBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");`
|
/// `loadBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");`
|
||||||
LoadBatchNamed(
|
LoadBatchNamed(
|
||||||
LiteralOrVariable,
|
Spanned<LiteralOrVariable>,
|
||||||
Box<Spanned<Expression>>,
|
Spanned<LiteralOrVariable>,
|
||||||
Literal,
|
Spanned<Literal>,
|
||||||
Literal,
|
Spanned<Literal>,
|
||||||
),
|
),
|
||||||
/// Loads a LogicType from all connected network devices, aggregating them via a
|
/// Loads a LogicType from all connected network devices, aggregating them via a
|
||||||
/// batchMode
|
/// BatchMode
|
||||||
/// ## IC10
|
/// ## IC10
|
||||||
/// `lb r? deviceHash logicType batchMode`
|
/// `lb r? deviceHash logicType batchMode`
|
||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `loadFromDeviceBatched(deviceHash, "Variable", "LogicType");`
|
/// `loadBatched(deviceHash, "Variable", "LogicType");`
|
||||||
LoadBatch(LiteralOrVariable, Literal, Literal),
|
LoadBatch(Spanned<LiteralOrVariable>, Spanned<Literal>, Spanned<Literal>),
|
||||||
/// Represents a function which stores a setting into a specific device.
|
/// Represents a function which stores a setting into a specific device.
|
||||||
/// ## IC10
|
/// ## IC10
|
||||||
/// `s d? logicType r?`
|
/// `s d? logicType r?`
|
||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `setOnDevice(deviceType, "Variable", (number|var));`
|
/// `set(deviceType, "Variable", (number|var));`
|
||||||
SetOnDevice(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
|
SetOnDevice(Spanned<LiteralOrVariable>, Spanned<Literal>, Box<Spanned<Expression>>),
|
||||||
/// Represents a function which stores a setting to all devices that match
|
/// Represents a function which stores a setting to all devices that match
|
||||||
/// the given deviceHash
|
/// the given deviceHash
|
||||||
/// ## IC10
|
/// ## IC10
|
||||||
/// `sb deviceHash logicType r?`
|
/// `sb deviceHash logicType r?`
|
||||||
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
|
/// ## Slang
|
||||||
|
/// `setBatched(deviceHash, "LogicType", (number|var))`
|
||||||
|
SetOnDeviceBatched(Spanned<LiteralOrVariable>, Spanned<Literal>, Box<Spanned<Expression>>),
|
||||||
/// Represents a function which stores a setting to all devices that match
|
/// Represents a function which stores a setting to all devices that match
|
||||||
/// both the given deviceHash AND the given nameHash
|
/// both the given deviceHash AND the given nameHash
|
||||||
/// ## IC10
|
/// ## IC10
|
||||||
/// `sbn deviceHash nameHash logicType r?`
|
/// `sbn deviceHash nameHash logicType r?`
|
||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `setOnDeviceBatchedNamed(deviceType, nameHash, "LogicType", (number|var))`
|
/// `setBatchedNamed(deviceHash, nameHash, "LogicType", (number|var))`
|
||||||
SetOnDeviceBatchedNamed(
|
SetOnDeviceBatchedNamed(
|
||||||
LiteralOrVariable,
|
Spanned<LiteralOrVariable>,
|
||||||
LiteralOrVariable,
|
Spanned<LiteralOrVariable>,
|
||||||
Literal,
|
Spanned<Literal>,
|
||||||
Box<Spanned<Expression>>,
|
Box<Spanned<Expression>>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,3 +144,10 @@ fn test_binary_expression() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::sys_call;
|
||||||
|
|
||||||
use super::sys_call::SysCall;
|
use super::sys_call::SysCall;
|
||||||
use tokenizer::token::Number;
|
use tokenizer::token::Number;
|
||||||
|
|
||||||
@@ -10,6 +12,21 @@ pub enum Literal {
|
|||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum LiteralOr<T> {
|
||||||
|
Literal(Spanned<Literal>),
|
||||||
|
Or(Spanned<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::fmt::Display> std::fmt::Display for LiteralOr<T> {
|
||||||
|
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 {
|
impl std::fmt::Display for Literal {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -198,7 +215,14 @@ impl std::fmt::Display for LiteralOrVariable {
|
|||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct ConstDeclarationExpression {
|
pub struct ConstDeclarationExpression {
|
||||||
pub name: Spanned<String>,
|
pub name: Spanned<String>,
|
||||||
pub value: Spanned<Literal>,
|
pub value: LiteralOr<SysCall>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl std::fmt::Display for ConstDeclarationExpression {
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ pub struct TokenizerBuffer<'a> {
|
|||||||
tokenizer: Tokenizer<'a>,
|
tokenizer: Tokenizer<'a>,
|
||||||
buffer: VecDeque<Token>,
|
buffer: VecDeque<Token>,
|
||||||
history: VecDeque<Token>,
|
history: VecDeque<Token>,
|
||||||
|
index: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TokenizerBuffer<'a> {
|
impl<'a> TokenizerBuffer<'a> {
|
||||||
@@ -527,17 +528,22 @@ impl<'a> TokenizerBuffer<'a> {
|
|||||||
tokenizer,
|
tokenizer,
|
||||||
buffer: VecDeque::new(),
|
buffer: VecDeque::new(),
|
||||||
history: VecDeque::with_capacity(128),
|
history: VecDeque::with_capacity(128),
|
||||||
|
index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn next_token(&mut self) -> Result<Option<Token>, Error> {
|
pub fn next_token(&mut self) -> Result<Option<Token>, Error> {
|
||||||
if let Some(token) = self.buffer.pop_front() {
|
if let Some(token) = self.buffer.pop_front() {
|
||||||
self.history.push_back(token.clone());
|
self.history.push_back(token.clone());
|
||||||
|
self.index += 1;
|
||||||
return Ok(Some(token));
|
return Ok(Some(token));
|
||||||
}
|
}
|
||||||
let token = self.tokenizer.next_token()?;
|
let token = self.tokenizer.next_token()?;
|
||||||
|
|
||||||
if let Some(ref token) = token {
|
if let Some(ref token) = token {
|
||||||
self.history.push_back(token.clone());
|
self.history.push_back(token.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
pub fn peek(&mut self) -> Result<Option<Token>, Error> {
|
pub fn peek(&mut self) -> Result<Option<Token>, Error> {
|
||||||
@@ -547,12 +553,15 @@ impl<'a> TokenizerBuffer<'a> {
|
|||||||
let token = self.tokenizer.peek_next()?;
|
let token = self.tokenizer.peek_next()?;
|
||||||
Ok(token)
|
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::*;
|
use Ordering::*;
|
||||||
match seek_to.cmp(&0) {
|
match seek_to_int.cmp(&0) {
|
||||||
Greater => {
|
Greater => {
|
||||||
let mut tokens = Vec::with_capacity(seek_to as usize);
|
let mut tokens = Vec::with_capacity(seek_to_int as usize);
|
||||||
for _ in 0..seek_to {
|
for _ in 0..seek_to_int {
|
||||||
if let Some(token) = self.tokenizer.next_token()? {
|
if let Some(token) = self.tokenizer.next_token()? {
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
} else {
|
} else {
|
||||||
@@ -565,7 +574,7 @@ impl<'a> TokenizerBuffer<'a> {
|
|||||||
self.history.extend(tokens);
|
self.history.extend(tokens);
|
||||||
}
|
}
|
||||||
Less => {
|
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);
|
let mut tokens = Vec::with_capacity(seek_to);
|
||||||
for _ in 0..seek_to {
|
for _ in 0..seek_to {
|
||||||
if let Some(token) = self.history.pop_back() {
|
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());
|
self.buffer.extend(tokens.into_iter().rev());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -168,11 +168,86 @@ impl std::fmt::Display for TokenType {
|
|||||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||||
pub enum Number {
|
pub enum Number {
|
||||||
/// Represents an integer number
|
/// Represents an integer number
|
||||||
Integer(u128),
|
Integer(i128),
|
||||||
/// Represents a decimal type number with a precision of 64 bits
|
/// Represents a decimal type number with a precision of 64 bits
|
||||||
Decimal(Decimal),
|
Decimal(Decimal),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Number> 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 {
|
impl std::fmt::Display for Number {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
Reference in New Issue
Block a user