13 Commits

Author SHA1 Message Date
15752fde3d Merge pull request #34 from dbidwell94/inline-ic10
Inline ic10
2025-12-20 19:07:45 -07:00
badcdd3c31 Removed unneeded array sort 2025-12-20 17:47:35 -07:00
f0e7506905 Remove dead code and change some comments 2025-12-20 17:35:22 -07:00
0962b3a5e7 highlight background of IC10 for the current caret position of the Slang script 2025-12-20 17:32:20 -07:00
1439f9ee7e Remove conditional IC10 formatting 2025-12-19 21:58:47 -07:00
3f105ef35c update changelog and version bump 2025-12-19 21:08:19 -07:00
45a7a6b38b wip -- show ic10 alongside of Slang 2025-12-19 20:46:24 -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
18 changed files with 357 additions and 109 deletions

View File

@@ -1,5 +1,21 @@
# Changelog # Changelog
[0.4.0]
- First pass getting compiled IC10 to output along side the Slang source code
- IC10 side is currently not scrollable, and text might be cut off from the bottom,
requiring newlines to be added to the bottom of the Slang source if needed
[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] [0.3.3]
- Fixed bug where negative temperature literals were converted to Kelvin - Fixed bug where negative temperature literals were converted to Kelvin

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.3</Version> <Version>0.4.0</Version>
<Description> <Description>
[h1]Slang: High-Level Programming for Stationeers[/h1] [h1]Slang: High-Level Programming for Stationeers[/h1]
@@ -23,6 +23,8 @@ Slang (Stationeers Language) brings modern programming to Stationeers. It allows
[*] [b]Optimizations:[/b] Features like Constant Folding calculate math at compile time to save instructions. [*] [b]Optimizations:[/b] Features like Constant Folding calculate math at compile time to save instructions.
[*] [b]Device Aliasing:[/b] Simple mapping: device sensor = "d0". [*] [b]Device Aliasing:[/b] Simple mapping: device sensor = "d0".
[*] [b]Temperature Literals:[/b] Don't worry about converting Celsius to Kelvin anymore. Define your temperatures as whatever you want and append the proper suffix at the end (ex. 20c, 68f, 293.15k) [*] [b]Temperature Literals:[/b] Don't worry about converting Celsius to Kelvin anymore. Define your temperatures as whatever you want and append the proper suffix at the end (ex. 20c, 68f, 293.15k)
[*] [b]Side-by-side IC10 output:[/b] Preview the compiled IC10 alongside the Slang source code. What you see is what you get.
[*] [b]Compiler Optimizations:[/b] Slang now does its best to safely optimize the output IC10, removing labels, unnecessary moves, etc.
[/list] [/list]
[h2]Installation[/h2] [h2]Installation[/h2]
@@ -50,16 +52,12 @@ loop {
[h2]Known Issues (Beta)[/h2] [h2]Known Issues (Beta)[/h2]
[list] [list]
[*] [b]Code Size:[/b] Compiled output is currently more verbose than hand-optimized assembly. Optimization passes are planned. [*] [b]Stack Access:[/b] Direct stack memory access is disabled to prevent conflicts with the compiler's internal memory management. A workaround is being planned.
[*] [b]Stack Access:[/b] Direct stack memory access is disabled to prevent conflicts with the compiler's internal memory management.
[*] [b]Documentation:[/b] In-game tooltips for syscalls (like load, set) are WIP. Check the "Slang" entry in the Stationpedia (F1) for help. [*] [b]Documentation:[/b] In-game tooltips for syscalls (like load, set) are WIP. Check the "Slang" entry in the Stationpedia (F1) for help.
[*] [b]Debugging:[/b] Runtime errors currently point to the compiled IC10 line number, not your Slang source line. Source mapping is coming soon.
[/list] [/list]
[h2]Planned Features[/h2] [h2]Planned Features[/h2]
[list] [list]
[*] Side-by-side view: Slang vs. Compiled IC10.
[*] Compiler optimizations (dead code elimination, smarter register allocation).
[*] Enhanced LSP features (Autocomplete, Go to Definition). [*] Enhanced LSP features (Autocomplete, Go to Definition).
[*] Full feature parity with all IC10 instructions. [*] Full feature parity with all IC10 instructions.
[*] Tutorials and beginner script examples. [*] Tutorials and beginner script examples.

View File

@@ -5,13 +5,20 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImGuiNET;
using StationeersIC10Editor; using StationeersIC10Editor;
using StationeersIC10Editor.IC10;
using UnityEngine;
public class SlangFormatter : ICodeFormatter public class SlangFormatter : ICodeFormatter
{ {
private CancellationTokenSource? _lspCancellationToken; private CancellationTokenSource? _lspCancellationToken;
private object _tokenLock = new(); private object _tokenLock = new();
private IC10CodeFormatter iC10CodeFormatter = new IC10CodeFormatter();
private string ic10CompilationResult = "";
private List<SourceMapEntry> ic10SourceMap = new();
// VS Code Dark Theme Palette // VS Code Dark Theme Palette
public static readonly uint ColorControl = ColorFromHTML("#C586C0"); // Pink (if, return, loop) public static readonly uint ColorControl = ColorFromHTML("#C586C0"); // Pink (if, return, loop)
public static readonly uint ColorDeclaration = ColorFromHTML("#569CD6"); // Blue (let, device, fn) public static readonly uint ColorDeclaration = ColorFromHTML("#569CD6"); // Blue (let, device, fn)
@@ -33,6 +40,7 @@ public class SlangFormatter : ICodeFormatter
: base() : base()
{ {
OnCodeChanged += HandleCodeChanged; OnCodeChanged += HandleCodeChanged;
OnCaretMoved += UpdateIc10Formatter;
} }
public static double MatchingScore(string input) public static double MatchingScore(string input)
@@ -70,6 +78,34 @@ public class SlangFormatter : ICodeFormatter
return this.Lines.RawText; return this.Lines.RawText;
} }
public override void DrawLine(int lineIndex, TextRange selection, bool drawLineNumber = true)
{
Vector2 cursorPos = ImGui.GetCursorScreenPos();
Vector2 space = ImGui.GetContentRegionAvail();
base.DrawLine(lineIndex, selection, drawLineNumber);
var charWidth = Settings.CharWidth;
var width = Mathf.Max(Lines.Width + 10.0f + LineNumberOffset * charWidth, space.x / 2);
ImGui
.GetWindowDrawList()
.AddLine(
new Vector2(cursorPos.x + width + 4.5f * charWidth, cursorPos.y),
new Vector2(
cursorPos.x + width + 4.5f * charWidth,
cursorPos.y + space.y + Settings.LineHeight
),
ColorLineNumber,
1.0f
);
cursorPos.x += width;
ImGui.SetCursorScreenPos(cursorPos);
if (lineIndex < iC10CodeFormatter.Lines.Count)
iC10CodeFormatter.DrawLine(lineIndex, new TextRange(), true);
}
public override StyledLine ParseLine(string line) public override StyledLine ParseLine(string line)
{ {
// We create the line first // We create the line first
@@ -126,6 +162,32 @@ public class SlangFormatter : ICodeFormatter
); );
ApplyDiagnostics(dict); ApplyDiagnostics(dict);
// If we have valid code, update the IC10 output
if (dict.Count > 0)
{
return;
}
var (compilationSuccess, compiled, sourceMap) = await Task.Run(
() =>
{
var successful = Marshal.CompileFromString(
inputSrc,
out var compiled,
out var sourceMap
);
return (successful, compiled, sourceMap);
},
cancellationToken
);
if (compilationSuccess)
{
ic10CompilationResult = compiled;
ic10SourceMap = sourceMap;
UpdateIc10Formatter();
}
} }
catch (OperationCanceledException) { } catch (OperationCanceledException) { }
catch (Exception ex) catch (Exception ex)
@@ -134,6 +196,53 @@ public class SlangFormatter : ICodeFormatter
} }
} }
private void UpdateIc10Formatter()
{
iC10CodeFormatter.Editor = Editor;
var caretPos = Editor.CaretPos.Line;
// get the slang sourceMap at the current editor line
var lines = ic10SourceMap.FindAll(entry =>
entry.SlangSource.StartLine == caretPos || entry.SlangSource.EndLine == caretPos
);
// extract the current "context" of the ic10 compilation. The current Slang source line
// should be directly next to the compiled IC10 source line, and we should highlight the
// IC10 code that directly represents the Slang source
iC10CodeFormatter.ResetCode(ic10CompilationResult);
if (lines.Count() < 1)
{
return;
}
// get the total range of the IC10 source for the selected Slang line
var max = lines.Max(line => line.Ic10Line);
var min = lines.Min(line => line.Ic10Line);
// highlight all the IC10 lines that are within the specified range
foreach (var index in Enumerable.Range((int)min, (int)(max - min) + 1))
{
var lineText = iC10CodeFormatter.Lines[index].Text;
var newLine = new StyledLine(
lineText,
[
new SemanticToken
{
Column = 0,
Length = lineText.Length,
Line = index,
Background = ColorIdentifier,
Color = ColorFromHTML("black"),
},
]
);
iC10CodeFormatter.Lines[index] = newLine;
}
}
// This runs on the Main Thread // This runs on the Main Thread
private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict) private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict)
{ {

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.3"; public const string PluginVersion = "0.4.0";
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.2</Version> <Version>0.4.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
@@ -39,6 +39,10 @@
<HintPath>./ref/Assembly-CSharp.dll</HintPath> <HintPath>./ref/Assembly-CSharp.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="RG.ImGui">
<HintPath>./ref/RG.ImGui.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="IC10Editor.dll"> <Reference Include="IC10Editor.dll">
<HintPath>./ref/IC10Editor.dll</HintPath> <HintPath>./ref/IC10Editor.dll</HintPath>

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.2" version = "0.4.0"
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.3" version = "0.4.0"
edition = "2021" edition = "2021"
[workspace] [workspace]

View File

@@ -54,9 +54,7 @@ fn nested_binary_expressions() -> Result<()> {
move r15 r2 move r15 r2
j __internal_L1 j __internal_L1
__internal_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

View File

@@ -18,9 +18,7 @@ fn no_arguments() -> anyhow::Result<()> {
doSomething: doSomething:
push ra push ra
__internal_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
@@ -61,9 +59,7 @@ fn let_var_args() -> anyhow::Result<()> {
move r15 r1 move r15 r1
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
main: main:
__internal_L2: __internal_L2:
@@ -71,9 +67,7 @@ fn let_var_args() -> anyhow::Result<()> {
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
@@ -129,9 +123,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
move r15 5 move r15 5
j __internal_L1 j __internal_L1
__internal_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
" "
} }
@@ -171,9 +161,7 @@ fn mixed_args() -> anyhow::Result<()> {
pop r9 pop r9
push ra push ra
__internal_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
" "
} }
@@ -216,9 +202,7 @@ fn with_return_statement() -> anyhow::Result<()> {
move r15 456 move r15 456
j __internal_L1 j __internal_L1
__internal_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
@@ -252,9 +236,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
push ra push ra
move r15 -1 move r15 -1
__internal_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

@@ -135,9 +135,7 @@ fn test_boolean_return() -> anyhow::Result<()> {
move r15 1 move r15 1
j __internal_L1 j __internal_L1
__internal_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
sub r0 sp 3
get r1 db r0
sub r0 sp 2
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: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0 sub sp sp 2
sub sp sp 3
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
"} "}
); );
@@ -60,9 +93,7 @@ fn test_early_return() -> anyhow::Result<()> {
move r8 3 move r8 3
j __internal_L1 j __internal_L1
__internal_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
@@ -91,9 +122,7 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
pop r9 pop r9
push ra push ra
__internal_L1: __internal_L1:
sub r0 sp 1 pop ra
get ra db r0
sub sp sp 1
j ra j ra
"} "}
); );

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

@@ -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),
)?; )?;
} }
@@ -2296,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,
}))
}
} }
} }
@@ -2693,33 +2718,49 @@ impl<'a> Compiler<'a> {
self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?;
self.write_instruction( if ra_stack_offset == 1 {
Instruction::Sub( self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?;
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(ra_stack_offset.into()),
),
Some(span),
)?;
self.write_instruction( let remaining_cleanup = block_scope.stack_offset() - 1;
Instruction::Get( if remaining_cleanup > 0 {
Operand::ReturnAddress, self.write_instruction(
Operand::Device(Cow::from("db")), Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer,
), Operand::StackPointer,
Some(span), Operand::Number(remaining_cleanup.into()),
)?; ),
Some(span),
if block_scope.stack_offset() > 0 { )?;
}
} else {
self.write_instruction( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, Operand::StackPointer,
Operand::StackPointer, Operand::Number(ra_stack_offset.into()),
Operand::Number(block_scope.stack_offset().into()),
), ),
Some(span), Some(span),
)?; )?;
self.write_instruction(
Instruction::Get(
Operand::ReturnAddress,
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(span),
)?;
if block_scope.stack_offset() > 0 {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(block_scope.stack_offset().into()),
),
Some(span),
)?;
}
} }
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;

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

@@ -565,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,
} }
@@ -595,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())),
@@ -657,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

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