diff --git a/.gitignore b/.gitignore
index b7c3df2..0c2f875 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
-/target
-*.ic10
\ No newline at end of file
+target
+*.ic10
+release
+csharp_mod/bin
+obj
+ref
diff --git a/build.sh b/build.sh
index 22273a4..ba83ba6 100755
--- a/build.sh
+++ b/build.sh
@@ -2,21 +2,49 @@
set -e
+RUST_DIR="rust_compiler"
+CSHARP_DIR="csharp_mod"
+RELEASE_DIR="release"
+
export RUSTFLAGS="--remap-path-prefix=${PWD}=. --remap-path-prefix=${HOME}/.cargo=~/.cargo"
-# -- Build for Native (Linux x86-64) --
-echo "Building native (Linux x86-64) executable..."
-cargo build --release --target=x86_64-unknown-linux-gnu
-echo "Native build complete."
echo "--------------------"
+cd "$RUST_DIR"
+echo "Building native Rust binaries and libs"
+
+# -- Build for Native (Linux x86-64) --
+cargo build --release --target=x86_64-unknown-linux-gnu
# -- Build for Windows (x86-64) --
-echo "Building Windows (x86-64) dll and executable..."
cargo build --release --target=x86_64-pc-windows-gnu
-echo "Windows build successful."
+
+# -- Generate C# Headers --
+cargo run --features headers --bin generate-headers
+
+cd ..
echo "--------------------"
-echo "All builds successful"
-echo "Linux executable at target/x86_64-unknown-linux-gnu/release/slang"
-echo "Windows .exe at target/x86_64-pc-windows-gnu/release/slang.exe"
-echo "Windows .dll at target/x86_64-pc-windows-gnu/release/slang.dll"
+echo "Building C# mod"
+echo "--------------------"
+
+cd "$CSHARP_DIR"
+dotnet build -c Release
+
+cd ..
+echo "--------------------"
+
+echo "Copying Release files to output directory"
+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"
+
+# Check if the release dir exists, if not: create it.
+if [[ ! -d "$RELEASE_DIR" ]]; then
+ mkdir "$RELEASE_DIR"
+fi
+
+cp "$RUST_WIN_EXE" "$RELEASE_DIR/slang.exe"
+cp "$RUST_LINUX_BIN" "$RELEASE_DIR/slang"
+cp "$CHARP_DLL" "$RELEASE_DIR/StationeersSlang.dll"
diff --git a/clean.sh b/clean.sh
new file mode 100755
index 0000000..c88bbd1
--- /dev/null
+++ b/clean.sh
@@ -0,0 +1,18 @@
+echo "Cleaning build directories"
+
+CSHARP_DIR="csharp_mod"
+RUST_DIR="rust_compiler"
+
+cd "$CSHARP_DIR"
+dotnet clean
+
+cd ..
+
+cd "$RUST_DIR"
+cargo clean
+
+cd ..
+
+if [[ -d "release" ]]; then
+ rm -rd release
+fi
diff --git a/csharp_mod/Extensions.cs b/csharp_mod/Extensions.cs
new file mode 100644
index 0000000..e957a88
--- /dev/null
+++ b/csharp_mod/Extensions.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Text;
+using StationeersIC10Editor;
+
+namespace Slang
+{
+ 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)
+ {
+ 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;
+ }
+
+ /**
+ * 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 AsList(this Vec_FfiToken_t vec)
+ {
+ 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;
+ }
+ }
+}
diff --git a/csharp_mod/FfiGlue.cs b/csharp_mod/FfiGlue.cs
new file mode 100644
index 0000000..da8fd3b
--- /dev/null
+++ b/csharp_mod/FfiGlue.cs
@@ -0,0 +1,136 @@
+/*! \file */
+/*******************************************
+ * *
+ * File auto-generated by `::safer_ffi`. *
+ * *
+ * Do not manually edit this file. *
+ * *
+ *******************************************/
+
+#pragma warning disable IDE0044, IDE0049, IDE0055, IDE1006,
+#pragma warning disable SA1004, SA1008, SA1023, SA1028,
+#pragma warning disable SA1121, SA1134,
+#pragma warning disable SA1201,
+#pragma warning disable SA1300, SA1306, SA1307, SA1310, SA1313,
+#pragma warning disable SA1500, SA1505, SA1507,
+#pragma warning disable SA1600, SA1601, SA1604, SA1605, SA1611, SA1615, SA1649,
+
+namespace Slang {
+using System;
+using System.Runtime.InteropServices;
+
+public unsafe partial class Ffi {
+#if IOS
+ private const string RustLib = "slang.framework/slang";
+#else
+ public const string RustLib = "slang_compiler.dll";
+#endif
+}
+
+///
+/// &'lt [T] but with a guaranteed #[repr(C)] layout.
+///
+/// # C layout (for some given type T)
+///
+/// ```c
+/// typedef struct {
+/// // Cannot be NULL
+/// T * ptr;
+/// size_t len;
+/// } slice_T;
+/// ```
+///
+/// # Nullable pointer?
+///
+/// If you want to support the above typedef, but where the ptr field is
+/// allowed to be NULL (with the contents of len then being undefined)
+/// use the Option< slice_ptr<_> > type.
+///
+[StructLayout(LayoutKind.Sequential, Size = 16)]
+public unsafe struct slice_ref_uint16_t {
+ ///
+ /// Pointer to the first element (if any).
+ ///
+ public UInt16 /*const*/ * ptr;
+
+ ///
+ /// Element count
+ ///
+ public UIntPtr len;
+}
+
+///
+/// Same as [Vec][rust::Vec], but with guaranteed #[repr(C)] layout
+///
+[StructLayout(LayoutKind.Sequential, Size = 24)]
+public unsafe struct Vec_uint8_t {
+ public byte * ptr;
+
+ public UIntPtr len;
+
+ public UIntPtr cap;
+}
+
+public unsafe partial class Ffi {
+ ///
+ /// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
+ /// we want to avoid GC. So we pass it to Rust to handle all the memory allocations.
+ /// This should result in the ability to compile many times without triggering frame drops
+ /// from the GC from a GetBytes() call on a string in C#.
+ ///
+ [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
+ Vec_uint8_t compile_from_string (
+ slice_ref_uint16_t input);
+}
+
+[StructLayout(LayoutKind.Sequential, Size = 104)]
+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;
+}
+
+///
+/// Same as [Vec][rust::Vec], but with guaranteed #[repr(C)] layout
+///
+[StructLayout(LayoutKind.Sequential, Size = 24)]
+public unsafe struct Vec_FfiToken_t {
+ public FfiToken_t * ptr;
+
+ public UIntPtr len;
+
+ public UIntPtr cap;
+}
+
+public unsafe partial class Ffi {
+ [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
+ void free_ffi_token_vec (
+ Vec_FfiToken_t v);
+}
+
+public unsafe partial class Ffi {
+ [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
+ void free_string (
+ Vec_uint8_t s);
+}
+
+public unsafe partial class Ffi {
+ ///
+ /// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
+ /// we want to avoid GC. So we pass it to Rust to handle all the memory allocations.
+ /// This should result in the ability to tokenize many times without triggering frame drops
+ /// from the GC from a GetBytes() call on a string in C#.
+ ///
+ [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
+ Vec_FfiToken_t tokenize_line (
+ slice_ref_uint16_t input);
+}
+
+
+} /* Slang */
diff --git a/csharp_mod/Formatter.cs b/csharp_mod/Formatter.cs
new file mode 100644
index 0000000..6bd38fd
--- /dev/null
+++ b/csharp_mod/Formatter.cs
@@ -0,0 +1,12 @@
+using StationeersIC10Editor;
+
+namespace Slang
+{
+ public class SlangFormatter : ICodeFormatter
+ {
+ public override Line ParseLine(string line)
+ {
+ return Marshal.TokenizeLine(line);
+ }
+ }
+}
diff --git a/csharp_mod/Marshal.cs b/csharp_mod/Marshal.cs
new file mode 100644
index 0000000..bc52a99
--- /dev/null
+++ b/csharp_mod/Marshal.cs
@@ -0,0 +1,60 @@
+using System;
+using StationeersIC10Editor;
+
+namespace Slang
+{
+ public static class Marshal
+ {
+ public static unsafe Line TokenizeLine(string source)
+ {
+ if (String.IsNullOrEmpty(source))
+ {
+ return new Line();
+ }
+
+ fixed (char* ptrString = source)
+ {
+ var input = new slice_ref_uint16_t
+ {
+ ptr = (ushort*)ptrString,
+ len = (UIntPtr)source.Length,
+ };
+ return Ffi.tokenize_line(input).AsList();
+ }
+ }
+
+ public static unsafe bool CompileFromString(string inputString, out string compiledString)
+ {
+ if (String.IsNullOrEmpty(inputString))
+ {
+ 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();
+ }
+ }
+ }
+ }
+}
diff --git a/csharp_mod/Patches.cs b/csharp_mod/Patches.cs
new file mode 100644
index 0000000..d4c4d82
--- /dev/null
+++ b/csharp_mod/Patches.cs
@@ -0,0 +1,41 @@
+using System;
+using Assets.Scripts.Objects.Motherboards;
+using HarmonyLib;
+
+namespace Slang
+{
+ [HarmonyPatch]
+ public static class SlangPatches
+ {
+ [HarmonyPatch(
+ typeof(ProgrammableChipMotherboard),
+ nameof(ProgrammableChipMotherboard.InputFinished)
+ )]
+ [HarmonyPrefix]
+ public static void PGM_InputFinished(ref string result)
+ {
+ if (string.IsNullOrEmpty(result) || !SlangPlugin.IsSlangSource(ref result))
+ {
+ return;
+ }
+
+ L.Debug("Detected Slang source, compiling...");
+
+ // Compile the Slang source into IC10
+ string compiled = SlangPlugin.Compile(result);
+
+ // Ensure that the string is correct
+ if (string.IsNullOrEmpty(compiled))
+ {
+ return;
+ }
+
+ var newUuid = Guid.NewGuid().ToString();
+
+ SlangPlugin.CopySourceToFile(result);
+
+ // Set the result to be the compiled source so the rest of the function can continue as normal
+ result = compiled;
+ }
+ }
+}
diff --git a/csharp_mod/Plugin.cs b/csharp_mod/Plugin.cs
new file mode 100644
index 0000000..b559d1f
--- /dev/null
+++ b/csharp_mod/Plugin.cs
@@ -0,0 +1,133 @@
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using BepInEx;
+using HarmonyLib;
+using StationeersIC10Editor;
+
+namespace Slang
+{
+ class L
+ {
+ private static BepInEx.Logging.ManualLogSource? _logger;
+
+ public static void SetLogger(BepInEx.Logging.ManualLogSource logger)
+ {
+ _logger = logger;
+ }
+
+ public static void Debug(string message)
+ {
+ _logger?.LogDebug(message);
+ }
+
+ public static void Info(string message)
+ {
+ _logger?.LogInfo(message);
+ }
+
+ public static void Error(string message)
+ {
+ _logger?.LogError(message);
+ }
+
+ public static void Warning(string message)
+ {
+ _logger?.LogWarning(message);
+ }
+ }
+
+ [BepInPlugin(PluginGuid, PluginName, "0.1.0")]
+ [BepInDependency(StationeersIC10Editor.IC10EditorPlugin.PluginGuid)]
+ public class SlangPlugin : BaseUnityPlugin
+ {
+ public const string PluginGuid = "com.biddydev.slang";
+ public const string PluginName = "Slang";
+
+ private static Regex? _slangSourceCheck = null;
+
+ private static Regex SlangSourceCheck
+ {
+ get
+ {
+ if (_slangSourceCheck is null)
+ {
+ _slangSourceCheck = new Regex(@"[;{}()]|\b(let|fn|device)\b|\/\/");
+ }
+
+ return _slangSourceCheck;
+ }
+ }
+
+ public static unsafe string Compile(string source)
+ {
+ string compiled;
+ if (Marshal.CompileFromString(source, out compiled))
+ {
+ // TODO: handle saving the original source code
+ return compiled;
+ }
+ else
+ {
+ return compiled;
+ }
+ }
+
+ /// Take original slang source code and copies it to a file
+ /// for use in restoring later.
+ ///
+ public static bool CopySourceToFile(string source)
+ {
+ return true;
+ }
+
+ public static bool IsSlangSource(ref string input)
+ {
+ return SlangSourceCheck.IsMatch(input);
+ }
+
+ private void Awake()
+ {
+ L.SetLogger(Logger);
+
+ if (ExtractNativeDll(Ffi.RustLib))
+ {
+ var harmony = new Harmony(PluginGuid);
+ harmony.PatchAll();
+ CodeFormatters.RegisterFormatter("slang", () => new SlangFormatter(), true);
+ }
+ }
+
+ private bool ExtractNativeDll(string fileName)
+ {
+ string destinationPath = Path.Combine(Path.GetDirectoryName(Info.Location), fileName);
+
+ Assembly assembly = Assembly.GetExecutingAssembly();
+
+ using (Stream stream = assembly.GetManifestResourceStream(fileName))
+ {
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/csharp_mod/StationpediaDocumentation.cs b/csharp_mod/StationpediaDocumentation.cs
new file mode 100644
index 0000000..e03814a
--- /dev/null
+++ b/csharp_mod/StationpediaDocumentation.cs
@@ -0,0 +1,22 @@
+using Assets.Scripts.UI;
+
+namespace Slang
+{
+ public static class SlangDocs
+ {
+ public static StationpediaPage[] Pages
+ {
+ get
+ {
+ return
+ [
+ new StationpediaPage(
+ "slang-init",
+ "Slang",
+ "Slang is a new high level language built specifically for Stationeers"
+ ),
+ ];
+ }
+ }
+ }
+}
diff --git a/csharp_mod/stationeersSlang.csproj b/csharp_mod/stationeersSlang.csproj
new file mode 100644
index 0000000..c281b7f
--- /dev/null
+++ b/csharp_mod/stationeersSlang.csproj
@@ -0,0 +1,61 @@
+
+
+
+ net46
+ enable
+ StationeersSlang
+ Slang Compiler Bridge
+ 0.1.0
+ true
+ latest
+
+
+
+ /home/dbidwell/.local/share/Steam/steamapps/common/Stationeers/
+ $(GameDir)/rocketstation_Data/Managed
+ $(GameDir)/BepInEx/core
+
+
+
+
+ $(ManagedDir)/netstandard.dll
+ False
+
+
+ $(BepInExDir)/BepInEx.dll
+ False
+
+
+ $(BepInExDir)/0Harmony.dll
+ False
+
+
+
+ $(ManagedDir)/UnityEngine.dll
+ False
+
+
+ $(ManagedDir)/UnityEngine.CoreModule.dll
+ False
+
+
+ $(ManagedDir)/Assembly-CSharp.dll
+ False
+
+
+ $(ManagedDir)/Assembly-CSharp-firstpass.dll
+ False
+
+
+ ./ref/IC10Editor.dll
+ False
+
+
+
+
+
+ slang_compiler.dll
+
+
+
+
diff --git a/Cargo.lock b/rust_compiler/Cargo.lock
similarity index 78%
rename from Cargo.lock
rename to rust_compiler/Cargo.lock
index 6348b0f..7a9df42 100644
--- a/Cargo.lock
+++ b/rust_compiler/Cargo.lock
@@ -265,6 +265,41 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+[[package]]
+name = "ext-trait"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5"
+dependencies = [
+ "ext-trait-proc_macros",
+]
+
+[[package]]
+name = "ext-trait-proc_macros"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "extension-traits"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537"
+dependencies = [
+ "ext-trait",
+]
+
+[[package]]
+name = "extern-c"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23"
+
[[package]]
name = "funty"
version = "2.0.0"
@@ -328,6 +363,15 @@ dependencies = [
"rustversion",
]
+[[package]]
+name = "inventory"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
+dependencies = [
+ "rustversion",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -342,9 +386,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
-version = "0.3.82"
+version = "0.3.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -356,6 +400,22 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
+[[package]]
+name = "macro_rules_attribute"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862"
+dependencies = [
+ "macro_rules_attribute-proc_macro",
+ "paste",
+]
+
+[[package]]
+name = "macro_rules_attribute-proc_macro"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d"
+
[[package]]
name = "memchr"
version = "2.7.6"
@@ -410,6 +470,12 @@ dependencies = [
"tokenizer",
]
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -429,6 +495,16 @@ dependencies = [
"yansi",
]
+[[package]]
+name = "prettyplease"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
@@ -578,6 +654,15 @@ version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -590,12 +675,56 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+[[package]]
+name = "safer-ffi"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd"
+dependencies = [
+ "extern-c",
+ "inventory",
+ "libc",
+ "macro_rules_attribute",
+ "paste",
+ "safer_ffi-proc_macros",
+ "scopeguard",
+ "stabby",
+ "uninit",
+ "unwind_safe",
+ "with_builtin_macros",
+]
+
+[[package]]
+name = "safer_ffi-proc_macros"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156"
+dependencies = [
+ "macro_rules_attribute",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -638,6 +767,12 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "sha2-const-stable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9"
+
[[package]]
name = "simdutf8"
version = "0.1.5"
@@ -645,7 +780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
-name = "stationlang"
+name = "slang"
version = "0.1.0"
dependencies = [
"anyhow",
@@ -654,9 +789,45 @@ dependencies = [
"parser",
"quick-error",
"rust_decimal",
+ "safer-ffi",
"tokenizer",
]
+[[package]]
+name = "stabby"
+version = "36.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8"
+dependencies = [
+ "rustversion",
+ "stabby-abi",
+]
+
+[[package]]
+name = "stabby-abi"
+version = "36.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a"
+dependencies = [
+ "rustc_version",
+ "rustversion",
+ "sha2-const-stable",
+ "stabby-macros",
+]
+
+[[package]]
+name = "stabby-macros"
+version = "36.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "rand",
+ "syn 1.0.109",
+]
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -751,6 +922,21 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+[[package]]
+name = "uninit"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6"
+dependencies = [
+ "extension-traits",
+]
+
+[[package]]
+name = "unwind_safe"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3"
+
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -781,9 +967,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
-version = "0.2.105"
+version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
dependencies = [
"cfg-if",
"once_cell",
@@ -794,9 +980,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.105"
+version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -804,9 +990,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.105"
+version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -817,9 +1003,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.105"
+version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
dependencies = [
"unicode-ident",
]
@@ -841,13 +1027,33 @@ dependencies = [
[[package]]
name = "winnow"
-version = "0.7.13"
+version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
+[[package]]
+name = "with_builtin_macros"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da"
+dependencies = [
+ "with_builtin_macros-proc_macros",
+]
+
+[[package]]
+name = "with_builtin_macros-proc_macros"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "wyz"
version = "0.5.1"
diff --git a/Cargo.toml b/rust_compiler/Cargo.toml
similarity index 69%
rename from Cargo.toml
rename to rust_compiler/Cargo.toml
index 5939f95..ae125f6 100644
--- a/Cargo.toml
+++ b/rust_compiler/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "stationlang"
+name = "slang"
version = "0.1.0"
edition = "2021"
@@ -9,6 +9,10 @@ members = ["libs/*"]
[workspace.dependencies]
quick-error = "2"
rust_decimal = "1"
+safer-ffi = { version = "0.1" }
+
+[features]
+headers = ["safer-ffi/headers"]
[profile.release]
strip = true
@@ -17,10 +21,15 @@ strip = true
name = "slang"
path = "src/main.rs"
+[[bin]]
+name = "generate-headers"
+path = "src/bin/generate_headers.rs"
+required-features = ["headers"]
+
[lib]
name = "slang"
path = "src/lib.rs"
-crate-type = ["cdylib"]
+crate-type = ["cdylib", "rlib"]
[dependencies]
clap = { version = "^4.5", features = ["derive"] }
@@ -29,7 +38,7 @@ rust_decimal = { workspace = true }
tokenizer = { path = "libs/tokenizer" }
parser = { path = "libs/parser" }
compiler = { path = "libs/compiler" }
-
+safer-ffi = { workspace = true }
[dev-dependencies]
anyhow = { version = "^1.0", features = ["backtrace"] }
diff --git a/libs/compiler/Cargo.toml b/rust_compiler/libs/compiler/Cargo.toml
similarity index 100%
rename from libs/compiler/Cargo.toml
rename to rust_compiler/libs/compiler/Cargo.toml
diff --git a/libs/compiler/src/lib.rs b/rust_compiler/libs/compiler/src/lib.rs
similarity index 100%
rename from libs/compiler/src/lib.rs
rename to rust_compiler/libs/compiler/src/lib.rs
diff --git a/libs/compiler/src/test/binary_expression.rs b/rust_compiler/libs/compiler/src/test/binary_expression.rs
similarity index 100%
rename from libs/compiler/src/test/binary_expression.rs
rename to rust_compiler/libs/compiler/src/test/binary_expression.rs
diff --git a/libs/compiler/src/test/branching.rs b/rust_compiler/libs/compiler/src/test/branching.rs
similarity index 99%
rename from libs/compiler/src/test/branching.rs
rename to rust_compiler/libs/compiler/src/test/branching.rs
index fb06024..d23d880 100644
--- a/libs/compiler/src/test/branching.rs
+++ b/rust_compiler/libs/compiler/src/test/branching.rs
@@ -155,4 +155,3 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
Ok(())
}
-
diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs
similarity index 100%
rename from libs/compiler/src/test/declaration_function_invocation.rs
rename to rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs
diff --git a/libs/compiler/src/test/declaration_literal.rs b/rust_compiler/libs/compiler/src/test/declaration_literal.rs
similarity index 100%
rename from libs/compiler/src/test/declaration_literal.rs
rename to rust_compiler/libs/compiler/src/test/declaration_literal.rs
diff --git a/libs/compiler/src/test/function_declaration.rs b/rust_compiler/libs/compiler/src/test/function_declaration.rs
similarity index 100%
rename from libs/compiler/src/test/function_declaration.rs
rename to rust_compiler/libs/compiler/src/test/function_declaration.rs
diff --git a/libs/compiler/src/test/logic_expression.rs b/rust_compiler/libs/compiler/src/test/logic_expression.rs
similarity index 100%
rename from libs/compiler/src/test/logic_expression.rs
rename to rust_compiler/libs/compiler/src/test/logic_expression.rs
diff --git a/libs/compiler/src/test/loops.rs b/rust_compiler/libs/compiler/src/test/loops.rs
similarity index 100%
rename from libs/compiler/src/test/loops.rs
rename to rust_compiler/libs/compiler/src/test/loops.rs
diff --git a/libs/compiler/src/test/mod.rs b/rust_compiler/libs/compiler/src/test/mod.rs
similarity index 100%
rename from libs/compiler/src/test/mod.rs
rename to rust_compiler/libs/compiler/src/test/mod.rs
diff --git a/libs/compiler/src/test/syscall.rs b/rust_compiler/libs/compiler/src/test/syscall.rs
similarity index 100%
rename from libs/compiler/src/test/syscall.rs
rename to rust_compiler/libs/compiler/src/test/syscall.rs
diff --git a/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs
similarity index 99%
rename from libs/compiler/src/v1.rs
rename to rust_compiler/libs/compiler/src/v1.rs
index 2b1709d..56b32ca 100644
--- a/libs/compiler/src/v1.rs
+++ b/rust_compiler/libs/compiler/src/v1.rs
@@ -68,7 +68,7 @@ struct CompilationResult {
}
pub struct Compiler<'a, W: std::io::Write> {
- parser: ASTParser,
+ parser: ASTParser<'a>,
function_locations: HashMap,
function_metadata: HashMap>,
devices: HashMap,
@@ -83,7 +83,7 @@ pub struct Compiler<'a, W: std::io::Write> {
impl<'a, W: std::io::Write> Compiler<'a, W> {
pub fn new(
- parser: ASTParser,
+ parser: ASTParser<'a>,
writer: &'a mut BufWriter,
config: Option,
) -> Self {
@@ -1125,9 +1125,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}))
}
- _ => {
- todo!()
- }
+ t => Err(Error::Unknown(format!("{t:?}\n\nNot yet implemented"))),
}
}
diff --git a/libs/compiler/src/variable_manager.rs b/rust_compiler/libs/compiler/src/variable_manager.rs
similarity index 100%
rename from libs/compiler/src/variable_manager.rs
rename to rust_compiler/libs/compiler/src/variable_manager.rs
diff --git a/libs/compiler/test_files/deviceIo.slang b/rust_compiler/libs/compiler/test_files/deviceIo.slang
similarity index 100%
rename from libs/compiler/test_files/deviceIo.slang
rename to rust_compiler/libs/compiler/test_files/deviceIo.slang
diff --git a/libs/parser/Cargo.toml b/rust_compiler/libs/parser/Cargo.toml
similarity index 100%
rename from libs/parser/Cargo.toml
rename to rust_compiler/libs/parser/Cargo.toml
diff --git a/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs
similarity index 99%
rename from libs/parser/src/lib.rs
rename to rust_compiler/libs/parser/src/lib.rs
index 8eca23a..3789894 100644
--- a/libs/parser/src/lib.rs
+++ b/rust_compiler/libs/parser/src/lib.rs
@@ -4,6 +4,7 @@ mod test;
pub mod sys_call;
pub mod tree_node;
+use crate::sys_call::System;
use quick_error::quick_error;
use std::io::SeekFrom;
use sys_call::SysCall;
@@ -13,8 +14,6 @@ use tokenizer::{
};
use tree_node::*;
-use crate::sys_call::System;
-
#[macro_export]
/// A macro to create a boxed value.
macro_rules! boxed {
@@ -114,13 +113,13 @@ macro_rules! token_matches {
};
}
-pub struct Parser {
- tokenizer: TokenizerBuffer,
+pub struct Parser<'a> {
+ tokenizer: TokenizerBuffer<'a>,
current_token: Option,
}
-impl Parser {
- pub fn new(tokenizer: Tokenizer) -> Self {
+impl<'a> Parser<'a> {
+ pub fn new(tokenizer: Tokenizer<'a>) -> Self {
Parser {
tokenizer: TokenizerBuffer::new(tokenizer),
current_token: None,
@@ -1261,7 +1260,9 @@ impl Parser {
let arg = literal_or_variable!(invocation.arguments.first());
Ok(SysCall::Math(sys_call::Math::Trunc(arg)))
}
- _ => todo!(),
+ _ => Err(Error::UnsupportedKeyword(token_from_option!(
+ self.current_token
+ ))),
}
}
}
diff --git a/libs/parser/src/sys_call.rs b/rust_compiler/libs/parser/src/sys_call.rs
similarity index 98%
rename from libs/parser/src/sys_call.rs
rename to rust_compiler/libs/parser/src/sys_call.rs
index bb2c882..576094f 100644
--- a/libs/parser/src/sys_call.rs
+++ b/rust_compiler/libs/parser/src/sys_call.rs
@@ -124,7 +124,7 @@ pub enum System {
/// Loads a LogicType from all connected network devices, aggregating them via a
/// batchMode
/// ## In Game
- /// lb r? deviceHash loggicType batchMode
+ /// lb r? deviceHash logicType batchMode
/// ## Examples
/// lb r0 HASH("StructureWallLight") On Minimum
LoadBatch(LiteralOrVariable, Literal, Literal),
@@ -137,7 +137,7 @@ pub enum System {
/// Represents a function which stores a setting to all devices that match
/// the given deviceHash
/// ## In Game
- /// `sb deviceHash logictype r?`
+ /// `sb deviceHash logicType r?`
/// ## Example
/// `sb HASH("Doors") Lock 1`
SetOnDeviceBatched(LiteralOrVariable, Literal, Box),
diff --git a/libs/parser/src/test/blocks.rs b/rust_compiler/libs/parser/src/test/blocks.rs
similarity index 100%
rename from libs/parser/src/test/blocks.rs
rename to rust_compiler/libs/parser/src/test/blocks.rs
diff --git a/libs/parser/src/test/mod.rs b/rust_compiler/libs/parser/src/test/mod.rs
similarity index 100%
rename from libs/parser/src/test/mod.rs
rename to rust_compiler/libs/parser/src/test/mod.rs
diff --git a/libs/parser/src/tree_node.rs b/rust_compiler/libs/parser/src/tree_node.rs
similarity index 100%
rename from libs/parser/src/tree_node.rs
rename to rust_compiler/libs/parser/src/tree_node.rs
diff --git a/libs/tokenizer/Cargo.toml b/rust_compiler/libs/tokenizer/Cargo.toml
similarity index 100%
rename from libs/tokenizer/Cargo.toml
rename to rust_compiler/libs/tokenizer/Cargo.toml
diff --git a/libs/tokenizer/src/lib.rs b/rust_compiler/libs/tokenizer/src/lib.rs
similarity index 87%
rename from libs/tokenizer/src/lib.rs
rename to rust_compiler/libs/tokenizer/src/lib.rs
index d360bc0..8909d5b 100644
--- a/libs/tokenizer/src/lib.rs
+++ b/rust_compiler/libs/tokenizer/src/lib.rs
@@ -18,18 +18,18 @@ quick_error! {
display("IO Error: {}", err)
source(err)
}
- NumberParseError(err: std::num::ParseIntError, line: usize, column: usize) {
+ NumberParseError(err: std::num::ParseIntError, line: usize, column: usize, original: String) {
display("Number Parse Error: {}\nLine: {}, Column: {}", err, line, column)
source(err)
}
- DecimalParseError(err: rust_decimal::Error, line: usize, column: usize) {
+ DecimalParseError(err: rust_decimal::Error, line: usize, column: usize, original: String) {
display("Decimal Parse Error: {}\nLine: {}, Column: {}", err, line, column)
source(err)
}
- UnknownSymbolError(char: char, line: usize, column: usize) {
+ UnknownSymbolError(char: char, line: usize, column: usize, original: String) {
display("Unknown Symbol: {}\nLine: {}, Column: {}", char, line, column)
}
- UnknownKeywordOrIdentifierError(val: String, line: usize, column: usize) {
+ UnknownKeywordOrIdentifierError(val: String, line: usize, column: usize, original: String) {
display("Unknown Keyword or Identifier: {}\nLine: {}, Column: {}", val, line, column)
}
}
@@ -39,15 +39,16 @@ pub trait Tokenize: Read + Seek {}
impl Tokenize for T where T: Read + Seek {}
-pub struct Tokenizer {
- reader: BufReader>,
+pub struct Tokenizer<'a> {
+ reader: BufReader>,
char_buffer: [u8; 1],
line: usize,
column: usize,
returned_eof: bool,
+ string_buffer: String,
}
-impl Tokenizer {
+impl<'a> Tokenizer<'a> {
pub fn from_path(input_file: impl Into) -> Result {
let file = std::fs::File::open(input_file.into())?;
let reader = BufReader::new(Box::new(file) as Box);
@@ -58,11 +59,12 @@ impl Tokenizer {
column: 1,
char_buffer: [0],
returned_eof: false,
+ string_buffer: String::new(),
})
}
}
-impl From for Tokenizer {
+impl<'a> From for Tokenizer<'a> {
fn from(input: String) -> Self {
let reader = BufReader::new(Box::new(Cursor::new(input)) as Box);
@@ -72,17 +74,25 @@ impl From for Tokenizer {
column: 1,
char_buffer: [0],
returned_eof: false,
+ string_buffer: String::new(),
}
}
}
-impl From<&str> for Tokenizer {
- fn from(value: &str) -> Self {
- Self::from(value.to_string())
+impl<'a> From<&'a str> for Tokenizer<'a> {
+ fn from(value: &'a str) -> Self {
+ Self {
+ reader: BufReader::new(Box::new(Cursor::new(value)) as Box),
+ char_buffer: [0],
+ column: 1,
+ line: 1,
+ returned_eof: false,
+ string_buffer: String::new(),
+ }
}
}
-impl Tokenizer {
+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
@@ -105,6 +115,7 @@ impl Tokenizer {
self.column += 1;
}
+ self.string_buffer.push(c);
Ok(Some(c))
}
@@ -171,7 +182,12 @@ impl Tokenizer {
return self.tokenize_keyword_or_identifier(next_char).map(Some);
}
_ => {
- return Err(Error::UnknownSymbolError(next_char, self.line, self.column));
+ return Err(Error::UnknownSymbolError(
+ next_char,
+ self.line,
+ self.column,
+ std::mem::take(&mut self.string_buffer),
+ ));
}
}
}
@@ -179,7 +195,12 @@ impl Tokenizer {
Ok(None)
} else {
self.returned_eof = true;
- Ok(Some(Token::new(TokenType::EOF, self.line, self.column)))
+ Ok(Some(Token::new(
+ TokenType::EOF,
+ self.line,
+ self.column,
+ Some(std::mem::take(&mut self.string_buffer)),
+ )))
}
}
@@ -206,6 +227,7 @@ impl Tokenizer {
TokenType::Symbol(Symbol::$symbol),
self.line,
self.column,
+ Some(std::mem::take(&mut self.string_buffer)),
))
};
}
@@ -273,6 +295,7 @@ impl Tokenizer {
first_symbol,
self.line,
self.column,
+ std::mem::take(&mut self.string_buffer),
)),
}
}
@@ -322,17 +345,28 @@ impl Tokenizer {
let decimal_scale = decimal.len() as u32;
let number = format!("{}{}", primary, decimal)
.parse::()
- .map_err(|e| Error::NumberParseError(e, self.line, self.column))?;
+ .map_err(|e| {
+ Error::NumberParseError(
+ e,
+ self.line,
+ self.column,
+ std::mem::take(&mut self.string_buffer),
+ )
+ })?;
Number::Decimal(
- Decimal::try_from_i128_with_scale(number, decimal_scale)
- .map_err(|e| Error::DecimalParseError(e, line, column))?,
+ Decimal::try_from_i128_with_scale(number, decimal_scale).map_err(|e| {
+ Error::DecimalParseError(
+ e,
+ line,
+ column,
+ std::mem::take(&mut self.string_buffer),
+ )
+ })?,
)
} else {
- Number::Integer(
- primary
- .parse()
- .map_err(|e| Error::NumberParseError(e, line, column))?,
- )
+ Number::Integer(primary.parse().map_err(|e| {
+ Error::NumberParseError(e, line, column, std::mem::take(&mut self.string_buffer))
+ })?)
};
// check if the next char is a temperature suffix
@@ -341,14 +375,31 @@ impl Tokenizer {
'c' => Temperature::Celsius(number),
'f' => Temperature::Fahrenheit(number),
'k' => Temperature::Kelvin(number),
- _ => return Ok(Token::new(TokenType::Number(number), line, column)),
+ _ => {
+ return Ok(Token::new(
+ TokenType::Number(number),
+ line,
+ column,
+ Some(std::mem::take(&mut self.string_buffer)),
+ ));
+ }
}
.to_kelvin();
self.next_char()?;
- Ok(Token::new(TokenType::Number(temperature), line, column))
+ Ok(Token::new(
+ TokenType::Number(temperature),
+ line,
+ column,
+ Some(std::mem::take(&mut self.string_buffer)),
+ ))
} else {
- Ok(Token::new(TokenType::Number(number), line, column))
+ Ok(Token::new(
+ TokenType::Number(number),
+ line,
+ column,
+ Some(std::mem::take(&mut self.string_buffer)),
+ ))
}
}
@@ -367,7 +418,12 @@ impl Tokenizer {
buffer.push(next_char);
}
- Ok(Token::new(TokenType::String(buffer), line, column))
+ Ok(Token::new(
+ TokenType::String(buffer),
+ line,
+ column,
+ Some(std::mem::take(&mut self.string_buffer)),
+ ))
}
/// Tokenizes a keyword or an identifier. Also handles boolean literals
@@ -378,6 +434,7 @@ impl Tokenizer {
TokenType::Keyword(Keyword::$keyword),
self.line,
self.column,
+ Some(std::mem::take(&mut self.string_buffer)),
));
}};
}
@@ -420,13 +477,19 @@ impl Tokenizer {
// boolean literals
"true" if next_ws!() => {
- return Ok(Token::new(TokenType::Boolean(true), self.line, self.column));
+ return Ok(Token::new(
+ TokenType::Boolean(true),
+ self.line,
+ self.column,
+ Some(std::mem::take(&mut self.string_buffer)),
+ ));
}
"false" if next_ws!() => {
return Ok(Token::new(
TokenType::Boolean(false),
self.line,
self.column,
+ Some(std::mem::take(&mut self.string_buffer)),
));
}
// if the next character is whitespace or not alphanumeric, then we have an identifier
@@ -436,6 +499,7 @@ impl Tokenizer {
TokenType::Identifier(val.to_string()),
line,
column,
+ Some(std::mem::take(&mut self.string_buffer)),
));
}
_ => {}
@@ -443,18 +507,35 @@ impl Tokenizer {
looped_char = self.next_char()?;
}
- Err(Error::UnknownKeywordOrIdentifierError(buffer, line, column))
+ Err(Error::UnknownKeywordOrIdentifierError(
+ buffer,
+ line,
+ column,
+ std::mem::take(&mut self.string_buffer),
+ ))
}
}
-pub struct TokenizerBuffer {
- tokenizer: Tokenizer,
+impl<'a> Iterator for Tokenizer<'a> {
+ type Item = Result;
+
+ fn next(&mut self) -> Option {
+ match self.next_token() {
+ Ok(Some(tok)) => Some(Ok(tok)),
+ Ok(None) => None,
+ Err(e) => Some(Err(e)),
+ }
+ }
+}
+
+pub struct TokenizerBuffer<'a> {
+ tokenizer: Tokenizer<'a>,
buffer: VecDeque,
history: VecDeque,
}
-impl TokenizerBuffer {
- pub fn new(tokenizer: Tokenizer) -> Self {
+impl<'a> TokenizerBuffer<'a> {
+ pub fn new(tokenizer: Tokenizer<'a>) -> Self {
Self {
tokenizer,
buffer: VecDeque::new(),
diff --git a/libs/tokenizer/src/token.rs b/rust_compiler/libs/tokenizer/src/token.rs
similarity index 77%
rename from libs/tokenizer/src/token.rs
rename to rust_compiler/libs/tokenizer/src/token.rs
index bea72dc..c5bed81 100644
--- a/libs/tokenizer/src/token.rs
+++ b/rust_compiler/libs/tokenizer/src/token.rs
@@ -8,14 +8,21 @@ pub struct Token {
pub line: usize,
/// The column where the token was found
pub column: usize,
+ pub original_string: Option,
}
impl Token {
- pub fn new(token_type: TokenType, line: usize, column: usize) -> Self {
+ pub fn new(
+ token_type: TokenType,
+ line: usize,
+ column: usize,
+ original: Option,
+ ) -> Self {
Self {
token_type,
line,
column,
+ original_string: original,
}
}
}
@@ -32,7 +39,7 @@ impl std::fmt::Display for Temperature {
match self {
Temperature::Celsius(n) => write!(f, "{}°C", n),
Temperature::Fahrenheit(n) => write!(f, "{}°F", n),
- Temperature::Kelvin(n) => write!(f, "{}K", n),
+ Temperature::Kelvin(n) => write!(f, "{}°K", n),
}
}
}
@@ -88,7 +95,7 @@ impl std::fmt::Display for TokenType {
TokenType::Boolean(b) => write!(f, "{}", b),
TokenType::Keyword(k) => write!(f, "{:?}", k),
TokenType::Identifier(i) => write!(f, "{}", i),
- TokenType::Symbol(s) => write!(f, "{:?}", s),
+ TokenType::Symbol(s) => write!(f, "{}", s),
TokenType::EOF => write!(f, "EOF"),
}
}
@@ -208,6 +215,40 @@ impl Symbol {
}
}
+impl std::fmt::Display for Symbol {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Percent => write!(f, "%"),
+ Self::LParen => write!(f, "("),
+ Self::RParen => write!(f, ")"),
+ Self::LBrace => write!(f, "{{"),
+ Self::RBrace => write!(f, "}}"),
+ Self::LBracket => write!(f, "["),
+ Self::RBracket => write!(f, "]"),
+ Self::Semicolon => write!(f, ";"),
+ Self::Colon => write!(f, ":"),
+ Self::Plus => write!(f, "+"),
+ Self::Minus => write!(f, "-"),
+ Self::Comma => write!(f, ","),
+ Self::Asterisk => write!(f, "*"),
+ Self::Slash => write!(f, "/"),
+ Self::LessThan => write!(f, "<"),
+ Self::LessThanOrEqual => write!(f, "<="),
+ Self::GreaterThan => write!(f, ">"),
+ Self::GreaterThanOrEqual => write!(f, ">="),
+ Self::Assign => write!(f, "="),
+ Self::Equal => write!(f, "=="),
+ Self::LogicalAnd => write!(f, "&&"),
+ Self::LogicalOr => write!(f, "||"),
+ Self::LogicalNot => write!(f, "!"),
+ Self::NotEqual => write!(f, "!="),
+ Self::Dot => write!(f, "."),
+ Self::Caret => write!(f, "^"),
+ Self::Exp => write!(f, "**"),
+ }
+ }
+}
+
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
pub enum Keyword {
/// Represents the `continue` keyword
diff --git a/libs/tokenizer/tests/file.stlg b/rust_compiler/libs/tokenizer/tests/file.stlg
similarity index 100%
rename from libs/tokenizer/tests/file.stlg
rename to rust_compiler/libs/tokenizer/tests/file.stlg
diff --git a/rust-toolchain.toml b/rust_compiler/rust-toolchain.toml
similarity index 100%
rename from rust-toolchain.toml
rename to rust_compiler/rust-toolchain.toml
diff --git a/rust_compiler/src/bin/generate_headers.rs b/rust_compiler/src/bin/generate_headers.rs
new file mode 100644
index 0000000..54cdb6a
--- /dev/null
+++ b/rust_compiler/src/bin/generate_headers.rs
@@ -0,0 +1,3 @@
+fn main() -> std::io::Result<()> {
+ ::slang::generate_headers()
+}
diff --git a/rust_compiler/src/lib.rs b/rust_compiler/src/lib.rs
new file mode 100644
index 0000000..056f3fc
--- /dev/null
+++ b/rust_compiler/src/lib.rs
@@ -0,0 +1,110 @@
+use compiler::Compiler;
+use parser::Parser;
+use safer_ffi::prelude::*;
+use std::io::BufWriter;
+use tokenizer::{token::TokenType, Error as TokenizerError, Tokenizer};
+
+#[derive_ReprC]
+#[repr(C)]
+pub struct FfiToken {
+ pub text: safer_ffi::String,
+ pub tooltip: safer_ffi::String,
+ pub error: safer_ffi::String,
+ pub status: safer_ffi::String,
+ pub column: i32,
+}
+
+/// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
+/// we want to avoid GC. So we pass it to Rust to handle all the memory allocations.
+/// This should result in the ability to compile many times without triggering frame drops
+/// from the GC from a `GetBytes()` call on a string in C#.
+#[ffi_export]
+pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::String {
+ let mut writer = BufWriter::new(Vec::new());
+
+ let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
+ let parser = Parser::new(tokenizer);
+ let compiler = Compiler::new(parser, &mut writer, None);
+
+ if compiler.compile().is_err() {
+ return safer_ffi::String::EMPTY;
+ }
+
+ let Ok(compiled_vec) = writer.into_inner() else {
+ return safer_ffi::String::EMPTY;
+ };
+
+ // Safety: I know the compiler only outputs valid utf8
+ safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) })
+}
+/// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
+/// we want to avoid GC. So we pass it to Rust to handle all the memory allocations.
+/// This should result in the ability to tokenize many times without triggering frame drops
+/// from the GC from a `GetBytes()` call on a string in C#.
+#[ffi_export]
+pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec {
+ let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
+
+ let mut tokens = Vec::::new();
+
+ for token in tokenizer {
+ match token {
+ Err(TokenizerError::NumberParseError(_, _, col, ref original))
+ | Err(TokenizerError::UnknownSymbolError(_, _, col, ref original))
+ | Err(TokenizerError::DecimalParseError(_, _, col, ref original))
+ | Err(TokenizerError::UnknownKeywordOrIdentifierError(_, _, col, ref original)) => {
+ tokens.push(FfiToken {
+ column: col as i32,
+ text: original.to_string().into(),
+ tooltip: "".into(),
+ // Safety: it's okay to unwrap the err here because we are matching on the `Err` variant
+ error: token.unwrap_err().to_string().into(),
+ status: "".into(),
+ });
+ }
+ Err(_) => return safer_ffi::Vec::EMPTY,
+ Ok(token) if !matches!(token.token_type, TokenType::EOF) => tokens.push(FfiToken {
+ text: token
+ .original_string
+ .unwrap_or(token.token_type.to_string())
+ .into(),
+ tooltip: "".into(),
+ error: "".into(),
+ status: "".into(),
+ column: token.column as i32,
+ }),
+ _ => {}
+ }
+ }
+
+ tokens.into()
+}
+
+#[ffi_export]
+pub fn free_ffi_token_vec(v: safer_ffi::Vec) {
+ drop(v)
+}
+
+#[ffi_export]
+pub fn free_string(s: safer_ffi::String) {
+ drop(s)
+}
+
+#[cfg(feature = "headers")]
+pub fn generate_headers() -> std::io::Result<()> {
+ let file_name = "../csharp_mod/FfiGlue.cs";
+ ::safer_ffi::headers::builder()
+ .with_language(safer_ffi::headers::Language::CSharp)
+ .to_file(file_name)?
+ .generate()?;
+
+ let content = std::fs::read_to_string(file_name)?;
+
+ let content = content.replace(
+ "private const string RustLib = \"slang\";",
+ "public const string RustLib = \"slang_compiler.dll\";",
+ );
+
+ std::fs::write(file_name, content)?;
+ Ok(())
+}
diff --git a/src/main.rs b/rust_compiler/src/main.rs
similarity index 100%
rename from src/main.rs
rename to rust_compiler/src/main.rs
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index e7c09ad..0000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use compiler::Compiler;
-use parser::Parser;
-use std::{
- ffi::{CStr, CString},
- io::BufWriter,
-};
-use tokenizer::Tokenizer;
-
-/// Takes a raw pointer to a string and compiles the `slang` code into valid IC10
-/// # Safety
-/// This must be called with a valid string pointer from C# (or wherever is calling this function)
-#[no_mangle]
-pub unsafe extern "C" fn compile_from_string(
- input_ptr: *const std::os::raw::c_char,
-) -> *mut std::os::raw::c_char {
- if input_ptr.is_null() {
- return std::ptr::null_mut();
- }
-
- let c_str = unsafe { CStr::from_ptr(input_ptr) };
-
- let Ok(input_str) = c_str.to_str() else {
- return std::ptr::null_mut();
- };
-
- let mut writer = BufWriter::new(Vec::new());
- let tokenizer = Tokenizer::from(input_str);
- let parser = Parser::new(tokenizer);
-
- let compiler = Compiler::new(parser, &mut writer, None);
-
- let Ok(()) = compiler.compile() else {
- return std::ptr::null_mut();
- };
-
- let Ok(buffer) = writer.into_inner() else {
- return std::ptr::null_mut();
- };
-
- let c_string = CString::from_vec_unchecked(buffer);
-
- c_string.into_raw()
-}
-
-/// Takes ownership of the string pointer and drops it, freeing the memory
-/// # Safety
-/// Must be called with a valid string pointer
-#[no_mangle]
-pub unsafe extern "C" fn free_slang_string(input_ptr: *mut std::os::raw::c_char) {
- if input_ptr.is_null() {
- return;
- }
-
- unsafe {
- // Takes ownership of the input string, and then drops it immediately
- let _ = CString::from_raw(input_ptr);
- }
-}