12 Commits

Author SHA1 Message Date
73e08b9896 Merge pull request 'Fix for Gitea actions' (#3) from actions-fix into master
All checks were successful
CI/CD Pipeline / test (push) Successful in 33s
CI/CD Pipeline / build (push) Successful in 1m45s
CI/CD Pipeline / release (push) Successful in 15s
Reviewed-on: #3
2025-12-21 16:42:59 -07:00
e83ff67af8 Fix for Gitea actions
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 34s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2025-12-21 16:42:29 -07:00
cacff4ff55 Merge pull request '35-accept-variables' (#2) from 35-accept-variables into master
Some checks failed
CI/CD Pipeline / test (push) Successful in 34s
CI/CD Pipeline / build (push) Failing after 1m55s
CI/CD Pipeline / release (push) Has been skipped
Reviewed-on: #2
2025-12-21 16:34:26 -07:00
7295b14f6a Update changelog, update workflow files
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 34s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2025-12-21 16:32:11 -07:00
93873dfa93 Accept expressions for the slotIndex in the slot logic syscalls 2025-12-21 15:59:40 -07:00
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
11 changed files with 186 additions and 52 deletions

View File

@@ -4,6 +4,7 @@ name: CI/CD Pipeline
on: on:
push: push:
branches: ["master"] branches: ["master"]
tags: ["*.*.*"]
pull_request: pull_request:
branches: ["master"] branches: ["master"]
@@ -57,6 +58,10 @@ jobs:
slang-builder \ slang-builder \
./build.sh ./build.sh
- name: Zip Workshop Folder
run: |
zip -r release/workshop.zip release/workshop/
# 3. Fix Permissions # 3. Fix Permissions
# Docker writes files as root. We need to own them to upload them. # Docker writes files as root. We need to own them to upload them.
- name: Fix Permissions - name: Fix Permissions
@@ -65,7 +70,36 @@ jobs:
# 4. Upload to GitHub # 4. Upload to GitHub
- name: Upload Release Artifacts - name: Upload Release Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: StationeersSlang-Release name: StationeersSlang-Release
path: 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 }}

View File

@@ -1,5 +1,22 @@
# Changelog # Changelog
[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] [0.3.4]
- Added support for `loadReagent`, which maps to the `lr` IC10 instruction - Added support for `loadReagent`, which maps to the `lr` IC10 instruction

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.4</Version> <Version>0.4.1</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.4"; public const string PluginVersion = "0.4.1";
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

@@ -930,7 +930,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "slang" name = "slang"
version = "0.3.4" version = "0.4.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",

View File

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

View File

@@ -2233,10 +2233,7 @@ impl<'a> Compiler<'a> {
System::LoadSlot(dev_name, slot_index, logic_type) => { System::LoadSlot(dev_name, slot_index, logic_type) => {
let (dev_hash, hash_cleanup) = let (dev_hash, hash_cleanup) =
self.compile_literal_or_variable(dev_name.node, scope)?; self.compile_literal_or_variable(dev_name.node, scope)?;
let (slot_index, slot_cleanup) = self.compile_literal_or_variable( let (slot_index, slot_cleanup) = self.compile_operand(*slot_index, scope)?;
LiteralOrVariable::Literal(slot_index.node),
scope,
)?;
let (logic_type, logic_cleanup) = self.compile_literal_or_variable( let (logic_type, logic_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(logic_type.node), LiteralOrVariable::Literal(logic_type.node),
scope, scope,
@@ -2261,10 +2258,7 @@ impl<'a> Compiler<'a> {
System::SetSlot(dev_name, slot_index, logic_type, var) => { System::SetSlot(dev_name, slot_index, logic_type, var) => {
let (dev_name, name_cleanup) = let (dev_name, name_cleanup) =
self.compile_literal_or_variable(dev_name.node, scope)?; self.compile_literal_or_variable(dev_name.node, scope)?;
let (slot_index, index_cleanup) = self.compile_literal_or_variable( let (slot_index, index_cleanup) = self.compile_operand(*slot_index, scope)?;
LiteralOrVariable::Literal(slot_index.node),
scope,
)?;
let (logic_type, type_cleanup) = self.compile_literal_or_variable( let (logic_type, type_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(logic_type.node), LiteralOrVariable::Literal(logic_type.node),
scope, scope,

View File

@@ -1834,20 +1834,8 @@ impl<'a> Parser<'a> {
let mut args = args!(3); let mut args = args!(3);
let next = args.next(); let next = args.next();
let dev_name = literal_or_variable!(next); let dev_name = literal_or_variable!(next);
let next = args.next(); let slot_index = args.next().ok_or(Error::UnexpectedEOF)?;
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 next = args.next(); let next = args.next();
let slot_logic = get_arg!(Literal, literal_or_variable!(next)); let slot_logic = get_arg!(Literal, literal_or_variable!(next));
if !matches!( if !matches!(
@@ -1864,27 +1852,17 @@ impl<'a> Parser<'a> {
} }
Ok(SysCall::System(System::LoadSlot( Ok(SysCall::System(System::LoadSlot(
dev_name, slot_index, slot_logic, dev_name,
boxed!(slot_index),
slot_logic,
))) )))
} }
"setSlot" | "ss" => { "setSlot" | "ss" => {
let mut args = args!(4); let mut args = args!(4);
let next = args.next(); let next = args.next();
let dev_name = literal_or_variable!(next); let dev_name = literal_or_variable!(next);
let next = args.next(); let slot_index = args.next().ok_or(Error::UnexpectedEOF)?;
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 next = args.next(); let next = args.next();
let slot_logic = get_arg!(Literal, literal_or_variable!(next)); let slot_logic = get_arg!(Literal, literal_or_variable!(next));
if !matches!( if !matches!(
@@ -1904,9 +1882,9 @@ impl<'a> Parser<'a> {
Ok(SysCall::System(System::SetSlot( Ok(SysCall::System(System::SetSlot(
dev_name, dev_name,
slot_index, boxed!(slot_index),
slot_logic, slot_logic,
Box::new(expr), boxed!(expr),
))) )))
} }
"loadReagent" | "lr" => { "loadReagent" | "lr" => {

View File

@@ -223,7 +223,7 @@ documented! {
/// `let isOccupied = ls(deviceHash, 2, "Occupied");` /// `let isOccupied = ls(deviceHash, 2, "Occupied");`
LoadSlot( LoadSlot(
Spanned<LiteralOrVariable<'a>>, Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>, Box<Spanned<Expression<'a>>>,
Spanned<Literal<'a>> Spanned<Literal<'a>>
), ),
/// Stores a value of LogicType on a device by the index value /// Stores a value of LogicType on a device by the index value
@@ -234,7 +234,7 @@ documented! {
/// `ss(deviceHash, 0, "Open", true);` /// `ss(deviceHash, 0, "Open", true);`
SetSlot( SetSlot(
Spanned<LiteralOrVariable<'a>>, Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>, Box<Spanned<Expression<'a>>>,
Spanned<Literal<'a>>, Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>> Box<Spanned<Expression<'a>>>
), ),