15 Commits

Author SHA1 Message Date
3a8f0e84af wip -- start indexing work 2025-12-21 00:47:47 -07:00
5dbb0ee2d7 Merge pull request #33 from dbidwell94/reagent
[0.3.4]

- Added support for `loadReagent`, which maps to the `lr` IC10 instruction
  - Shorthand is `lr`
  - Longform is `loadReagent`
- Update various Rust dependencies
- Added more optimizations, prioritizing `pop` instead of `get` when available
  when backing up / restoring registers for function invocations. This should
  save approximately 2 lines per backed up register
2025-12-17 21:18:16 -07:00
6b18489f54 Added more optimizations in regards to function invocations and backing
up and restoring registers
2025-12-17 21:05:01 -07:00
ecfed65221 Update rust dependencies 2025-12-17 18:02:34 -07:00
ed5ea9f6eb update changelog and version bump 2025-12-17 17:57:37 -07:00
0b354d4ec0 First pass getting loadReagent support into the compiler with optimizations 2025-12-17 17:49:34 -07:00
6c11c0e6e5 Merge pull request #31 from dbidwell94/30-temp-literal-negatives
0.3.3
2025-12-15 23:19:14 -07:00
88b6571659 update changelog and version bump 2025-12-15 23:15:41 -07:00
477c2b1aef Fixed bug where temperature literals were not being calculated correctly with negative numbers 2025-12-15 23:13:40 -07:00
941e81a3e5 Merge pull request #29 from dbidwell94/overflow-bug
Overflow bug
2025-12-14 03:27:12 -07:00
b98817c8a0 Fixed tests to show new line label convention for internal labels 2025-12-14 03:23:49 -07:00
6d5c179eac Fixed stack overflow due to improper handling of leaf functions 2025-12-14 03:16:58 -07:00
b7fbc499b6 WIP fix stack overflow 2025-12-14 02:54:56 -07:00
30b564a153 Merge pull request #28 from dbidwell94/key-not-found-exception
Fixed possible KeyNotFoundException
2025-12-13 01:42:26 -07:00
415e69628d Fixed possible KeyNotFoundException 2025-12-13 01:35:21 -07:00
27 changed files with 565 additions and 268 deletions

View File

@@ -1,5 +1,29 @@
# Changelog # Changelog
[0.3.4]
- Added support for `loadReagent`, which maps to the `lr` IC10 instruction
- Shorthand is `lr`
- Longform is `loadReagent`
- Update various Rust dependencies
- Added more optimizations, prioritizing `pop` instead of `get` when available
when backing up / restoring registers for function invocations. This should
save approximately 2 lines per backed up register
[0.3.3]
- Fixed bug where negative temperature literals were converted to Kelvin
first before applying the negative
[0.3.2]
- Fixed stack overflow due to incorrect optimization of 'leaf' functions
[0.3.1]
- Fixed possible `KeyNotFoundException` in C# code due to invalid
dictionary access when an IC housing has an error
[0.3.0] [0.3.0]
- Implemented a multi-pass optimizer - Implemented a multi-pass optimizer

View File

@@ -2,7 +2,7 @@
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Slang</Name> <Name>Slang</Name>
<Author>JoeDiertay</Author> <Author>JoeDiertay</Author>
<Version>0.3.0</Version> <Version>0.3.4</Version>
<Description> <Description>
[h1]Slang: High-Level Programming for Stationeers[/h1] [h1]Slang: High-Level Programming for Stationeers[/h1]

View File

@@ -56,6 +56,11 @@ public static class GlobalCode
return false; return false;
} }
if (!sourceMaps[reference].ContainsKey(icErrorLine))
{
return false;
}
var foundRange = sourceMaps[reference][icErrorLine]; var foundRange = sourceMaps[reference][icErrorLine];
if (foundRange is null) if (foundRange is null)

View File

@@ -41,7 +41,7 @@ namespace Slang
{ {
public const string PluginGuid = "com.biddydev.slang"; public const string PluginGuid = "com.biddydev.slang";
public const string PluginName = "Slang"; public const string PluginName = "Slang";
public const string PluginVersion = "0.3.0"; public const string PluginVersion = "0.3.4";
public static Mod MOD = new Mod(PluginName, PluginVersion); public static Mod MOD = new Mod(PluginName, PluginVersion);

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyName>StationeersSlang</AssemblyName> <AssemblyName>StationeersSlang</AssemblyName>
<Description>Slang Compiler Bridge</Description> <Description>Slang Compiler Bridge</Description>
<Version>0.3.0</Version> <Version>0.3.2</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -172,9 +172,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.0" version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]] [[package]]
name = "bytecheck" name = "bytecheck"
@@ -930,7 +930,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "slang" name = "slang"
version = "0.3.0" version = "0.3.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -1063,18 +1063,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.7.3" version = "0.7.4+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.23.7" version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "toml_datetime",
@@ -1084,9 +1084,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.0.4" version = "1.0.5+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c"
dependencies = [ dependencies = [
"winnow", "winnow",
] ]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "slang" name = "slang"
version = "0.3.0" version = "0.3.4"
edition = "2021" edition = "2021"
[workspace] [workspace]

View File

@@ -13,6 +13,6 @@ lsp-types = { workspace = true }
rust_decimal = { workspace = true } rust_decimal = { workspace = true }
[dev-dependencies] [dev-dependencies]
anyhow = { version = "1.0" } anyhow = { workspace = true }
indoc = { version = "2.0" } indoc = { version = "2.0" }
pretty_assertions = "1" pretty_assertions = "1"

View File

@@ -52,11 +52,9 @@ fn nested_binary_expressions() -> Result<()> {
add r1 r10 r9 add r1 r10 r9
mul r2 r1 r8 mul r2 r1 r8
move r15 r2 move r15 r2
j L1 j __internal_L1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
push 10 push 10
@@ -175,3 +173,52 @@ fn test_ternary_expression_assignment() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_negative_literals() -> Result<()> {
let compiled = compile!(
debug
r#"
let item = -10c - 20c;
"#
);
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 243.15
"
}
);
Ok(())
}
#[test]
fn test_mismatched_temperature_literals() -> Result<()> {
let compiled = compile!(
debug
r#"
let item = -10c - 100k;
let item2 = item + 500c;
"#
);
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 163.15
add r1 r8 773.15
move r9 r1
"
}
);
Ok(())
}

View File

@@ -22,9 +22,9 @@ fn test_if_statement() -> anyhow::Result<()> {
main: main:
move r8 10 move r8 10
sgt r1 r8 5 sgt r1 r8 5
beqz r1 L1 beqz r1 __internal_L1
move r8 20 move r8 20
L1: __internal_L1:
" "
} }
); );
@@ -54,12 +54,12 @@ fn test_if_else_statement() -> anyhow::Result<()> {
main: main:
move r8 0 move r8 0
sgt r1 10 5 sgt r1 10 5
beqz r1 L2 beqz r1 __internal_L2
move r8 1 move r8 1
j L1 j __internal_L1
L2: __internal_L2:
move r8 2 move r8 2
L1: __internal_L1:
" "
} }
); );
@@ -91,18 +91,18 @@ fn test_if_else_if_statement() -> anyhow::Result<()> {
main: main:
move r8 0 move r8 0
seq r1 r8 1 seq r1 r8 1
beqz r1 L2 beqz r1 __internal_L2
move r8 10 move r8 10
j L1 j __internal_L1
L2: __internal_L2:
seq r2 r8 2 seq r2 r8 2
beqz r2 L4 beqz r2 __internal_L4
move r8 20 move r8 20
j L3 j __internal_L3
L4: __internal_L4:
move r8 30 move r8 30
L3: __internal_L3:
L1: __internal_L1:
" "
} }
); );
@@ -145,10 +145,10 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
move r14 7 move r14 7
push 8 push 8
seq r1 r8 1 seq r1 r8 1
beqz r1 L1 beqz r1 __internal_L1
sub r0 sp 1 sub r0 sp 1
put db r0 99 put db r0 99
L1: __internal_L1:
sub sp sp 1 sub sp sp 1
" "
} }

View File

@@ -17,10 +17,8 @@ fn no_arguments() -> anyhow::Result<()> {
j main j main
doSomething: doSomething:
push ra push ra
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
jal doSomething jal doSomething
@@ -59,26 +57,22 @@ fn let_var_args() -> anyhow::Result<()> {
push ra push ra
mul r1 r8 2 mul r1 r8 2
move r15 r1 move r15 r1
j L1 j __internal_L1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
L2: __internal_L2:
move r8 123 move r8 123
push r8 push r8
push r8 push r8
jal mul2 jal mul2
sub r0 sp 1 pop r8
get r8 db r0
sub sp sp 1
move r9 r15 move r9 r15
pow r1 r9 2 pow r1 r9 2
move r9 r1 move r9 r1
j L2 j __internal_L2
L3: __internal_L3:
" "
} }
); );
@@ -127,11 +121,9 @@ fn inline_literal_args() -> anyhow::Result<()> {
pop r9 pop r9
push ra push ra
move r15 5 move r15 5
j L1 j __internal_L1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
move r8 123 move r8 123
@@ -139,9 +131,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
push 12 push 12
push 34 push 34
jal doSomething jal doSomething
sub r0 sp 1 pop r8
get r8 db r0
sub sp sp 1
move r9 r15 move r9 r15
" "
} }
@@ -170,10 +160,8 @@ fn mixed_args() -> anyhow::Result<()> {
pop r8 pop r8
pop r9 pop r9
push ra push ra
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
move r8 123 move r8 123
@@ -181,9 +169,7 @@ fn mixed_args() -> anyhow::Result<()> {
push r8 push r8
push 456 push 456
jal doSomething jal doSomething
sub r0 sp 1 pop r8
get r8 db r0
sub sp sp 1
move r9 r15 move r9 r15
" "
} }
@@ -214,11 +200,9 @@ fn with_return_statement() -> anyhow::Result<()> {
pop r8 pop r8
push ra push ra
move r15 456 move r15 456
j L1 j __internal_L1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
push 123 push 123
@@ -251,10 +235,8 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
doSomething: doSomething:
push ra push ra
move r15 -1 move r15 -1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
jal doSomething jal doSomething

View File

@@ -133,11 +133,9 @@ fn test_boolean_return() -> anyhow::Result<()> {
getTrue: getTrue:
push ra push ra
move r15 1 move r15 1
j L1 j __internal_L1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
jal getTrue jal getTrue

View File

@@ -5,7 +5,12 @@ use pretty_assertions::assert_eq;
fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#" let compiled = compile!(debug r#"
// we need more than 4 params to 'spill' into a stack var // we need more than 4 params to 'spill' into a stack var
fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {}; fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9;
};
let item1 = 1;
let returned = doSomething(item1, 2, 3, 4, 5, 6, 7, 8, 9);
"#); "#);
assert_eq!( assert_eq!(
@@ -21,11 +26,39 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
pop r13 pop r13
pop r14 pop r14
push ra push ra
L1: sub r0 sp 3
sub r0 sp 1 get r1 db r0
get ra db r0 sub r0 sp 2
sub sp sp 3 get r2 db r0
add r3 r1 r2
add r4 r3 r14
add r5 r4 r13
add r6 r5 r12
add r7 r6 r11
add r1 r7 r10
add r2 r1 r9
add r3 r2 r8
move r15 r3
j __internal_L1
__internal_L1:
pop ra
sub sp sp 2
j ra j ra
main:
move r8 1
push r8
push r8
push 2
push 3
push 4
push 5
push 6
push 7
push 8
push 9
jal doSomething
pop r8
move r9 r15
"} "}
); );
@@ -54,15 +87,13 @@ fn test_early_return() -> anyhow::Result<()> {
doSomething: doSomething:
push ra push ra
seq r1 1 1 seq r1 1 1
beqz r1 L2 beqz r1 __internal_L2
j L1 j __internal_L1
L2: __internal_L2:
move r8 3 move r8 3
j L1 j __internal_L1
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
jal doSomething jal doSomething
@@ -90,10 +121,8 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
pop r8 pop r8
pop r9 pop r9
push ra push ra
L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
"} "}
); );

View File

@@ -14,7 +14,7 @@ fn test_infinite_loop() -> anyhow::Result<()> {
" "
}; };
// Labels: L1 (start), L2 (end) // __internal_Labels: L1 (start), L2 (end)
assert_eq!( assert_eq!(
compiled, compiled,
indoc! { indoc! {
@@ -22,11 +22,11 @@ fn test_infinite_loop() -> anyhow::Result<()> {
j main j main
main: main:
move r8 0 move r8 0
L1: __internal_L1:
add r1 r8 1 add r1 r8 1
move r8 r1 move r8 r1
j L1 j __internal_L1
L2: __internal_L2:
" "
} }
); );
@@ -49,7 +49,7 @@ fn test_loop_break() -> anyhow::Result<()> {
" "
}; };
// Labels: L1 (start), L2 (end), L3 (if end - implicit else label) // __internal_Labels: L1 (start), L2 (end), L3 (if end - implicit else label)
assert_eq!( assert_eq!(
compiled, compiled,
indoc! { indoc! {
@@ -57,15 +57,15 @@ fn test_loop_break() -> anyhow::Result<()> {
j main j main
main: main:
move r8 0 move r8 0
L1: __internal_L1:
add r1 r8 1 add r1 r8 1
move r8 r1 move r8 r1
sgt r2 r8 10 sgt r2 r8 10
beqz r2 L3 beqz r2 __internal_L3
j L2 j __internal_L2
L3: __internal_L3:
j L1 j __internal_L1
L2: __internal_L2:
" "
} }
); );
@@ -85,7 +85,7 @@ fn test_while_loop() -> anyhow::Result<()> {
" "
}; };
// Labels: L1 (start), L2 (end) // __internal_Labels: L1 (start), L2 (end)
assert_eq!( assert_eq!(
compiled, compiled,
indoc! { indoc! {
@@ -93,13 +93,13 @@ fn test_while_loop() -> anyhow::Result<()> {
j main j main
main: main:
move r8 0 move r8 0
L1: __internal_L1:
slt r1 r8 10 slt r1 r8 10
beqz r1 L2 beqz r1 __internal_L2
add r2 r8 1 add r2 r8 1
move r8 r2 move r8 r2
j L1 j __internal_L1
L2: __internal_L2:
" "
} }
); );
@@ -123,7 +123,7 @@ fn test_loop_continue() -> anyhow::Result<()> {
"# "#
}; };
// Labels: L1 (start), L2 (end), L3 (if end) // __internal_Labels: L1 (start), L2 (end), L3 (if end)
assert_eq!( assert_eq!(
compiled, compiled,
indoc! { indoc! {
@@ -131,16 +131,16 @@ fn test_loop_continue() -> anyhow::Result<()> {
j main j main
main: main:
move r8 0 move r8 0
L1: __internal_L1:
add r1 r8 1 add r1 r8 1
move r8 r1 move r8 r1
slt r2 r8 5 slt r2 r8 5
beqz r2 L3 beqz r2 __internal_L3
j L1 j __internal_L1
L3: __internal_L3:
j L2 j __internal_L2
j L1 j __internal_L1
L2: __internal_L2:
" "
} }
); );

View File

@@ -208,3 +208,29 @@ fn test_set_slot() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_load_reagent() -> anyhow::Result<()> {
let compiled = compile! {
debug
r#"
device thingy = "d0";
let something = lr(thingy, "Contents", hash("Iron"));
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
lr r15 d0 Contents -666742878
move r8 r15
"
}
);
Ok(())
}

View File

@@ -15,7 +15,7 @@ use parser::{
use rust_decimal::Decimal; use rust_decimal::Decimal;
use std::{borrow::Cow, collections::HashMap}; use std::{borrow::Cow, collections::HashMap};
use thiserror::Error; use thiserror::Error;
use tokenizer::token::Number; use tokenizer::token::{Number, Unit};
fn extract_literal<'a>( fn extract_literal<'a>(
literal: Literal<'a>, literal: Literal<'a>,
@@ -264,7 +264,7 @@ impl<'a> Compiler<'a> {
fn next_label_name(&mut self) -> Cow<'a, str> { fn next_label_name(&mut self) -> Cow<'a, str> {
self.label_counter += 1; self.label_counter += 1;
Cow::from(format!("L{}", self.label_counter)) Cow::from(format!("__internal_L{}", self.label_counter))
} }
fn expression( fn expression(
@@ -811,7 +811,7 @@ impl<'a> Compiler<'a> {
.. ..
})), })),
.. ..
}) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash))), }) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash), Unit::None)),
LiteralOr::Or(Spanned { span, .. }) => { LiteralOr::Or(Spanned { span, .. }) => {
return Err(Error::Unknown( return Err(Error::Unknown(
"hash only supports string literals in this context.".into(), "hash only supports string literals in this context.".into(),
@@ -1115,43 +1115,26 @@ impl<'a> Compiler<'a> {
Some(name.span), Some(name.span),
)?; )?;
for register in active_registers { // cleanup spilled temporary variables
let VariableLocation::Stack(stack_offset) = stack let total_stack_usage = stack.stack_offset();
.get_location_of(&Cow::from(format!("temp_{register}")), None) let saved_regs_count = active_registers.len() as u16;
.map_err(Error::Scope)?
else { if total_stack_usage > saved_regs_count {
// This shouldn't happen if we just added it let spill_amount = total_stack_usage - saved_regs_count;
return Err(Error::Unknown(
format!("Failed to recover temp_{register}"),
Some(name.span),
));
};
self.write_instruction( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, Operand::StackPointer,
Operand::Number(stack_offset.into()), Operand::StackPointer,
), Operand::Number(spill_amount.into()),
Some(name.span),
)?;
self.write_instruction(
Instruction::Get(
Operand::Register(register),
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
), ),
Some(name.span), Some(name.span),
)?; )?;
} }
if stack.stack_offset() > 0 { // restore the registers in reverse order from the stack, now using `pop`
for register in active_registers.iter().rev() {
self.write_instruction( self.write_instruction(
Instruction::Sub( Instruction::Pop(Operand::Register(*register)),
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(Decimal::from(stack.stack_offset())),
),
Some(name.span), Some(name.span),
)?; )?;
} }
@@ -2022,6 +2005,7 @@ impl<'a> Compiler<'a> {
let loc = VariableLocation::Constant(Literal::Number(Number::Integer( let loc = VariableLocation::Constant(Literal::Number(Number::Integer(
crc_hash_signed(&str_lit), crc_hash_signed(&str_lit),
Unit::None,
))); )));
Ok(Some(CompileLocation { Ok(Some(CompileLocation {
@@ -2295,6 +2279,48 @@ impl<'a> Compiler<'a> {
Ok(None) Ok(None)
} }
System::LoadReagent(device, reagent_mode, reagent_hash) => {
let Spanned {
node: LiteralOrVariable::Variable(device_spanned),
..
} = device
else {
return Err(Error::AgrumentMismatch(
"Arg1 expected to be a variable".into(),
span,
));
};
let (device, device_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Variable(device_spanned),
scope,
)?;
let (reagent_mode, reagent_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(reagent_mode.node),
scope,
)?;
let (reagent_hash, reagent_hash_cleanup) =
self.compile_operand(*reagent_hash, scope)?;
self.write_instruction(
Instruction::LoadReagent(
Operand::Register(VariableScope::RETURN_REGISTER),
device,
reagent_mode,
reagent_hash,
),
Some(span),
)?;
cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup);
Ok(Some(CompileLocation {
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
temp_name: None,
}))
}
} }
} }
@@ -2692,6 +2718,21 @@ impl<'a> Compiler<'a> {
self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?;
if ra_stack_offset == 1 {
self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?;
let remaining_cleanup = block_scope.stack_offset() - 1;
if remaining_cleanup > 0 {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(remaining_cleanup.into()),
),
Some(span),
)?;
}
} else {
self.write_instruction( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Register(VariableScope::TEMP_STACK_REGISTER),
@@ -2720,6 +2761,7 @@ impl<'a> Compiler<'a> {
Some(span), Some(span),
)?; )?;
} }
}
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
Ok(()) Ok(())

View File

@@ -10,6 +10,7 @@ macro_rules! with_syscalls {
"loadBatched", "loadBatched",
"loadBatchedNamed", "loadBatchedNamed",
"loadSlot", "loadSlot",
"loadReagent",
"set", "set",
"setBatched", "setBatched",
"setBatchedNamed", "setBatchedNamed",
@@ -35,6 +36,7 @@ macro_rules! with_syscalls {
"lb", "lb",
"lbn", "lbn",
"ls", "ls",
"lr",
"s", "s",
"sb", "sb",
"sbn", "sbn",

View File

@@ -191,6 +191,9 @@ pub enum Instruction<'a> {
/// `sbn deviceHash nameHash type value` - Set Batch Named /// `sbn deviceHash nameHash type value` - Set Batch Named
StoreBatchNamed(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), StoreBatchNamed(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
/// `lr register device reagentMode int`
LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
/// `j label` - Unconditional Jump /// `j label` - Unconditional Jump
Jump(Operand<'a>), Jump(Operand<'a>),
/// `jal label` - Jump and Link (Function Call) /// `jal label` - Jump and Link (Function Call)
@@ -311,6 +314,9 @@ impl<'a> fmt::Display for Instruction<'a> {
Instruction::StoreBatchNamed(d_hash, n_hash, typ, val) => { Instruction::StoreBatchNamed(d_hash, n_hash, typ, val) => {
write!(f, "sbn {} {} {} {}", d_hash, n_hash, typ, val) write!(f, "sbn {} {} {} {}", d_hash, n_hash, typ, val)
} }
Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => {
write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash)
}
Instruction::Jump(lbl) => write!(f, "j {}", lbl), Instruction::Jump(lbl) => write!(f, "j {}", lbl),
Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl), Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
Instruction::JumpRelative(off) => write!(f, "jr {}", off), Instruction::JumpRelative(off) => write!(f, "jr {}", off),

View File

@@ -14,6 +14,10 @@ pub fn find_leaf_functions(instructions: &[InstructionNode]) -> HashSet<String>
for node in instructions { for node in instructions {
match &node.instruction { match &node.instruction {
Instruction::LabelDef(label) => { Instruction::LabelDef(label) => {
if label.starts_with("__internal_L") {
continue;
}
// If we were tracking a function, and it remained a leaf until now, save it. // If we were tracking a function, and it remained a leaf until now, save it.
if let Some(name) = current_label.take() if let Some(name) = current_label.take()
&& is_current_leaf && is_current_leaf

View File

@@ -99,7 +99,7 @@ fn optimize_leaf_functions<'a>(
// First scan: Identify instructions to remove and capture RA offsets // First scan: Identify instructions to remove and capture RA offsets
for (i, node) in input.iter().enumerate() { for (i, node) in input.iter().enumerate() {
match &node.instruction { match &node.instruction {
Instruction::LabelDef(label) => { Instruction::LabelDef(label) if !label.starts_with("__internal_L") => {
current_function = Some(label.to_string()); current_function = Some(label.to_string());
function_start_indices.insert(label.to_string(), i); function_start_indices.insert(label.to_string(), i);
} }
@@ -174,7 +174,9 @@ fn optimize_leaf_functions<'a>(
continue; // SKIP (Remove) continue; // SKIP (Remove)
} }
if let Instruction::LabelDef(l) = &node.instruction { if let Instruction::LabelDef(l) = &node.instruction
&& !l.starts_with("__internal_L")
{
processing_function = Some(l.to_string()); processing_function = Some(l.to_string());
} }
@@ -563,6 +565,7 @@ fn get_destination_reg(instr: &Instruction) -> Option<u8> {
| Instruction::Sqrt(Operand::Register(r), _) | Instruction::Sqrt(Operand::Register(r), _)
| Instruction::Tan(Operand::Register(r), _) | Instruction::Tan(Operand::Register(r), _)
| Instruction::Trunc(Operand::Register(r), _) | Instruction::Trunc(Operand::Register(r), _)
| Instruction::LoadReagent(Operand::Register(r), _, _, _)
| Instruction::Pop(Operand::Register(r)) => Some(*r), | Instruction::Pop(Operand::Register(r)) => Some(*r),
_ => None, _ => None,
} }
@@ -593,6 +596,9 @@ fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<Instr
c.clone(), c.clone(),
d.clone(), d.clone(),
)), )),
Instruction::LoadReagent(_, b, c, d) => {
Some(Instruction::LoadReagent(r, b.clone(), c.clone(), d.clone()))
}
Instruction::SetEq(_, a, b) => Some(Instruction::SetEq(r, a.clone(), b.clone())), Instruction::SetEq(_, a, b) => Some(Instruction::SetEq(r, a.clone(), b.clone())),
Instruction::SetNe(_, a, b) => Some(Instruction::SetNe(r, a.clone(), b.clone())), Instruction::SetNe(_, a, b) => Some(Instruction::SetNe(r, a.clone(), b.clone())),
Instruction::SetGt(_, a, b) => Some(Instruction::SetGt(r, a.clone(), b.clone())), Instruction::SetGt(_, a, b) => Some(Instruction::SetGt(r, a.clone(), b.clone())),
@@ -655,6 +661,14 @@ fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a), Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {
check(d_hash) || check(n_hash) || check(mode)
}
Instruction::SetEq(_, a, b) Instruction::SetEq(_, a, b)
| Instruction::SetNe(_, a, b) | Instruction::SetNe(_, a, b)
| Instruction::SetGt(_, a, b) | Instruction::SetGt(_, a, b)

View File

@@ -308,7 +308,7 @@ impl<'a> Parser<'a> {
Ok(Some(lhs)) Ok(Some(lhs))
} }
/// Handles dot notation chains: x.y.z() /// Handles dot notation chains (x.y.z()) and array indexing (x[0])
fn parse_postfix( fn parse_postfix(
&mut self, &mut self,
mut lhs: Spanned<Expression<'a>>, mut lhs: Spanned<Expression<'a>>,
@@ -411,6 +411,9 @@ impl<'a> Parser<'a> {
}), }),
}; };
} }
} else if self_matches_peek!(self, TokenType::Symbol(Symbol::LBracket)) {
// consume the `[` token
self.assign_next()?;
} else { } else {
break; break;
} }
@@ -1909,6 +1912,20 @@ impl<'a> Parser<'a> {
Box::new(expr), Box::new(expr),
))) )))
} }
"loadReagent" | "lr" => {
let mut args = args!(3);
let next = args.next();
let device = literal_or_variable!(next);
let next = args.next();
let reagent_mode = get_arg!(Literal, literal_or_variable!(next));
let reagent_hash = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::System(System::LoadReagent(
device,
reagent_mode,
Box::new(reagent_hash),
)))
}
// Math SysCalls // Math SysCalls
"acos" => { "acos" => {

View File

@@ -237,6 +237,18 @@ documented! {
Spanned<Literal<'a>>, Spanned<Literal<'a>>,
Spanned<Literal<'a>>, Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>> Box<Spanned<Expression<'a>>>
),
/// Loads reagent of device's ReagentMode where a hash of the reagent type to check for
///
/// ## IC10
/// `lr r? device(d?|r?|id) reagentMode int`
/// ## Slang
/// `let result = loadReagent(deviceHash, "ReagentMode", reagentHash);`
/// `let result = lr(deviceHash, "ReagentMode", reagentHash);`
LoadReagent(
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>
) )
} }
} }
@@ -261,6 +273,7 @@ impl<'a> std::fmt::Display for System<'a> {
} }
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c), System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d), System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
} }
} }
} }

View File

@@ -1,6 +1,6 @@
use tokenizer::Tokenizer;
use crate::Parser; use crate::Parser;
use pretty_assertions::assert_eq;
use tokenizer::Tokenizer;
#[test] #[test]
fn test_block() -> anyhow::Result<()> { fn test_block() -> anyhow::Result<()> {

View File

@@ -54,10 +54,7 @@ fn test_const_declaration() -> Result<()> {
let tokenizer = Tokenizer::from(input); let tokenizer = Tokenizer::from(input);
let mut parser = Parser::new(tokenizer); let mut parser = Parser::new(tokenizer);
assert_eq!( assert_eq!("(const item = 20c)", parser.parse()?.unwrap().to_string());
"(const item = 293.15)",
parser.parse()?.unwrap().to_string()
);
assert_eq!( assert_eq!(
"(const decimal = 200.15)", "(const decimal = 200.15)",

View File

@@ -174,6 +174,18 @@ impl<'a> std::fmt::Display for MemberAccessExpression<'a> {
} }
} }
#[derive(Debug, PartialEq, Eq)]
pub struct MemberIndexingExpression<'a> {
pub object: LiteralOrVariable<'a>,
pub index_of: Box<Spanned<Expression<'a>>>,
}
impl<'a> std::fmt::Display for MemberIndexingExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}[{}]", self.object, self.index_of)
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct MethodCallExpression<'a> { pub struct MethodCallExpression<'a> {
pub object: Box<Spanned<Expression<'a>>>, pub object: Box<Spanned<Expression<'a>>>,

View File

@@ -102,48 +102,6 @@ impl<'a> Token<'a> {
} }
} }
#[derive(Debug, PartialEq, Hash, Eq, Clone)]
pub enum Temperature {
Celsius(Number),
Fahrenheit(Number),
Kelvin(Number),
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Temperature::Celsius(n) => write!(f, "{}°C", n),
Temperature::Fahrenheit(n) => write!(f, "{}°F", n),
Temperature::Kelvin(n) => write!(f, "{}°K", n),
}
}
}
impl Temperature {
pub fn to_kelvin(self) -> Number {
match self {
Temperature::Celsius(n) => {
let n = match n {
Number::Integer(i) => Decimal::new(i as i64, 0),
Number::Decimal(d) => d,
};
Number::Decimal(n + Decimal::new(27315, 2))
}
Temperature::Fahrenheit(n) => {
let n = match n {
Number::Integer(i) => Decimal::new(i as i64, 0),
Number::Decimal(d) => d,
};
let a = n - Decimal::new(32, 0);
let b = Decimal::new(5, 0) / Decimal::new(9, 0);
Number::Decimal(a * b + Decimal::new(27315, 2))
}
Temperature::Kelvin(n) => n,
}
}
}
macro_rules! symbol { macro_rules! symbol {
($var:ident) => { ($var:ident) => {
|_| Symbol::$var |_| Symbol::$var
@@ -280,30 +238,27 @@ fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexE
span.end -= lexer.extras.line_start_index; span.end -= lexer.extras.line_start_index;
span.start -= lexer.extras.line_start_index; span.start -= lexer.extras.line_start_index;
let num = if clean_str.contains('.') { let unit = match suffix {
Number::Decimal( Some('c') => Unit::Celsius,
Some('f') => Unit::Fahrenheit,
Some('k') => Unit::Kelvin,
_ => Unit::None,
};
if clean_str.contains('.') {
Ok(Number::Decimal(
clean_str clean_str
.parse::<Decimal>() .parse::<Decimal>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?, .map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
) unit,
))
} else { } else {
Number::Integer( Ok(Number::Integer(
clean_str clean_str
.parse::<i128>() .parse::<i128>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?, .map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
) unit,
}; ))
if let Some(suffix) = suffix {
Ok(match suffix {
'c' => Temperature::Celsius(num),
'f' => Temperature::Fahrenheit(num),
'k' => Temperature::Kelvin(num),
_ => unreachable!(),
}
.to_kelvin())
} else {
Ok(num)
} }
} }
@@ -395,25 +350,55 @@ impl<'a> std::fmt::Display for TokenType<'a> {
} }
} }
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
pub enum Unit {
None,
Celsius,
Fahrenheit,
Kelvin,
}
#[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(i128), Integer(i128, Unit),
/// 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, Unit),
}
impl Number {
pub fn unit(&self) -> Unit {
match self {
Number::Integer(_, u) => *u,
Number::Decimal(_, u) => *u,
}
}
pub fn has_unit(&self) -> bool {
self.unit() != Unit::None
}
} }
impl From<bool> for Number { impl From<bool> for Number {
fn from(value: bool) -> Self { fn from(value: bool) -> Self {
Self::Integer(if value { 1 } else { 0 }) Self::Integer(if value { 1 } else { 0 }, Unit::None)
} }
} }
impl From<Number> for Decimal { impl From<Number> for Decimal {
fn from(value: Number) -> Self { fn from(value: Number) -> Self {
match value { let (val, unit) = match value {
Number::Decimal(d) => d, Number::Decimal(d, u) => (d, u),
Number::Integer(i) => Decimal::from(i), Number::Integer(i, u) => (Decimal::from(i), u),
};
match unit {
Unit::None | Unit::Kelvin => val,
Unit::Celsius => val + Decimal::new(27315, 2),
Unit::Fahrenheit => {
(val - Decimal::new(32, 0)) * Decimal::new(5, 0) / Decimal::new(9, 0)
+ Decimal::new(27315, 2)
}
} }
} }
} }
@@ -423,22 +408,48 @@ impl std::ops::Neg for Number {
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
match self { match self {
Self::Integer(i) => Self::Integer(-i), Self::Integer(i, u) => Self::Integer(-i, u),
Self::Decimal(d) => Self::Decimal(-d), Self::Decimal(d, u) => Self::Decimal(-d, u),
} }
} }
} }
fn determine_target_unit(lhs_unit: Unit, rhs_unit: Unit) -> Option<Unit> {
if lhs_unit == rhs_unit {
return Some(lhs_unit);
}
if lhs_unit != Unit::None && rhs_unit == Unit::None {
return Some(lhs_unit);
}
if lhs_unit == Unit::None && rhs_unit != Unit::None {
return Some(rhs_unit);
}
// Mismatched units (C + F) -> Fallback to Kelvin/None
None
}
impl std::ops::Add for Number { impl std::ops::Add for Number {
type Output = Number; type Output = Number;
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) { // If we can determine a common target unit (e.g. C + C = C, or C + Scalar = C),
(Self::Integer(l), Self::Integer(r)) => Number::Integer(l + r), // we preserve that unit. Otherwise, we convert to Kelvin (Decimal) and return Unit::None.
(Self::Decimal(l), Self::Decimal(r)) => Number::Decimal(l + r), if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
(Self::Integer(l), Self::Decimal(r)) => Number::Decimal(Decimal::from(l) + r), return match (self, rhs) {
(Self::Decimal(l), Self::Integer(r)) => Number::Decimal(l + Decimal::from(r)), (Self::Integer(l, _), Self::Integer(r, _)) => Number::Integer(l + r, target_unit),
(Self::Decimal(l, _), Self::Decimal(r, _)) => Number::Decimal(l + r, target_unit),
(Self::Integer(l, _), Self::Decimal(r, _)) => {
Number::Decimal(Decimal::from(l) + r, target_unit)
} }
(Self::Decimal(l, _), Self::Integer(r, _)) => {
Number::Decimal(l + Decimal::from(r), target_unit)
}
};
}
let l: Decimal = self.into();
let r: Decimal = rhs.into();
Number::Decimal(l + r, Unit::None)
} }
} }
@@ -446,12 +457,22 @@ impl std::ops::Sub for Number {
type Output = Number; type Output = Number;
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
match (self, rhs) { if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
(Self::Integer(l), Self::Integer(r)) => Self::Integer(l - r), return match (self, rhs) {
(Self::Decimal(l), Self::Integer(r)) => Self::Decimal(l - Decimal::from(r)), (Self::Integer(l, _), Self::Integer(r, _)) => Number::Integer(l - r, target_unit),
(Self::Integer(l), Self::Decimal(r)) => Self::Decimal(Decimal::from(l) - r), (Self::Decimal(l, _), Self::Decimal(r, _)) => Number::Decimal(l - r, target_unit),
(Self::Decimal(l), Self::Decimal(r)) => Self::Decimal(l - r), (Self::Integer(l, _), Self::Decimal(r, _)) => {
Number::Decimal(Decimal::from(l) - r, target_unit)
} }
(Self::Decimal(l, _), Self::Integer(r, _)) => {
Number::Decimal(l - Decimal::from(r), target_unit)
}
};
}
let l: Decimal = self.into();
let r: Decimal = rhs.into();
Number::Decimal(l - r, Unit::None)
} }
} }
@@ -459,12 +480,26 @@ impl std::ops::Mul for Number {
type Output = Number; type Output = Number;
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
match (self, rhs) { if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
(Number::Integer(l), Number::Integer(r)) => Number::Integer(l * r), return match (self, rhs) {
(Number::Integer(l), Number::Decimal(r)) => Number::Decimal(Decimal::from(l) * r), (Number::Integer(l, _), Number::Integer(r, _)) => {
(Number::Decimal(l), Number::Integer(r)) => Number::Decimal(l * Decimal::from(r)), Number::Integer(l * r, target_unit)
(Number::Decimal(l), Number::Decimal(r)) => Number::Decimal(l * r),
} }
(Number::Integer(l, _), Number::Decimal(r, _)) => {
Number::Decimal(Decimal::from(l) * r, target_unit)
}
(Number::Decimal(l, _), Number::Integer(r, _)) => {
Number::Decimal(l * Decimal::from(r), target_unit)
}
(Number::Decimal(l, _), Number::Decimal(r, _)) => {
Number::Decimal(l * r, target_unit)
}
};
}
let l: Decimal = self.into();
let r: Decimal = rhs.into();
Number::Decimal(l * r, Unit::None)
} }
} }
@@ -472,7 +507,22 @@ impl std::ops::Div for Number {
type Output = Number; type Output = Number;
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
Number::Decimal(Decimal::from(self) / Decimal::from(rhs)) if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
// Division always promotes to Decimal
let l_val = match self {
Self::Integer(i, _) => Decimal::from(i),
Self::Decimal(d, _) => d,
};
let r_val = match rhs {
Self::Integer(i, _) => Decimal::from(i),
Self::Decimal(d, _) => d,
};
return Number::Decimal(l_val / r_val, target_unit);
}
let l: Decimal = self.into();
let r: Decimal = rhs.into();
Number::Decimal(l / r, Unit::None)
} }
} }
@@ -480,15 +530,36 @@ impl std::ops::Rem for Number {
type Output = Number; type Output = Number;
fn rem(self, rhs: Self) -> Self::Output { fn rem(self, rhs: Self) -> Self::Output {
Number::Decimal(Decimal::from(self) % Decimal::from(rhs)) if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
let l_val = match self {
Self::Integer(i, _) => Decimal::from(i),
Self::Decimal(d, _) => d,
};
let r_val = match rhs {
Self::Integer(i, _) => Decimal::from(i),
Self::Decimal(d, _) => d,
};
return Number::Decimal(l_val % r_val, target_unit);
}
let l: Decimal = self.into();
let r: Decimal = rhs.into();
Number::Decimal(l % r, Unit::None)
} }
} }
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 { let (val, unit) = match self {
Number::Integer(i) => write!(f, "{}", i), Number::Integer(i, u) => (i.to_string(), u),
Number::Decimal(d) => write!(f, "{}", d), Number::Decimal(d, u) => (d.to_string(), u),
};
match unit {
Unit::None => write!(f, "{}", val),
Unit::Celsius => write!(f, "{}c", val),
Unit::Fahrenheit => write!(f, "{}f", val),
Unit::Kelvin => write!(f, "{}k", val),
} }
} }
} }
@@ -771,3 +842,4 @@ documented! {
While, While,
} }
} }

View File

@@ -53,6 +53,9 @@ struct Args {
/// The output file for the compiled program. If not set, output will go to stdout. /// The output file for the compiled program. If not set, output will go to stdout.
#[arg(short, long)] #[arg(short, long)]
output_file: Option<PathBuf>, output_file: Option<PathBuf>,
/// Should Slang attempt to optimize the output?
#[arg(short = 'z', long)]
optimize: bool,
} }
fn run_logic<'a>() -> Result<(), Error<'a>> { fn run_logic<'a>() -> Result<(), Error<'a>> {
@@ -107,7 +110,11 @@ fn run_logic<'a>() -> Result<(), Error<'a>> {
} }
} }
if args.optimize {
optimizer::optimize(instructions).write(&mut writer)?; optimizer::optimize(instructions).write(&mut writer)?;
} else {
instructions.write(&mut writer)?;
}
writer.flush()?; writer.flush()?;