Compare commits
27 Commits
0.3.3
...
9b8900d7a7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b8900d7a7 | |||
|
792bba4875
|
|||
|
1c39e146fb
|
|||
|
47bcd0be34
|
|||
|
445f731170
|
|||
|
c7aa30581d
|
|||
|
42b0b0acf9
|
|||
|
5230c620e8
|
|||
|
06a0ec28eb
|
|||
| 73e08b9896 | |||
|
e83ff67af8
|
|||
| cacff4ff55 | |||
|
7295b14f6a
|
|||
|
93873dfa93
|
|||
| 15752fde3d | |||
|
badcdd3c31
|
|||
|
f0e7506905
|
|||
|
0962b3a5e7
|
|||
|
1439f9ee7e
|
|||
|
3f105ef35c
|
|||
|
45a7a6b38b
|
|||
| 5dbb0ee2d7 | |||
|
6b18489f54
|
|||
|
ecfed65221
|
|||
|
ed5ea9f6eb
|
|||
|
0b354d4ec0
|
|||
| 6c11c0e6e5 |
@@ -4,6 +4,7 @@ name: CI/CD Pipeline
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
tags: ["*.*.*"]
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
|
||||
@@ -57,6 +58,10 @@ jobs:
|
||||
slang-builder \
|
||||
./build.sh
|
||||
|
||||
- name: Zip Workshop Folder
|
||||
run: |
|
||||
zip -r release/workshop.zip release/workshop/
|
||||
|
||||
# 3. Fix Permissions
|
||||
# Docker writes files as root. We need to own them to upload them.
|
||||
- name: Fix Permissions
|
||||
@@ -65,7 +70,36 @@ jobs:
|
||||
|
||||
# 4. Upload to GitHub
|
||||
- name: Upload Release Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: StationeersSlang-Release
|
||||
path: release/
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: self-hosted
|
||||
# ONLY run this job if we pushed a tag (e.g., v1.0.1)
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# We download the artifact from the previous 'build' job
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: StationeersSlang-Release
|
||||
path: ./release-files
|
||||
|
||||
- name: Create Gitea Release
|
||||
uses: https://gitea.com/actions/gitea-release-action@v1
|
||||
with:
|
||||
files: |
|
||||
./release-files/workshop.zip
|
||||
./release-files/slang
|
||||
./release-files/slang.exe
|
||||
name: ${{ github.ref_name }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
36
Changelog.md
36
Changelog.md
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
[0.4.2]
|
||||
|
||||
- Removed all harmony patches as most functionality as been added into the
|
||||
`IC10 Editor` mod
|
||||
- IC10 runtime errors will have been reverted back to showing as IC10 line
|
||||
numbers instead of Slang line numbers.
|
||||
- The IC10 line should be easily mapped to a Slang line via the side-by-side
|
||||
IC10 compilation view.
|
||||
|
||||
[0.4.1]
|
||||
|
||||
- Update syscalls for `loadSlot` and `setSlot` to support expressions instead of
|
||||
just variables for the slot index
|
||||
- Moved the main repository from GitHub to a self-hosted Gitea
|
||||
- Restructured workflow files to support this change
|
||||
- GitHub will still remain as a mirrored repository of the new
|
||||
Gitea instance.
|
||||
- This is in response to the new upcoming changes to the pricing model
|
||||
for self-hosted GitHub action runners.
|
||||
|
||||
[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]
|
||||
|
||||
- Fixed bug where negative temperature literals were converted to Kelvin
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Slang</Name>
|
||||
<Author>JoeDiertay</Author>
|
||||
<Version>0.3.3</Version>
|
||||
<Version>0.4.2</Version>
|
||||
<Description>
|
||||
[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]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]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]
|
||||
|
||||
[h2]Installation[/h2]
|
||||
@@ -50,16 +52,12 @@ loop {
|
||||
|
||||
[h2]Known Issues (Beta)[/h2]
|
||||
[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.
|
||||
[*] [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]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]
|
||||
|
||||
[h2]Planned Features[/h2]
|
||||
[list]
|
||||
[*] Side-by-side view: Slang vs. Compiled IC10.
|
||||
[*] Compiler optimizations (dead code elimination, smarter register allocation).
|
||||
[*] Enhanced LSP features (Autocomplete, Go to Definition).
|
||||
[*] Full feature parity with all IC10 instructions.
|
||||
[*] Tutorials and beginner script examples.
|
||||
@@ -88,7 +86,7 @@ A: Yes! Slang does not modify any existing IC10 code, it is only a compiler. As
|
||||
<Tag>Quality of Life</Tag>
|
||||
</Tags>
|
||||
<DependsOn WorkshopHandle="3592775931" />
|
||||
<OrderBefore WorkshopHandle="3592775931" />
|
||||
<OrderAfter WorkshopHandle="3592775931" />
|
||||
<InGameDescription><![CDATA[
|
||||
<size=30><color=#ffff00>Slang - High Level Language Compiler</color></size>
|
||||
A modern programming experience for Stationeers. Write C-style code that compiles to IC10 instantly.
|
||||
|
||||
@@ -6,13 +6,20 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StationeersIC10Editor;
|
||||
using StationeersIC10Editor.IC10;
|
||||
|
||||
public class SlangFormatter : ICodeFormatter
|
||||
{
|
||||
public const string SLANG_SRC = "SLANG_SRC";
|
||||
private CancellationTokenSource? _lspCancellationToken;
|
||||
private object _tokenLock = new();
|
||||
|
||||
// VS Code Dark Theme Palette
|
||||
protected Editor? Ic10Editor = null;
|
||||
private IC10CodeFormatter iC10CodeFormatter = new IC10CodeFormatter();
|
||||
private string ic10CompilationResult = "";
|
||||
private List<SourceMapEntry> ic10SourceMap = new();
|
||||
|
||||
#region Colors
|
||||
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 ColorFunction = ColorFromHTML("#DCDCAA"); // Yellow (syscalls)
|
||||
@@ -21,10 +28,8 @@ public class SlangFormatter : ICodeFormatter
|
||||
public static readonly uint ColorBoolean = ColorFromHTML("#569CD6"); // Blue (true/false)
|
||||
public static readonly uint ColorIdentifier = ColorFromHTML("#9CDCFE"); // Light Blue (variables)
|
||||
public static new readonly uint ColorDefault = ColorFromHTML("#D4D4D4"); // White (punctuation ; { } )
|
||||
|
||||
// Operators are often the same color as default text in VS Code Dark,
|
||||
// but having a separate definition lets you tweak it (e.g. make them slightly darker or distinct)
|
||||
public static readonly uint ColorOperator = ColorFromHTML("#D4D4D4");
|
||||
#endregion
|
||||
|
||||
private HashSet<uint> _linesWithErrors = new();
|
||||
private int _lastLineCount = -1;
|
||||
@@ -33,6 +38,7 @@ public class SlangFormatter : ICodeFormatter
|
||||
: base()
|
||||
{
|
||||
OnCodeChanged += HandleCodeChanged;
|
||||
OnCaretMoved += UpdateIc10Formatter;
|
||||
}
|
||||
|
||||
public static double MatchingScore(string input)
|
||||
@@ -41,6 +47,11 @@ public class SlangFormatter : ICodeFormatter
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return 0d;
|
||||
|
||||
if (input.Contains(SLANG_SRC))
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Run the compiler to get diagnostics
|
||||
var diagnostics = Marshal.DiagnoseSource(input);
|
||||
|
||||
@@ -67,7 +78,28 @@ public class SlangFormatter : ICodeFormatter
|
||||
|
||||
public override string Compile()
|
||||
{
|
||||
return this.Lines.RawText;
|
||||
if (!Marshal.CompileFromString(RawText, out var compilationResult, out var sourceMap))
|
||||
{
|
||||
return "Compilation Error";
|
||||
}
|
||||
|
||||
return compilationResult + $"\n{EncodeSource(RawText, SLANG_SRC)}";
|
||||
}
|
||||
|
||||
public override void ResetCode(string code)
|
||||
{
|
||||
// for compatibility, we need to check for GlobalCode.SLANG_SRC
|
||||
// `#SLANG_SRC:<code>`
|
||||
// and replace with `# SLANG_SRC: <code>`
|
||||
if (code.Contains(GlobalCode.SLANG_SRC))
|
||||
{
|
||||
code = code.Replace(GlobalCode.SLANG_SRC, $"# {SLANG_SRC}: ");
|
||||
}
|
||||
if (code.Contains(SLANG_SRC))
|
||||
{
|
||||
code = ExtractEncodedSource(code, SLANG_SRC);
|
||||
}
|
||||
base.ResetCode(code);
|
||||
}
|
||||
|
||||
public override StyledLine ParseLine(string line)
|
||||
@@ -126,6 +158,32 @@ public class SlangFormatter : ICodeFormatter
|
||||
);
|
||||
|
||||
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 (Exception ex)
|
||||
@@ -134,6 +192,59 @@ public class SlangFormatter : ICodeFormatter
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIc10Formatter()
|
||||
{
|
||||
var tab = Editor.ParentTab;
|
||||
if (Ic10Editor == null)
|
||||
{
|
||||
iC10CodeFormatter = new IC10CodeFormatter();
|
||||
Ic10Editor = new Editor(Editor.KeyHandler);
|
||||
Ic10Editor.IsReadOnly = true;
|
||||
iC10CodeFormatter.Editor = Ic10Editor;
|
||||
}
|
||||
|
||||
if (tab.Editors.Count < 2)
|
||||
{
|
||||
tab.AddEditor(Ic10Editor);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Ic10Editor.ResetCode(ic10CompilationResult);
|
||||
|
||||
if (lines.Count() < 1)
|
||||
{
|
||||
Ic10Editor.Selection = new TextRange
|
||||
{
|
||||
End = new TextPosition { Col = 0, Line = 0 },
|
||||
Start = new TextPosition { Col = 0, Line = 0 },
|
||||
};
|
||||
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);
|
||||
|
||||
Ic10Editor.CaretPos = new TextPosition { Col = 0, Line = (int)max };
|
||||
|
||||
// highlight all the IC10 lines that are within the specified range
|
||||
Ic10Editor.Selection.Start = new TextPosition { Col = 0, Line = (int)min };
|
||||
Ic10Editor.Selection.End = new TextPosition
|
||||
{
|
||||
Col = Ic10Editor.Lines[(int)max].Text.Length,
|
||||
Line = (int)max,
|
||||
};
|
||||
}
|
||||
|
||||
// This runs on the Main Thread
|
||||
private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict)
|
||||
{
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace Slang;
|
||||
|
||||
public static class GlobalCode
|
||||
{
|
||||
public const string SLANG_REF = "#SLANG_REF:";
|
||||
/// <summary>
|
||||
/// This is the OLD way of handling saving / loading. This has been replaced with a native
|
||||
/// save / load from IC10 Editor. However; this needs to remain for compatibility with
|
||||
/// previous versions of code not compiled with 0.4.2 or later.
|
||||
/// </summary>
|
||||
public const string SLANG_SRC = "#SLANG_SRC:";
|
||||
|
||||
// This is a Dictionary of ENCODED source code, compressed
|
||||
// so that save file data is smaller
|
||||
private static Dictionary<Guid, string> codeDict = new();
|
||||
|
||||
// This Dictionary stores the source maps for the given SLANG_REF, where
|
||||
// the key is the IC10 line, and the value is a List of Slang ranges where that
|
||||
// line would have come from
|
||||
/// <summary>
|
||||
/// This Dictionary stores the source maps for the given SLANG_REF, where
|
||||
/// the key is the IC10 line, and the value is a List of Slang ranges where that
|
||||
/// line would have come from
|
||||
/// </summary>
|
||||
private static Dictionary<Guid, Dictionary<uint, List<Range>>> sourceMaps = new();
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
codeDict.Clear();
|
||||
}
|
||||
|
||||
public static void SetSourceMap(Guid reference, List<SourceMapEntry> sourceMapEntries)
|
||||
{
|
||||
var builtDictionary = new Dictionary<uint, List<Range>>();
|
||||
@@ -72,81 +66,4 @@ public static class GlobalCode
|
||||
slangSpan = foundRange[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetSource(Guid reference)
|
||||
{
|
||||
if (!codeDict.ContainsKey(reference))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return DecodeSource(codeDict[reference]);
|
||||
}
|
||||
|
||||
public static void SetSource(Guid reference, string source)
|
||||
{
|
||||
codeDict[reference] = EncodeSource(source);
|
||||
}
|
||||
|
||||
public static string? GetEncoded(Guid reference)
|
||||
{
|
||||
if (!codeDict.ContainsKey(reference))
|
||||
return null;
|
||||
|
||||
return codeDict[reference];
|
||||
}
|
||||
|
||||
public static void SetEncoded(Guid reference, string encodedSource)
|
||||
{
|
||||
if (codeDict.ContainsKey(reference))
|
||||
{
|
||||
codeDict[reference] = encodedSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
codeDict.Add(reference, encodedSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static string EncodeSource(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(source);
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress))
|
||||
{
|
||||
gzipStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
return Convert.ToBase64String(memoryStream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static string DecodeSource(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] compressedBytes = Convert.FromBase64String(source);
|
||||
|
||||
using (var memoryStream = new MemoryStream(compressedBytes))
|
||||
{
|
||||
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
|
||||
{
|
||||
using (var outputStream = new MemoryStream())
|
||||
{
|
||||
gzipStream.CopyTo(outputStream);
|
||||
|
||||
return Encoding.UTF8.GetString(outputStream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,362 +0,0 @@
|
||||
namespace Slang;
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Assets.Scripts.Objects;
|
||||
using Assets.Scripts.Objects.Electrical;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using Assets.Scripts.UI;
|
||||
using HarmonyLib;
|
||||
|
||||
class LineErrorData
|
||||
{
|
||||
public AsciiString SourceRef;
|
||||
public uint IC10ErrorSource;
|
||||
public string SlangErrorReference;
|
||||
public Range SlangErrorSpan;
|
||||
|
||||
public LineErrorData(
|
||||
AsciiString sourceRef,
|
||||
uint ic10ErrorSource,
|
||||
string slangErrorRef,
|
||||
Range slangErrorSpan
|
||||
)
|
||||
{
|
||||
this.SourceRef = sourceRef;
|
||||
this.IC10ErrorSource = ic10ErrorSource;
|
||||
this.SlangErrorReference = slangErrorRef;
|
||||
this.SlangErrorSpan = slangErrorSpan;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class SlangPatches
|
||||
{
|
||||
private static ProgrammableChipMotherboard? _currentlyEditingMotherboard;
|
||||
private static AsciiString? _motherboardCachedCode;
|
||||
private static Guid? _currentlyEditingGuid;
|
||||
|
||||
private static ConditionalWeakTable<ProgrammableChip, LineErrorData> _errorReferenceTable =
|
||||
new();
|
||||
|
||||
[HarmonyPatch(
|
||||
typeof(ProgrammableChipMotherboard),
|
||||
nameof(ProgrammableChipMotherboard.InputFinished)
|
||||
)]
|
||||
[HarmonyPrefix]
|
||||
public static void pgmb_InputFinished(ref string result)
|
||||
{
|
||||
_currentlyEditingMotherboard = null;
|
||||
_motherboardCachedCode = null;
|
||||
// guard to ensure we have valid IC10 before continuing
|
||||
if (
|
||||
!SlangPlugin.IsSlangSource(ref result)
|
||||
|| !Marshal.CompileFromString(result, out var compiled, out var sourceMap)
|
||||
|| string.IsNullOrEmpty(compiled)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var thisRef = _currentlyEditingGuid ?? Guid.NewGuid();
|
||||
|
||||
// Ensure we cache this compiled code for later retreival.
|
||||
GlobalCode.SetSource(thisRef, result);
|
||||
GlobalCode.SetSourceMap(thisRef, sourceMap);
|
||||
|
||||
_currentlyEditingGuid = null;
|
||||
|
||||
// Append REF to the bottom
|
||||
compiled += $"\n{GlobalCode.SLANG_REF}{thisRef}";
|
||||
result = compiled;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ProgrammableChipMotherboard), nameof(ProgrammableChipMotherboard.OnEdit))]
|
||||
[HarmonyPrefix]
|
||||
public static void isc_OnEdit(ProgrammableChipMotherboard __instance)
|
||||
{
|
||||
_currentlyEditingMotherboard = __instance;
|
||||
_motherboardCachedCode = __instance.GetSourceCode();
|
||||
var sourceCode = System.Text.Encoding.UTF8.GetString(
|
||||
System.Text.Encoding.ASCII.GetBytes(__instance.GetSourceCode())
|
||||
);
|
||||
|
||||
if (string.IsNullOrEmpty(sourceCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for REF at the bottom
|
||||
var tagIndex = sourceCode.LastIndexOf(GlobalCode.SLANG_REF);
|
||||
|
||||
if (tagIndex == -1)
|
||||
{
|
||||
// this is not slang managed code
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!Guid.TryParse(
|
||||
sourceCode.Substring(tagIndex + GlobalCode.SLANG_REF.Length).Trim(),
|
||||
out Guid sourceRef
|
||||
)
|
||||
)
|
||||
{
|
||||
// not a valid Guid, not managed by slang
|
||||
return;
|
||||
}
|
||||
|
||||
_currentlyEditingGuid = sourceRef;
|
||||
var slangSource = GlobalCode.GetSource(sourceRef);
|
||||
|
||||
if (string.IsNullOrEmpty(slangSource))
|
||||
{
|
||||
// Didn't find that source ref in the global code manager.
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.SetSourceCode(slangSource);
|
||||
}
|
||||
|
||||
private static void HandleSerialization(ref string sourceCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourceCode))
|
||||
return;
|
||||
|
||||
// Check if the file ends with the Reference Tag
|
||||
var tagIndex = sourceCode.LastIndexOf(GlobalCode.SLANG_REF);
|
||||
|
||||
if (tagIndex == -1)
|
||||
return;
|
||||
|
||||
string guidString = sourceCode.Substring(tagIndex + GlobalCode.SLANG_REF.Length).Trim();
|
||||
|
||||
if (!Guid.TryParse(guidString, out Guid slangRefGuid))
|
||||
{
|
||||
L.Warning($"Found SLANG_REF but failed to parse GUID: {guidString}");
|
||||
return;
|
||||
}
|
||||
|
||||
var slangEncoded = GlobalCode.GetEncoded(slangRefGuid);
|
||||
|
||||
if (string.IsNullOrEmpty(slangEncoded))
|
||||
{
|
||||
L.Warning(
|
||||
$"Could not find encoded source for ref {slangRefGuid}. Save will contain compiled IC10 only."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract the clean IC10 code (everything before the tag)
|
||||
var cleanIc10 = sourceCode.Substring(0, tagIndex).TrimEnd();
|
||||
|
||||
// Append the encoded source tag to the bottom
|
||||
sourceCode = $"{cleanIc10}\n{GlobalCode.SLANG_SRC}{slangEncoded}";
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ProgrammableChip), nameof(ProgrammableChip.SerializeSave))]
|
||||
[HarmonyPostfix]
|
||||
public static void pgc_SerializeSave(ProgrammableChip __instance, ref ThingSaveData __result)
|
||||
{
|
||||
if (__result is not ProgrammableChipSaveData chipData)
|
||||
return;
|
||||
|
||||
string code = chipData.SourceCode;
|
||||
HandleSerialization(ref code);
|
||||
chipData.SourceCode = code;
|
||||
}
|
||||
|
||||
[HarmonyPatch(
|
||||
typeof(ProgrammableChip),
|
||||
nameof(ProgrammableChip.ErrorLineNumberString),
|
||||
MethodType.Getter
|
||||
)]
|
||||
[HarmonyPostfix]
|
||||
public static void pgc_ErrorLineNumberString(ProgrammableChip __instance, ref string __result)
|
||||
{
|
||||
if (
|
||||
String.IsNullOrEmpty(__result)
|
||||
|| !uint.TryParse(__result.Trim(), out var ic10ErrorLineNumber)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceAscii = __instance.GetSourceCode();
|
||||
|
||||
if (_errorReferenceTable.TryGetValue(__instance, out var cache))
|
||||
{
|
||||
if (cache.SourceRef.Equals(sourceAscii) && cache.IC10ErrorSource == ic10ErrorLineNumber)
|
||||
{
|
||||
__result = cache.SlangErrorReference;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var source = System.Text.Encoding.UTF8.GetString(
|
||||
System.Text.Encoding.ASCII.GetBytes(__instance.GetSourceCode())
|
||||
);
|
||||
|
||||
var slangIndex = source.LastIndexOf(GlobalCode.SLANG_REF);
|
||||
|
||||
if (
|
||||
slangIndex < 0
|
||||
|| !Guid.TryParse(
|
||||
source
|
||||
.Substring(
|
||||
source.LastIndexOf(GlobalCode.SLANG_REF) + GlobalCode.SLANG_REF.Length
|
||||
)
|
||||
.Trim(),
|
||||
out var slangGuid
|
||||
)
|
||||
|| !GlobalCode.GetSlangErrorLineFromICError(
|
||||
slangGuid,
|
||||
ic10ErrorLineNumber,
|
||||
out var slangErrorLineNumber,
|
||||
out var slangSpan
|
||||
)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
L.Warning($"IC error at: {__result} -- Slang source error line: {slangErrorLineNumber}");
|
||||
__result = slangErrorLineNumber.ToString();
|
||||
_errorReferenceTable.Remove(__instance);
|
||||
_errorReferenceTable.Add(
|
||||
__instance,
|
||||
new LineErrorData(
|
||||
sourceAscii,
|
||||
ic10ErrorLineNumber,
|
||||
slangErrorLineNumber.ToString(),
|
||||
slangSpan
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HarmonyPatch(
|
||||
typeof(ProgrammableChip),
|
||||
nameof(ProgrammableChip.SetSourceCode),
|
||||
new Type[] { typeof(string) }
|
||||
)]
|
||||
[HarmonyPostfix]
|
||||
public static void pgc_SetSourceCode_string(ProgrammableChip __instance, string sourceCode)
|
||||
{
|
||||
_errorReferenceTable.Remove(__instance);
|
||||
}
|
||||
|
||||
[HarmonyPatch(
|
||||
typeof(ProgrammableChip),
|
||||
nameof(ProgrammableChip.SetSourceCode),
|
||||
new Type[] { typeof(string), typeof(ICircuitHolder) }
|
||||
)]
|
||||
[HarmonyPostfix]
|
||||
public static void pgc_SetSourceCode_string_parent(
|
||||
ProgrammableChip __instance,
|
||||
string sourceCode,
|
||||
ICircuitHolder parent
|
||||
)
|
||||
{
|
||||
_errorReferenceTable.Remove(__instance);
|
||||
}
|
||||
|
||||
[HarmonyPatch(
|
||||
typeof(ProgrammableChipMotherboard),
|
||||
nameof(ProgrammableChipMotherboard.SerializeSave)
|
||||
)]
|
||||
[HarmonyPostfix]
|
||||
public static void pgmb_SerializeSave(
|
||||
ProgrammableChipMotherboard __instance,
|
||||
ref ThingSaveData __result
|
||||
)
|
||||
{
|
||||
if (__result is not ProgrammableChipMotherboardSaveData chipData)
|
||||
return;
|
||||
|
||||
string code = chipData.SourceCode;
|
||||
HandleSerialization(ref code);
|
||||
chipData.SourceCode = code;
|
||||
}
|
||||
|
||||
private static void HandleDeserialization(ref string sourceCode)
|
||||
{
|
||||
// Safety check for null/empty code
|
||||
if (string.IsNullOrEmpty(sourceCode))
|
||||
return;
|
||||
|
||||
// Check for the #SLANG_SRC: footer
|
||||
int tagIndex = sourceCode.LastIndexOf(GlobalCode.SLANG_SRC);
|
||||
|
||||
// If the tag is missing, this is just a normal IC10 script. Do nothing.
|
||||
if (tagIndex == -1)
|
||||
return;
|
||||
|
||||
// Extract the Encoded Source (Base64)
|
||||
string encodedSource = sourceCode.Substring(tagIndex + GlobalCode.SLANG_SRC.Length).Trim();
|
||||
|
||||
// Extract the IC10 Code (strip off the tag and the newline before it)
|
||||
string ic10Code = sourceCode.Substring(0, tagIndex).TrimEnd();
|
||||
|
||||
// Generate a new Runtime GUID for this session
|
||||
Guid runtimeGuid = Guid.NewGuid();
|
||||
|
||||
// Hydrate the Cache
|
||||
GlobalCode.SetEncoded(runtimeGuid, encodedSource);
|
||||
|
||||
// Rewrite the SourceCode to the "Runtime" format (REF at bottom)
|
||||
sourceCode = $"{ic10Code}\n{GlobalCode.SLANG_REF}{runtimeGuid}";
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ProgrammableChip), nameof(ProgrammableChip.DeserializeSave))]
|
||||
[HarmonyPrefix]
|
||||
public static void pgc_DeserializeSave(ref ThingSaveData savedData)
|
||||
{
|
||||
if (savedData is not ProgrammableChipSaveData pcSaveData)
|
||||
return;
|
||||
|
||||
string code = pcSaveData.SourceCode;
|
||||
HandleDeserialization(ref code);
|
||||
pcSaveData.SourceCode = code;
|
||||
}
|
||||
|
||||
[HarmonyPatch(
|
||||
typeof(ProgrammableChipMotherboard),
|
||||
nameof(ProgrammableChipMotherboard.DeserializeSave)
|
||||
)]
|
||||
[HarmonyPrefix]
|
||||
public static void pgmb_DeserializeSave(ref ThingSaveData savedData)
|
||||
{
|
||||
if (savedData is not ProgrammableChipMotherboardSaveData pcSaveData)
|
||||
return;
|
||||
|
||||
string code = pcSaveData.SourceCode;
|
||||
HandleDeserialization(ref code);
|
||||
pcSaveData.SourceCode = code;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InputSourceCode), nameof(InputSourceCode.ButtonInputCancel))]
|
||||
[HarmonyPrefix]
|
||||
public static void isc_ButtonInputCancel()
|
||||
{
|
||||
if (_currentlyEditingMotherboard is null || _motherboardCachedCode is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentlyEditingMotherboard.SetSourceCode(_motherboardCachedCode);
|
||||
|
||||
_currentlyEditingMotherboard = null;
|
||||
_motherboardCachedCode = null;
|
||||
_currentlyEditingGuid = null;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))]
|
||||
[HarmonyPostfix]
|
||||
public static void Stationpedia_Regenerate()
|
||||
{
|
||||
foreach (var page in Marshal.GetSlangDocs())
|
||||
{
|
||||
Stationpedia.Register(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Slang
|
||||
{
|
||||
public const string PluginGuid = "com.biddydev.slang";
|
||||
public const string PluginName = "Slang";
|
||||
public const string PluginVersion = "0.3.3";
|
||||
public const string PluginVersion = "0.4.2";
|
||||
|
||||
public static Mod MOD = new Mod(PluginName, PluginVersion);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>StationeersSlang</AssemblyName>
|
||||
<Description>Slang Compiler Bridge</Description>
|
||||
<Version>0.3.2</Version>
|
||||
<Version>0.4.2</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
@@ -39,6 +39,10 @@
|
||||
<HintPath>./ref/Assembly-CSharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="RG.ImGui">
|
||||
<HintPath>./ref/RG.ImGui.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
|
||||
<Reference Include="IC10Editor.dll">
|
||||
<HintPath>./ref/IC10Editor.dll</HintPath>
|
||||
|
||||
18
rust_compiler/Cargo.lock
generated
18
rust_compiler/Cargo.lock
generated
@@ -172,9 +172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
@@ -930,7 +930,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "slang"
|
||||
version = "0.3.2"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1063,18 +1063,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
|
||||
checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -1084,9 +1084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||
checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "slang"
|
||||
version = "0.3.3"
|
||||
version = "0.4.2"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use anyhow::Result;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -54,9 +53,7 @@ fn nested_binary_expressions() -> Result<()> {
|
||||
move r15 r2
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
push 10
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -18,9 +17,7 @@ fn no_arguments() -> anyhow::Result<()> {
|
||||
doSomething:
|
||||
push ra
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
jal doSomething
|
||||
@@ -61,9 +58,7 @@ fn let_var_args() -> anyhow::Result<()> {
|
||||
move r15 r1
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
__internal_L2:
|
||||
@@ -71,9 +66,7 @@ fn let_var_args() -> anyhow::Result<()> {
|
||||
push r8
|
||||
push r8
|
||||
jal mul2
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
pop r8
|
||||
move r9 r15
|
||||
pow r1 r9 2
|
||||
move r9 r1
|
||||
@@ -129,9 +122,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
|
||||
move r15 5
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
move r8 123
|
||||
@@ -139,9 +130,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
|
||||
push 12
|
||||
push 34
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
pop r8
|
||||
move r9 r15
|
||||
"
|
||||
}
|
||||
@@ -171,9 +160,7 @@ fn mixed_args() -> anyhow::Result<()> {
|
||||
pop r9
|
||||
push ra
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
move r8 123
|
||||
@@ -181,9 +168,7 @@ fn mixed_args() -> anyhow::Result<()> {
|
||||
push r8
|
||||
push 456
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
pop r8
|
||||
move r9 r15
|
||||
"
|
||||
}
|
||||
@@ -216,9 +201,7 @@ fn with_return_statement() -> anyhow::Result<()> {
|
||||
move r15 456
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
push 123
|
||||
@@ -252,9 +235,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
|
||||
push ra
|
||||
move r15 -1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
jal doSomething
|
||||
|
||||
@@ -135,9 +135,7 @@ fn test_boolean_return() -> anyhow::Result<()> {
|
||||
move r15 1
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
jal getTrue
|
||||
|
||||
@@ -5,7 +5,12 @@ use pretty_assertions::assert_eq;
|
||||
fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
|
||||
let compiled = compile!(debug r#"
|
||||
// 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!(
|
||||
@@ -21,11 +26,39 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
|
||||
pop r13
|
||||
pop r14
|
||||
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:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 3
|
||||
pop ra
|
||||
sub sp sp 2
|
||||
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
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
main:
|
||||
jal doSomething
|
||||
@@ -91,9 +122,7 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
|
||||
pop r9
|
||||
push ra
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
pop ra
|
||||
j ra
|
||||
"}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use anyhow::Result;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -208,3 +207,29 @@ fn test_set_slot() -> anyhow::Result<()> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -1115,43 +1115,26 @@ impl<'a> Compiler<'a> {
|
||||
Some(name.span),
|
||||
)?;
|
||||
|
||||
for register in active_registers {
|
||||
let VariableLocation::Stack(stack_offset) = stack
|
||||
.get_location_of(&Cow::from(format!("temp_{register}")), None)
|
||||
.map_err(Error::Scope)?
|
||||
else {
|
||||
// This shouldn't happen if we just added it
|
||||
return Err(Error::Unknown(
|
||||
format!("Failed to recover temp_{register}"),
|
||||
Some(name.span),
|
||||
));
|
||||
};
|
||||
// cleanup spilled temporary variables
|
||||
let total_stack_usage = stack.stack_offset();
|
||||
let saved_regs_count = active_registers.len() as u16;
|
||||
|
||||
if total_stack_usage > saved_regs_count {
|
||||
let spill_amount = total_stack_usage - saved_regs_count;
|
||||
self.write_instruction(
|
||||
Instruction::Sub(
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
Operand::StackPointer,
|
||||
Operand::Number(stack_offset.into()),
|
||||
),
|
||||
Some(name.span),
|
||||
)?;
|
||||
|
||||
self.write_instruction(
|
||||
Instruction::Get(
|
||||
Operand::Register(register),
|
||||
Operand::Device(Cow::from("db")),
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
Operand::StackPointer,
|
||||
Operand::Number(spill_amount.into()),
|
||||
),
|
||||
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(
|
||||
Instruction::Sub(
|
||||
Operand::StackPointer,
|
||||
Operand::StackPointer,
|
||||
Operand::Number(Decimal::from(stack.stack_offset())),
|
||||
),
|
||||
Instruction::Pop(Operand::Register(*register)),
|
||||
Some(name.span),
|
||||
)?;
|
||||
}
|
||||
@@ -2250,10 +2233,7 @@ impl<'a> Compiler<'a> {
|
||||
System::LoadSlot(dev_name, slot_index, logic_type) => {
|
||||
let (dev_hash, hash_cleanup) =
|
||||
self.compile_literal_or_variable(dev_name.node, scope)?;
|
||||
let (slot_index, slot_cleanup) = self.compile_literal_or_variable(
|
||||
LiteralOrVariable::Literal(slot_index.node),
|
||||
scope,
|
||||
)?;
|
||||
let (slot_index, slot_cleanup) = self.compile_operand(*slot_index, scope)?;
|
||||
let (logic_type, logic_cleanup) = self.compile_literal_or_variable(
|
||||
LiteralOrVariable::Literal(logic_type.node),
|
||||
scope,
|
||||
@@ -2278,10 +2258,7 @@ impl<'a> Compiler<'a> {
|
||||
System::SetSlot(dev_name, slot_index, logic_type, var) => {
|
||||
let (dev_name, name_cleanup) =
|
||||
self.compile_literal_or_variable(dev_name.node, scope)?;
|
||||
let (slot_index, index_cleanup) = self.compile_literal_or_variable(
|
||||
LiteralOrVariable::Literal(slot_index.node),
|
||||
scope,
|
||||
)?;
|
||||
let (slot_index, index_cleanup) = self.compile_operand(*slot_index, scope)?;
|
||||
let (logic_type, type_cleanup) = self.compile_literal_or_variable(
|
||||
LiteralOrVariable::Literal(logic_type.node),
|
||||
scope,
|
||||
@@ -2296,6 +2273,48 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
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,6 +2712,21 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
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(
|
||||
Instruction::Sub(
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
@@ -2721,6 +2755,7 @@ impl<'a> Compiler<'a> {
|
||||
Some(span),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
|
||||
Ok(())
|
||||
|
||||
@@ -10,6 +10,7 @@ macro_rules! with_syscalls {
|
||||
"loadBatched",
|
||||
"loadBatchedNamed",
|
||||
"loadSlot",
|
||||
"loadReagent",
|
||||
"set",
|
||||
"setBatched",
|
||||
"setBatchedNamed",
|
||||
@@ -35,6 +36,7 @@ macro_rules! with_syscalls {
|
||||
"lb",
|
||||
"lbn",
|
||||
"ls",
|
||||
"lr",
|
||||
"s",
|
||||
"sb",
|
||||
"sbn",
|
||||
|
||||
@@ -191,6 +191,9 @@ pub enum Instruction<'a> {
|
||||
/// `sbn deviceHash nameHash type value` - Set Batch Named
|
||||
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
|
||||
Jump(Operand<'a>),
|
||||
/// `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) => {
|
||||
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::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
|
||||
Instruction::JumpRelative(off) => write!(f, "jr {}", off),
|
||||
|
||||
@@ -565,6 +565,7 @@ fn get_destination_reg(instr: &Instruction) -> Option<u8> {
|
||||
| Instruction::Sqrt(Operand::Register(r), _)
|
||||
| Instruction::Tan(Operand::Register(r), _)
|
||||
| Instruction::Trunc(Operand::Register(r), _)
|
||||
| Instruction::LoadReagent(Operand::Register(r), _, _, _)
|
||||
| Instruction::Pop(Operand::Register(r)) => Some(*r),
|
||||
_ => None,
|
||||
}
|
||||
@@ -595,6 +596,9 @@ fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<Instr
|
||||
c.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::SetNe(_, a, b) => Some(Instruction::SetNe(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::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::SetNe(_, a, b)
|
||||
| Instruction::SetGt(_, a, b)
|
||||
|
||||
@@ -1834,20 +1834,8 @@ impl<'a> Parser<'a> {
|
||||
let mut args = args!(3);
|
||||
let next = args.next();
|
||||
let dev_name = literal_or_variable!(next);
|
||||
let next = args.next();
|
||||
let slot_index = get_arg!(Literal, literal_or_variable!(next));
|
||||
if !matches!(
|
||||
slot_index,
|
||||
Spanned {
|
||||
node: Literal::Number(_),
|
||||
..
|
||||
},
|
||||
) {
|
||||
return Err(Error::InvalidSyntax(
|
||||
slot_index.span,
|
||||
"Expected a number".to_string(),
|
||||
));
|
||||
}
|
||||
let slot_index = args.next().ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
let next = args.next();
|
||||
let slot_logic = get_arg!(Literal, literal_or_variable!(next));
|
||||
if !matches!(
|
||||
@@ -1864,27 +1852,17 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
Ok(SysCall::System(System::LoadSlot(
|
||||
dev_name, slot_index, slot_logic,
|
||||
dev_name,
|
||||
boxed!(slot_index),
|
||||
slot_logic,
|
||||
)))
|
||||
}
|
||||
"setSlot" | "ss" => {
|
||||
let mut args = args!(4);
|
||||
let next = args.next();
|
||||
let dev_name = literal_or_variable!(next);
|
||||
let next = args.next();
|
||||
let slot_index = get_arg!(Literal, literal_or_variable!(next));
|
||||
if !matches!(
|
||||
slot_index,
|
||||
Spanned {
|
||||
node: Literal::Number(_),
|
||||
..
|
||||
}
|
||||
) {
|
||||
return Err(Error::InvalidSyntax(
|
||||
slot_index.span,
|
||||
"Expected a number".into(),
|
||||
));
|
||||
}
|
||||
let slot_index = args.next().ok_or(Error::UnexpectedEOF)?;
|
||||
|
||||
let next = args.next();
|
||||
let slot_logic = get_arg!(Literal, literal_or_variable!(next));
|
||||
if !matches!(
|
||||
@@ -1904,9 +1882,23 @@ impl<'a> Parser<'a> {
|
||||
|
||||
Ok(SysCall::System(System::SetSlot(
|
||||
dev_name,
|
||||
slot_index,
|
||||
boxed!(slot_index),
|
||||
slot_logic,
|
||||
Box::new(expr),
|
||||
boxed!(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),
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ documented! {
|
||||
/// `let isOccupied = ls(deviceHash, 2, "Occupied");`
|
||||
LoadSlot(
|
||||
Spanned<LiteralOrVariable<'a>>,
|
||||
Spanned<Literal<'a>>,
|
||||
Box<Spanned<Expression<'a>>>,
|
||||
Spanned<Literal<'a>>
|
||||
),
|
||||
/// Stores a value of LogicType on a device by the index value
|
||||
@@ -234,7 +234,19 @@ documented! {
|
||||
/// `ss(deviceHash, 0, "Open", true);`
|
||||
SetSlot(
|
||||
Spanned<LiteralOrVariable<'a>>,
|
||||
Box<Spanned<Expression<'a>>>,
|
||||
Spanned<Literal<'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::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
|
||||
System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user