diff --git a/build.sh b/build.sh
index ba83ba6..533eb3f 100755
--- a/build.sh
+++ b/build.sh
@@ -39,6 +39,7 @@ echo "--------------------"
RUST_WIN_EXE="$RUST_DIR/target/x86_64-pc-windows-gnu/release/slang.exe"
RUST_LINUX_BIN="$RUST_DIR/target/x86_64-unknown-linux-gnu/release/slang"
CHARP_DLL="$CSHARP_DIR/bin/Release/net46/StationeersSlang.dll"
+CHARP_PDB="$CSHARP_DIR/bin/Release/net46/StationeersSlang.pdb"
# Check if the release dir exists, if not: create it.
if [[ ! -d "$RELEASE_DIR" ]]; then
@@ -48,3 +49,4 @@ fi
cp "$RUST_WIN_EXE" "$RELEASE_DIR/slang.exe"
cp "$RUST_LINUX_BIN" "$RELEASE_DIR/slang"
cp "$CHARP_DLL" "$RELEASE_DIR/StationeersSlang.dll"
+cp "$CHARP_PDB" "$RELEASE_DIR/StationeersSlang.pdb"
diff --git a/csharp_mod/Extensions.cs b/csharp_mod/Extensions.cs
index e957a88..764b6c9 100644
--- a/csharp_mod/Extensions.cs
+++ b/csharp_mod/Extensions.cs
@@ -1,66 +1,103 @@
+namespace Slang;
+
using System;
using System.Text;
using StationeersIC10Editor;
-namespace Slang
+public static unsafe class SlangExtensions
{
- public static unsafe class SlangExtensions
+ /**
+ *
+ * This is a helper method to convert a Rust struct for a string pointer
+ * into a C# style string.
+ *
+ */
+ public static string AsString(this Vec_uint8_t vec)
{
- /**
- *
- * This is a helper method to convert a Rust struct for a string pointer
- * into a C# style string.
- *
- */
- public static string AsString(this Vec_uint8_t vec)
+ if (vec.ptr == null || vec.len == UIntPtr.Zero)
{
- if (vec.ptr == null || vec.len == UIntPtr.Zero)
- {
- return string.Empty;
- }
-
- // Rust strings are UTF-8. Read bytes from raw pointer.
- var toReturn = Encoding.UTF8.GetString(vec.ptr, (int)vec.len);
-
- return toReturn;
+ return string.Empty;
}
- /**
- * This will free a Rust string struct. Because this is a pointer to a struct, this memory
- * is managed by Rust, therefor it must be freed by Rust
- *
- */
- public static void Drop(this Vec_uint8_t vec)
+ // Rust strings are UTF-8. Read bytes from raw pointer.
+ var toReturn = Encoding.UTF8.GetString(vec.ptr, (int)vec.len);
+
+ return toReturn;
+ }
+
+ /**
+ * This will free a Rust string struct. Because this is a pointer to a struct, this memory
+ * is managed by Rust, therefor it must be freed by Rust
+ *
+ */
+ public static void Drop(this Vec_uint8_t vec)
+ {
+ Ffi.free_string(vec);
+ }
+
+ /**
+ * This helper converts a Rust vec to a C# List. This handles freeing the
+ * Rust allocation after the List is created, there is no need to Drop this memory.
+ *
+ */
+ public static Line ToLine(this Vec_FfiToken_t vec, string sourceText)
+ {
+ var list = new Line(sourceText);
+
+ var currentPtr = vec.ptr;
+
+ // Iterate through the raw memory array
+ for (int i = 0; i < (int)vec.len; i++)
{
- Ffi.free_string(vec);
+ var token = currentPtr[i];
+
+ var color = GetColorForKind(token.token_kind);
+
+ int colIndex = token.column;
+ if (colIndex < 0)
+ colIndex = 0;
+
+ var semanticToken = new SemanticToken(
+ 0,
+ colIndex,
+ token.length,
+ color,
+ token.token_kind
+ );
+
+ string errMsg = token.error.AsString();
+ if (!string.IsNullOrEmpty(errMsg))
+ {
+ semanticToken.IsError = true;
+ semanticToken.Data = errMsg;
+ semanticToken.Color = ICodeFormatter.ColorError;
+ }
+ list.AddToken(semanticToken);
}
- /**
- * This helper converts a Rust vec to a C# List. This handles freeing the
- * Rust allocation after the List is created, there is no need to Drop this memory.
- *
- */
- public static Line AsList(this Vec_FfiToken_t vec)
+ Ffi.free_ffi_token_vec(vec);
+
+ return list;
+ }
+
+ private static uint GetColorForKind(uint kind)
+ {
+ switch (kind)
{
- var list = new Line();
- list.Capacity = (int)vec.len;
-
- var currentPtr = vec.ptr;
-
- // Iterate through the raw memory array
- for (int i = 0; i < (int)vec.len; i++)
- {
- // Dereference pointer to get the struct at index i
- FfiToken_t token = currentPtr[i];
-
- var newToken = new Token(token.text.AsString(), token.column);
-
- list.Add(newToken);
- }
-
- Ffi.free_ffi_token_vec(vec);
-
- return list;
+ case 1:
+ return SlangFormatter.ColorInstruction; // Keyword
+ case 2:
+ return SlangFormatter.ColorDefault; // Identifier
+ case 3:
+ return SlangFormatter.ColorNumber; // Number
+ case 4:
+ return SlangFormatter.ColorString; // String
+ case 5:
+ return SlangFormatter.ColorInstruction; // Boolean
+ case 6:
+ return SlangFormatter.ColorDefault; // Symbol
+ default:
+ return SlangFormatter.ColorDefault;
}
}
}
diff --git a/csharp_mod/FfiGlue.cs b/csharp_mod/FfiGlue.cs
index da8fd3b..400805d 100644
--- a/csharp_mod/FfiGlue.cs
+++ b/csharp_mod/FfiGlue.cs
@@ -83,17 +83,17 @@ public unsafe partial class Ffi {
slice_ref_uint16_t input);
}
-[StructLayout(LayoutKind.Sequential, Size = 104)]
+[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FfiToken_t {
- public Vec_uint8_t text;
-
public Vec_uint8_t tooltip;
public Vec_uint8_t error;
- public Vec_uint8_t status;
-
public Int32 column;
+
+ public Int32 length;
+
+ public UInt32 token_kind;
}
///
diff --git a/csharp_mod/Formatter.cs b/csharp_mod/Formatter.cs
index 6bd38fd..29c1822 100644
--- a/csharp_mod/Formatter.cs
+++ b/csharp_mod/Formatter.cs
@@ -1,12 +1,24 @@
+namespace Slang;
+
using StationeersIC10Editor;
-namespace Slang
+public class SlangFormatter : ICodeFormatter
{
- public class SlangFormatter : ICodeFormatter
+ public static readonly uint ColorInstruction = ColorFromHTML("#ffff00");
+ public static readonly uint ColorString = ColorFromHTML("#ce9178");
+
+ public override Line ParseLine(string line)
{
- public override Line ParseLine(string line)
+ return Marshal.TokenizeLine(line);
+ }
+
+ public override string Compile()
+ {
+ if (Marshal.CompileFromString(this.Lines.RawText, out string compiled))
{
- return Marshal.TokenizeLine(line);
+ return compiled;
}
+
+ return string.Empty;
}
}
diff --git a/csharp_mod/Marshal.cs b/csharp_mod/Marshal.cs
index bc52a99..e0b54ab 100644
--- a/csharp_mod/Marshal.cs
+++ b/csharp_mod/Marshal.cs
@@ -1,59 +1,157 @@
+namespace Slang;
+
using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
using StationeersIC10Editor;
-namespace Slang
+public static class Marshal
{
- public static class Marshal
- {
- public static unsafe Line TokenizeLine(string source)
- {
- if (String.IsNullOrEmpty(source))
- {
- return new Line();
- }
+ private static IntPtr _libraryHandle = IntPtr.Zero;
- fixed (char* ptrString = source)
- {
- var input = new slice_ref_uint16_t
- {
- ptr = (ushort*)ptrString,
- len = (UIntPtr)source.Length,
- };
- return Ffi.tokenize_line(input).AsList();
- }
+ [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
+ private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
+
+ [DllImport("kernel32", SetLastError = true)]
+ private static extern bool FreeLibrary(IntPtr hModule);
+
+ private static bool EnsureLibLoaded()
+ {
+ if (_libraryHandle != IntPtr.Zero)
+ {
+ return true;
}
- public static unsafe bool CompileFromString(string inputString, out string compiledString)
+ try
{
- if (String.IsNullOrEmpty(inputString))
+ _libraryHandle = LoadLibrary(ExtractNativeLibrary(Ffi.RustLib));
+ CodeFormatters.RegisterFormatter("Slang", typeof(SlangFormatter), true);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ L.Error($"Failed to init slang compiler: {ex.Message}");
+ return false;
+ }
+ }
+
+ public static bool Init()
+ {
+ return EnsureLibLoaded();
+ }
+
+ public static bool Destroy()
+ {
+ if (_libraryHandle == IntPtr.Zero)
+ {
+ return true;
+ }
+
+ try
+ {
+ FreeLibrary(_libraryHandle);
+ _libraryHandle = IntPtr.Zero;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ L.Warning($"Unable to free handle to slang compiler's dll. {ex.Message}");
+ return false;
+ }
+ }
+
+ public static unsafe Line TokenizeLine(string source)
+ {
+ if (String.IsNullOrEmpty(source))
+ {
+ return new Line(source);
+ }
+
+ if (!EnsureLibLoaded())
+ {
+ return new Line(source);
+ }
+
+ fixed (char* ptrString = source)
+ {
+ var input = new slice_ref_uint16_t
{
- compiledString = String.Empty;
- return false;
+ ptr = (ushort*)ptrString,
+ len = (UIntPtr)source.Length,
+ };
+ return Ffi.tokenize_line(input).ToLine(source);
+ }
+ }
+
+ public static unsafe bool CompileFromString(string inputString, out string compiledString)
+ {
+ if (String.IsNullOrEmpty(inputString))
+ {
+ compiledString = String.Empty;
+ return false;
+ }
+
+ if (!EnsureLibLoaded())
+ {
+ compiledString = String.Empty;
+ return false;
+ }
+
+ fixed (char* ptrString = inputString)
+ {
+ var input = new slice_ref_uint16_t
+ {
+ ptr = (ushort*)ptrString,
+ len = (UIntPtr)inputString.Length,
+ };
+
+ var result = Ffi.compile_from_string(input);
+ try
+ {
+ if ((ulong)result.len < 1)
+ {
+ compiledString = String.Empty;
+ return false;
+ }
+ compiledString = result.AsString();
+ return true;
+ }
+ finally
+ {
+ result.Drop();
+ }
+ }
+ }
+
+ private static string ExtractNativeLibrary(string libName)
+ {
+ string destinationPath = Path.Combine(Path.GetTempPath(), libName);
+
+ Assembly assembly = Assembly.GetExecutingAssembly();
+
+ using (Stream stream = assembly.GetManifestResourceStream(libName))
+ {
+ if (stream == null)
+ {
+ L.Error(
+ $"{libName} not found. This means it was not embedded in the mod. Please contact the mod author!"
+ );
+ return "";
}
- fixed (char* ptrString = inputString)
+ try
{
- var input = new slice_ref_uint16_t
+ using (FileStream fileStream = new FileStream(destinationPath, FileMode.Create))
{
- ptr = (ushort*)ptrString,
- len = (UIntPtr)inputString.Length,
- };
-
- var result = Ffi.compile_from_string(input);
- try
- {
- if ((ulong)result.len < 1)
- {
- compiledString = String.Empty;
- return false;
- }
- compiledString = result.AsString();
- return true;
- }
- finally
- {
- result.Drop();
+ stream.CopyTo(fileStream);
}
+ return destinationPath;
+ }
+ catch (IOException e)
+ {
+ L.Warning($"Could not overwrite {libName} (it might be in use): {e.Message}");
+ return "";
}
}
}
diff --git a/csharp_mod/Plugin.cs b/csharp_mod/Plugin.cs
index b559d1f..09bf66e 100644
--- a/csharp_mod/Plugin.cs
+++ b/csharp_mod/Plugin.cs
@@ -1,9 +1,6 @@
-using System.IO;
-using System.Reflection;
using System.Text.RegularExpressions;
using BepInEx;
using HarmonyLib;
-using StationeersIC10Editor;
namespace Slang
{
@@ -44,6 +41,8 @@ namespace Slang
public const string PluginGuid = "com.biddydev.slang";
public const string PluginName = "Slang";
+ private Harmony? _harmony;
+
private static Regex? _slangSourceCheck = null;
private static Regex SlangSourceCheck
@@ -89,44 +88,28 @@ namespace Slang
private void Awake()
{
L.SetLogger(Logger);
+ this._harmony = new Harmony(PluginGuid);
+ L.Info("slang loaded");
- if (ExtractNativeDll(Ffi.RustLib))
+ // If we failed to load the compiler, bail from the rest of the patches. It won't matter,
+ // as the compiler itself has failed to load.
+ if (!Marshal.Init())
{
- var harmony = new Harmony(PluginGuid);
- harmony.PatchAll();
- CodeFormatters.RegisterFormatter("slang", () => new SlangFormatter(), true);
+ return;
}
+
+ this._harmony.PatchAll();
}
- private bool ExtractNativeDll(string fileName)
+ private void OnDestroy()
{
- string destinationPath = Path.Combine(Path.GetDirectoryName(Info.Location), fileName);
-
- Assembly assembly = Assembly.GetExecutingAssembly();
-
- using (Stream stream = assembly.GetManifestResourceStream(fileName))
+ if (Marshal.Destroy())
{
- if (stream == null)
- {
- L.Error(
- $"{Ffi.RustLib} not found. This means it was not embedded in the mod. Please contact the mod author!"
- );
- return false;
- }
-
- try
- {
- using (FileStream fileStream = new FileStream(destinationPath, FileMode.Create))
- {
- stream.CopyTo(fileStream);
- }
- return true;
- }
- catch (IOException e)
- {
- L.Warning($"Could not overwrite {fileName} (it might be in use): {e.Message}");
- return false;
- }
+ L.Info("FFI references cleaned up.");
+ }
+ if (this._harmony is not null)
+ {
+ this._harmony.UnpatchSelf();
}
}
}
diff --git a/rust_compiler/libs/tokenizer/src/lib.rs b/rust_compiler/libs/tokenizer/src/lib.rs
index 8909d5b..8f2dc41 100644
--- a/rust_compiler/libs/tokenizer/src/lib.rs
+++ b/rust_compiler/libs/tokenizer/src/lib.rs
@@ -56,7 +56,7 @@ impl<'a> Tokenizer<'a> {
Ok(Self {
reader,
line: 1,
- column: 1,
+ column: 0, // Start at 0 so first char becomes 1
char_buffer: [0],
returned_eof: false,
string_buffer: String::new(),
@@ -71,7 +71,7 @@ impl<'a> From for Tokenizer<'a> {
Self {
reader,
line: 1,
- column: 1,
+ column: 0,
char_buffer: [0],
returned_eof: false,
string_buffer: String::new(),
@@ -84,7 +84,7 @@ impl<'a> From<&'a str> for Tokenizer<'a> {
Self {
reader: BufReader::new(Box::new(Cursor::new(value)) as Box),
char_buffer: [0],
- column: 1,
+ column: 0,
line: 1,
returned_eof: false,
string_buffer: String::new(),
@@ -93,12 +93,6 @@ impl<'a> From<&'a str> for Tokenizer<'a> {
}
impl<'a> Tokenizer<'a> {
- /// Consumes the tokenizer and returns the next token in the stream
- /// If there are no more tokens in the stream, this function returns None
- /// If there is an error reading the stream, this function returns an error
- ///
- /// # Important
- /// This function will increment the line and column counters
fn next_char(&mut self) -> Result