Merge pull request #18 from dbidwell94/syscalls

More Syscalls and Optimizations
This commit is contained in:
2025-12-06 02:13:23 -07:00
committed by GitHub
19 changed files with 741 additions and 170 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,6 +352,13 @@ 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(_) => {
// fallback, check devices
if let Some(device) = self.devices.get(&name.node) {
Ok(Some(CompilationResult {
location: VariableLocation::Device(device.clone()),
temp_name: None,
}))
} else {
self.errors self.errors
.push(Error::UnknownIdentifier(name.node.clone(), name.span)); .push(Error::UnknownIdentifier(name.node.clone(), name.span));
Ok(Some(CompilationResult { Ok(Some(CompilationResult {
@@ -355,6 +368,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
} }
} }
} }
}
Expression::MemberAccess(access) => { Expression::MemberAccess(access) => {
// "load" behavior (e.g. `let x = d0.On`) // "load" behavior (e.g. `let x = d0.On`)
let MemberAccessExpression { object, member } = access.node; let MemberAccessExpression { object, member } = access.node;
@@ -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,6 +579,14 @@ 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)?;
if let CompilationResult {
location: VariableLocation::Constant(Literal::Number(num)),
..
} = result
{
self.emit_variable_assignment(&name_str, &var_loc, num)?;
(var_loc, None)
} else {
// Move result from temp to new persistent variable // Move result from temp to new persistent variable
let result_reg = self.resolve_register(&result.location)?; let result_reg = self.resolve_register(&result.location)?;
self.emit_variable_assignment(&name_str, &var_loc, result_reg)?; self.emit_variable_assignment(&name_str, &var_loc, result_reg)?;
@@ -567,6 +597,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
} }
(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)?;
let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?; let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?;
@@ -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,
}))
}
} }
} }

View File

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

View File

@@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
crc32fast = { workspace = true }

View 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
}

View File

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

View File

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

View File

@@ -1256,18 +1256,48 @@ 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();
if let Ok(lit) = self.spanned(|p| p.literal()) {
Ok(ConstDeclarationExpression {
name: Spanned {
span: ident_span,
node: ident,
},
value: LiteralOr::Literal(lit),
})
} else {
// we need to rewind our tokenizer to our previous location
self.tokenizer.seek(SeekFrom::Current(
self.tokenizer.loc() - current_token_index,
))?;
let syscall = self.spanned(|p| p.syscall())?;
if !matches!(
syscall,
Spanned {
node: SysCall::System(sys_call::System::Hash(_)),
..
}
) {
return Err(Error::UnexpectedToken(
syscall.span,
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
));
}
Ok(ConstDeclarationExpression { Ok(ConstDeclarationExpression {
name: Spanned { name: Spanned {
span: ident_span, span: ident_span,
node: ident, node: ident,
}, },
value: lit, value: LiteralOr::Or(syscall),
}) })
} }
}
fn declaration(&mut self) -> Result<Expression, Error> { fn declaration(&mut self) -> Result<Expression, Error> {
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?; let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
@@ -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 {
Expression::Variable(ident) => LiteralOrVariable::Variable(ident.clone()), span,
_ => { node: LiteralOrVariable::Literal(literal.node.clone()),
return Err(Error::UnexpectedToken(
self.current_span(),
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
))
}
}, },
Expression::Variable(ident) => Spanned {
span,
node: LiteralOrVariable::Variable(ident.clone()),
},
_ => {
return Err(Error::InvalidSyntax(
expr.span,
"Expected a literal or variable".to_string(),
));
}
}
}
_ => { _ => {
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)?,

View File

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

View File

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

View File

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

View File

@@ -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());
} }
_ => {} _ => {}

View File

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