37 Commits
0.1.1 ... 0.2.4

Author SHA1 Message Date
d3974ad590 update changelog and version bump 2025-12-11 23:33:17 -07:00
098d689750 wip -- source mapping overrides in-game line error number 2025-12-11 17:14:43 -07:00
3edf0324c7 populate GlobalCode.sourceMaps 2025-12-11 14:06:54 -07:00
92f0d22805 hook up compilationResult to FFI boundry 2025-12-11 13:32:46 -07:00
811f4f4959 Keep track of source map throughout the compilation process 2025-12-11 13:03:12 -07:00
c041518c9b Merge pull request #25 from dbidwell94/stabalize-functions
0.2.3
2025-12-11 02:27:39 -07:00
2b26d0d278 Update changelog 2025-12-11 02:26:20 -07:00
236b50c813 Allow syscalls in infix operations 2025-12-11 02:24:01 -07:00
342b1ab107 Fix function invocation stack underflow 2025-12-11 01:03:43 -07:00
0732f68bcf Merge pull request #24 from dbidwell94/documentation
QOL
2025-12-10 18:01:21 -07:00
0ac010ef8f Fixed documentation rendering and added ternary expressions 2025-12-10 18:00:20 -07:00
c2208fbb15 Fixed some formatting issues with header markdowns for Stationpedia 2025-12-10 13:39:58 -07:00
295f062797 Merge pull request #23 from dbidwell94/slot-logic
Slot logic
2025-12-10 00:11:45 -07:00
9c260ef2d5 Fixed bug where infix wouldn't rewind when encountering a comma, causing the rest of a syscall parse in an assignment expression to fail 2025-12-10 00:09:16 -07:00
0fde11a2bf Added support for syscalls with assignment expressions 2025-12-09 23:57:19 -07:00
b21d6cc73e Found bug, unable to do an assignment expression with a syscall 2025-12-09 23:45:10 -07:00
f19801d4e6 Merge pull request #22 from dbidwell94/logos
Tech Debt Cleanup
2025-12-09 17:41:02 -07:00
46500a456a Add support for colorized comments 2025-12-09 17:38:35 -07:00
f60c9b32a8 minor version bump 2025-12-09 16:50:23 -07:00
0cddb3e8c8 The Cows are all working. Moo. 2025-12-09 16:31:24 -07:00
c3986ab4d9 VariableManager lifetime errors 2025-12-09 16:05:40 -07:00
f54214acb9 Most of all the errors are gone 2025-12-09 13:59:54 -07:00
d9a7a31306 Lifetimes are declared, now I gotta fix the lifetime issues 2025-12-09 13:51:54 -07:00
d40b759442 TEST -- use Cow instead of String for tokens 2025-12-09 13:17:35 -07:00
080b5320f7 Removed off-by-one calculations in the C# mod 2025-12-09 12:24:29 -07:00
a50a45f0b4 More cleanup 2025-12-09 12:12:28 -07:00
c531f673a5 Remove quickerror in favor of thiserror 2025-12-09 11:32:14 -07:00
23c2ba4134 Fix visual bugs with new span logic 2025-12-09 02:21:56 -07:00
7b7c1f7d29 Logos plugged into Parser 2025-12-09 02:15:43 -07:00
72cf9ea042 wip 2025-12-09 01:43:12 -07:00
fac36c756b Lexer impl done 2025-12-08 23:19:23 -07:00
115a57128c Before error type refactor 2025-12-08 22:50:20 -07:00
6afeec6da2 First pass getting a logos tokenizer up and running 2025-12-08 21:06:42 -07:00
b6123219f8 Merge pull request #21 from dbidwell94/cysharp-removal
Updates for Stationeers Beta branch
2025-12-08 15:26:22 -07:00
f38c15da9c Remove references to Unitask and Cysharp 2025-12-07 23:00:45 -07:00
fdd1a311d5 Merge pull request #20 from dbidwell94/release
Release
2025-12-07 21:46:05 -07:00
fb7ca0d4fd Update About.xml again 2025-12-07 21:45:07 -07:00
37 changed files with 2761 additions and 2093 deletions

40
Changelog.md Normal file
View File

@@ -0,0 +1,40 @@
# Changelog
[0.2.4]
- Groundwork laid to collect and track source maps
- IC Housing will now display the `Slang` source error line (if available)
instead of the `IC10` source error line
[0.2.3]
- Fixed stack underflow with function invocations
- They are still "heavy", but they should work as expected now
- Fixed issue where syscall functions were not allowed as infix operators
[0.2.2]
- Fixed some formatting issues when converting Markdown to Text Mesh Pro for
Stationpedia
- Added support for ternary expressions
- `let i = someValue ? 4 : 5;`
- `i = someValue ? 4 : 5;`
- This greedily evaluates both sides, so side effects like calling functions
is not recommended i.e.
- `i = someValue : doSomething() : doSomethingElse();`
- Both sides will be evaluated before calling the `select` instruction
[0.2.1]
- Added support for `loadSlot` and `setSlot`
- Fixed bug where syscalls like `max(1, 2)` were not allowed in assignment expressions
[0.2.0]
- Completely re-wrote the tokenizer to use `logos`
- Changed AST and Token data structures to use `Cow` instead of `String`
- Updated error reporting to use `thiserror` instead of `quickerror`
[0.1.2]
- Removed references to `Unitask`

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<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>StationeersSlang</Name> <Name>Slang</Name>
<Author>JoeDiertay</Author> <Author>JoeDiertay</Author>
<Version>0.1.1</Version> <Version>0.2.4</Version>
<Description> <Description>
[h1]Slang: High-Level Programming for Stationeers[/h1] [h1]Slang: High-Level Programming for Stationeers[/h1]
Stop writing assembly. Start writing code. Stop writing assembly. Start writing code.
Slang (Stationeers Language) brings modern programming to Stationeers. It allows you to write scripts using a familiar C-style syntax (variables, functions, if/else, loops) directly in the in-game editor. When you hit confirm, Slang compiles your code into optimized IC10 MIPS assembly instantly. Slang (Stationeers Language) brings modern programming to Stationeers. It allows you to write scripts using a familiar C-style syntax (variables, functions, if/else, loops) directly in the in-game editor. When you hit confirm, Slang compiles your code into IC10 instantly.
[b]NOTE: This project is in BETA. Expect updates and changes![/b] [b]NOTE: This project is in BETA. Expect updates and changes![/b]
@@ -91,7 +91,7 @@ A: Yes! Slang does not modify any existing IC10 code, it is only a compiler. As
<OrderBefore WorkshopHandle="3592775931" /> <OrderBefore WorkshopHandle="3592775931" />
<InGameDescription><![CDATA[ <InGameDescription><![CDATA[
<size=30><color=#ffff00>Slang - High Level Language Compiler</color></size> <size=30><color=#ffff00>Slang - High Level Language Compiler</color></size>
A modern programming experience for Stationeers. Write C-style code that compiles to MIPS assembly instantly. A modern programming experience for Stationeers. Write C-style code that compiles to IC10 instantly.
<color=#ffa500><b>Features</b></color> <color=#ffa500><b>Features</b></color>
- <b>In-Game Compilation:</b> Write high-level logic directly in the chip editor. - <b>In-Game Compilation:</b> Write high-level logic directly in the chip editor.

View File

@@ -55,7 +55,7 @@ public static unsafe class SlangExtensions
var color = GetColorForKind(token.token_kind); var color = GetColorForKind(token.token_kind);
int colIndex = token.column - 1; int colIndex = token.column;
if (colIndex < 0) if (colIndex < 0)
colIndex = 0; colIndex = 0;
@@ -100,10 +100,10 @@ public static unsafe class SlangExtensions
Severity = item.severity, Severity = item.severity,
Range = new Slang.Range Range = new Slang.Range
{ {
EndCol = Math.Max(item.range.end_col - 2, 0), EndCol = Math.Max(item.range.end_col, 0),
EndLine = item.range.end_line - 1, EndLine = item.range.end_line,
StartCol = Math.Max(item.range.start_col - 2, 0), StartCol = Math.Max(item.range.start_col, 0),
StartLine = item.range.end_line - 1, StartLine = item.range.start_line,
}, },
} }
); );
@@ -113,6 +113,34 @@ public static unsafe class SlangExtensions
return toReturn; return toReturn;
} }
public static unsafe List<SourceMapEntry> ToList(this Vec_FfiSourceMapEntry_t vec)
{
var toReturn = new List<SourceMapEntry>((int)vec.len);
var currentPtr = vec.ptr;
for (int i = 0; i < (int)vec.len; i++)
{
var item = currentPtr[i];
toReturn.Add(
new SourceMapEntry
{
Ic10Line = item.line_number,
SlangSource = new Range
{
EndCol = item.span.end_col,
EndLine = item.span.end_line,
StartCol = item.span.start_col,
StartLine = item.span.start_line,
},
}
);
}
return toReturn;
}
private static uint GetColorForKind(uint kind) private static uint GetColorForKind(uint kind)
{ {
switch (kind) switch (kind)
@@ -134,6 +162,9 @@ public static unsafe class SlangExtensions
case 7: // (punctuation) case 7: // (punctuation)
return SlangFormatter.ColorDefault; return SlangFormatter.ColorDefault;
case 8: // Comments
return SlangFormatter.ColorComment;
case 10: // (syscalls) case 10: // (syscalls)
return SlangFormatter.ColorFunction; return SlangFormatter.ColorFunction;

View File

@@ -71,18 +71,6 @@ public unsafe struct Vec_uint8_t {
public UIntPtr cap; public UIntPtr cap;
} }
public unsafe partial class Ffi {
/// <summary>
/// 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 <c>GetBytes()</c> call on a string in C#.
/// </summary>
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
Vec_uint8_t compile_from_string (
slice_ref_uint16_t input);
}
[StructLayout(LayoutKind.Sequential, Size = 16)] [StructLayout(LayoutKind.Sequential, Size = 16)]
public unsafe struct FfiRange_t { public unsafe struct FfiRange_t {
public UInt32 start_col; public UInt32 start_col;
@@ -94,6 +82,44 @@ public unsafe struct FfiRange_t {
public UInt32 end_line; public UInt32 end_line;
} }
[StructLayout(LayoutKind.Sequential, Size = 20)]
public unsafe struct FfiSourceMapEntry_t {
public UInt32 line_number;
public FfiRange_t span;
}
/// <summary>
/// Same as [<c>Vec<T></c>][<c>rust::Vec</c>], but with guaranteed <c>#[repr(C)]</c> layout
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 24)]
public unsafe struct Vec_FfiSourceMapEntry_t {
public FfiSourceMapEntry_t * ptr;
public UIntPtr len;
public UIntPtr cap;
}
[StructLayout(LayoutKind.Sequential, Size = 48)]
public unsafe struct FfiCompilationResult_t {
public Vec_uint8_t output_code;
public Vec_FfiSourceMapEntry_t source_map;
}
public unsafe partial class Ffi {
/// <summary>
/// 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 <c>GetBytes()</c> call on a string in C#.
/// </summary>
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
FfiCompilationResult_t compile_from_string (
slice_ref_uint16_t input);
}
[StructLayout(LayoutKind.Sequential, Size = 48)] [StructLayout(LayoutKind.Sequential, Size = 48)]
public unsafe struct FfiDiagnostic_t { public unsafe struct FfiDiagnostic_t {
public Vec_uint8_t message; public Vec_uint8_t message;
@@ -146,6 +172,12 @@ public unsafe partial class Ffi {
Vec_FfiDocumentedItem_t v); Vec_FfiDocumentedItem_t v);
} }
public unsafe partial class Ffi {
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
void free_ffi_compilation_result (
FfiCompilationResult_t input);
}
public unsafe partial class Ffi { public unsafe partial class Ffi {
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
void free_ffi_diagnostic_vec ( void free_ffi_diagnostic_vec (

View File

@@ -4,7 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using System.Threading.Tasks;
using StationeersIC10Editor; using StationeersIC10Editor;
public class SlangFormatter : ICodeFormatter public class SlangFormatter : ICodeFormatter
@@ -98,29 +98,32 @@ public class SlangFormatter : ICodeFormatter
inputSrc = this.RawText; inputSrc = this.RawText;
} }
HandleLsp(inputSrc, token).Forget(); _ = HandleLsp(inputSrc, token);
} }
private async UniTaskVoid HandleLsp(string inputSrc, CancellationToken cancellationToken) private async Task HandleLsp(string inputSrc, CancellationToken cancellationToken)
{ {
try try
{ {
await UniTask.SwitchToThreadPool(); if (cancellationToken.IsCancellationRequested)
return;
await Task.Delay(200, cancellationToken: cancellationToken);
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
return; return;
await System.Threading.Tasks.Task.Delay(200, cancellationToken: cancellationToken); // Running this potentially CPU intensive work on a background thread.
var dict = await Task.Run(
if (cancellationToken.IsCancellationRequested) () =>
return; {
return Marshal
var dict = Marshal
.DiagnoseSource(inputSrc) .DiagnoseSource(inputSrc)
.GroupBy(d => d.Range.StartLine) .GroupBy(d => d.Range.StartLine)
.ToDictionary(g => g.Key); .ToDictionary(g => g.Key);
},
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken); cancellationToken
);
ApplyDiagnostics(dict); ApplyDiagnostics(dict);
} }
@@ -136,7 +139,6 @@ public class SlangFormatter : ICodeFormatter
{ {
HashSet<uint> linesToRefresh; HashSet<uint> linesToRefresh;
// CRITICAL FIX FOR LINE SHIFTS:
// If the line count has changed (lines added/deleted), indices have shifted. // If the line count has changed (lines added/deleted), indices have shifted.
// We must refresh ALL lines to ensure any line that shifted into a new position // We must refresh ALL lines to ensure any line that shifted into a new position
// gets scrubbed of its old visual state. // gets scrubbed of its old visual state.

View File

@@ -15,11 +15,59 @@ public static class GlobalCode
// so that save file data is smaller // so that save file data is smaller
private static Dictionary<Guid, string> codeDict = new(); 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
private static Dictionary<Guid, Dictionary<uint, List<Range>>> sourceMaps = new();
public static void ClearCache() public static void ClearCache()
{ {
codeDict.Clear(); codeDict.Clear();
} }
public static void SetSourceMap(Guid reference, List<SourceMapEntry> sourceMapEntries)
{
var builtDictionary = new Dictionary<uint, List<Range>>();
foreach (var entry in sourceMapEntries)
{
if (!builtDictionary.ContainsKey(entry.Ic10Line))
{
builtDictionary[entry.Ic10Line] = new();
}
builtDictionary[entry.Ic10Line].Add(entry.SlangSource);
}
sourceMaps[reference] = builtDictionary;
}
public static bool GetSlangErrorLineFromICError(
Guid reference,
uint icErrorLine,
out uint slangSrc,
out Range slangSpan
)
{
slangSrc = icErrorLine;
slangSpan = new Range { };
if (!sourceMaps.ContainsKey(reference))
{
return false;
}
var foundRange = sourceMaps[reference][icErrorLine];
if (foundRange is null)
{
return false;
}
slangSrc = foundRange[0].StartLine;
slangSpan = foundRange[0];
return true;
}
public static string GetSource(Guid reference) public static string GetSource(Guid reference)
{ {
if (!codeDict.ContainsKey(reference)) if (!codeDict.ContainsKey(reference))

View File

@@ -10,10 +10,23 @@ using StationeersIC10Editor;
public struct Range public struct Range
{ {
public uint StartCol; public uint StartCol = 0;
public uint EndCol; public uint EndCol = 0;
public uint StartLine; public uint StartLine = 0;
public uint EndLine; public uint EndLine = 0;
public Range(uint startLine, uint startCol, uint endLine, uint endCol)
{
StartLine = startLine;
StartCol = startCol;
EndLine = endLine;
EndCol = endCol;
}
public override string ToString()
{
return $"L{StartLine}C{StartCol} - L{EndLine}C{EndCol}";
}
} }
public struct Diagnostic public struct Diagnostic
@@ -23,6 +36,17 @@ public struct Diagnostic
public Range Range; public Range Range;
} }
public struct SourceMapEntry
{
public Range SlangSource;
public uint Ic10Line;
public override string ToString()
{
return $"IC10: {Ic10Line} Slang: `{SlangSource}`";
}
}
public static class Marshal public static class Marshal
{ {
private static IntPtr _libraryHandle = IntPtr.Zero; private static IntPtr _libraryHandle = IntPtr.Zero;
@@ -78,11 +102,16 @@ public static class Marshal
} }
} }
public static unsafe bool CompileFromString(string inputString, out string compiledString) public static unsafe bool CompileFromString(
string inputString,
out string compiledString,
out List<SourceMapEntry> sourceMapEntries
)
{ {
if (String.IsNullOrEmpty(inputString) || !EnsureLibLoaded()) if (String.IsNullOrEmpty(inputString) || !EnsureLibLoaded())
{ {
compiledString = String.Empty; compiledString = String.Empty;
sourceMapEntries = new();
return false; return false;
} }
@@ -95,19 +124,16 @@ public static class Marshal
}; };
var result = Ffi.compile_from_string(input); var result = Ffi.compile_from_string(input);
try try
{ {
if ((ulong)result.len < 1) sourceMapEntries = result.source_map.ToList();
{ compiledString = result.output_code.AsString();
compiledString = String.Empty;
return false;
}
compiledString = result.AsString();
return true; return true;
} }
finally finally
{ {
result.Drop(); Ffi.free_ffi_compilation_result(result);
} }
} }
} }

View File

@@ -1,17 +1,43 @@
namespace Slang; namespace Slang;
using System; using System;
using System.Runtime.CompilerServices;
using Assets.Scripts.Objects; using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Electrical; using Assets.Scripts.Objects.Electrical;
using Assets.Scripts.Objects.Motherboards; using Assets.Scripts.Objects.Motherboards;
using Assets.Scripts.UI; using Assets.Scripts.UI;
using HarmonyLib; 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] [HarmonyPatch]
public static class SlangPatches public static class SlangPatches
{ {
private static ProgrammableChipMotherboard? _currentlyEditingMotherboard; private static ProgrammableChipMotherboard? _currentlyEditingMotherboard;
private static AsciiString? _motherboardCachedCode; private static AsciiString? _motherboardCachedCode;
private static Guid? _currentlyEditingGuid;
private static ConditionalWeakTable<ProgrammableChip, LineErrorData> _errorReferenceTable =
new();
[HarmonyPatch( [HarmonyPatch(
typeof(ProgrammableChipMotherboard), typeof(ProgrammableChipMotherboard),
@@ -25,17 +51,20 @@ public static class SlangPatches
// guard to ensure we have valid IC10 before continuing // guard to ensure we have valid IC10 before continuing
if ( if (
!SlangPlugin.IsSlangSource(ref result) !SlangPlugin.IsSlangSource(ref result)
|| !Marshal.CompileFromString(result, out string compiled) || !Marshal.CompileFromString(result, out var compiled, out var sourceMap)
|| string.IsNullOrEmpty(compiled) || string.IsNullOrEmpty(compiled)
) )
{ {
return; return;
} }
var thisRef = Guid.NewGuid(); var thisRef = _currentlyEditingGuid ?? Guid.NewGuid();
// Ensure we cache this compiled code for later retreival. // Ensure we cache this compiled code for later retreival.
GlobalCode.SetSource(thisRef, result); GlobalCode.SetSource(thisRef, result);
GlobalCode.SetSourceMap(thisRef, sourceMap);
_currentlyEditingGuid = null;
// Append REF to the bottom // Append REF to the bottom
compiled += $"\n{GlobalCode.SLANG_REF}{thisRef}"; compiled += $"\n{GlobalCode.SLANG_REF}{thisRef}";
@@ -77,6 +106,7 @@ public static class SlangPatches
return; return;
} }
_currentlyEditingGuid = sourceRef;
var slangSource = GlobalCode.GetSource(sourceRef); var slangSource = GlobalCode.GetSource(sourceRef);
if (string.IsNullOrEmpty(slangSource)) if (string.IsNullOrEmpty(slangSource))
@@ -136,6 +166,100 @@ public static class SlangPatches
chipData.SourceCode = 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( [HarmonyPatch(
typeof(ProgrammableChipMotherboard), typeof(ProgrammableChipMotherboard),
nameof(ProgrammableChipMotherboard.SerializeSave) nameof(ProgrammableChipMotherboard.SerializeSave)
@@ -223,6 +347,7 @@ public static class SlangPatches
_currentlyEditingMotherboard = null; _currentlyEditingMotherboard = null;
_motherboardCachedCode = null; _motherboardCachedCode = null;
_currentlyEditingGuid = null;
} }
[HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))] [HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))]

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.1.1"; public const string PluginVersion = "0.2.4";
public static Mod MOD = new Mod(PluginName, PluginVersion); public static Mod MOD = new Mod(PluginName, PluginVersion);

View File

@@ -26,12 +26,18 @@ public static class TextMeshProFormatter
RegexOptions.Singleline RegexOptions.Singleline
); );
// 3. Handle Headers (## Header)
// Convert ## Header to large bold text
text = Regex.Replace( text = Regex.Replace(
text, text,
@"^##(\s+)?(.+)$", @"^\s*##\s+(.+)$",
"<size=120%><b>$1</b></size>", "<size=110%><color=#ffffff><b>$1</b></color></size>",
RegexOptions.Multiline
);
// 3. Handle # Headers SECOND (General)
text = Regex.Replace(
text,
@"^\s*#\s+(.+)$",
"<size=120%><color=#ffffff><b>$1</b></color></size>",
RegexOptions.Multiline RegexOptions.Multiline
); );

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.1.1</Version> <Version>0.2.4</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
@@ -39,10 +39,6 @@
<HintPath>./ref/Assembly-CSharp.dll</HintPath> <HintPath>./ref/Assembly-CSharp.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UniTask">
<HintPath>./ref/UniTask.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="IC10Editor.dll"> <Reference Include="IC10Editor.dll">
<HintPath>./ref/IC10Editor.dll</HintPath> <HintPath>./ref/IC10Editor.dll</HintPath>

110
rust_compiler/Cargo.lock generated
View File

@@ -28,6 +28,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.21" version = "0.6.21"
@@ -114,6 +123,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -257,8 +272,8 @@ dependencies = [
"lsp-types", "lsp-types",
"parser", "parser",
"pretty_assertions", "pretty_assertions",
"quick-error",
"rust_decimal", "rust_decimal",
"thiserror",
"tokenizer", "tokenizer",
] ]
@@ -327,6 +342,12 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "funty" name = "funty"
version = "2.0.0" version = "2.0.0"
@@ -434,6 +455,40 @@ version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "logos"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-codegen"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-automata",
"regex-syntax",
"rustc_version",
"syn 2.0.111",
]
[[package]]
name = "logos-derive"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc"
dependencies = [
"logos-codegen",
]
[[package]] [[package]]
name = "lsp-types" name = "lsp-types"
version = "0.97.0" version = "0.97.0"
@@ -516,7 +571,8 @@ dependencies = [
"helpers", "helpers",
"lsp-types", "lsp-types",
"pretty_assertions", "pretty_assertions",
"quick-error", "safer-ffi",
"thiserror",
"tokenizer", "tokenizer",
] ]
@@ -593,12 +649,6 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.42" version = "1.0.42"
@@ -644,6 +694,23 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]] [[package]]
name = "rend" name = "rend"
version = "0.4.2" version = "0.4.2"
@@ -843,7 +910,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "slang" name = "slang"
version = "0.1.1" version = "0.2.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -851,9 +918,9 @@ dependencies = [
"helpers", "helpers",
"lsp-types", "lsp-types",
"parser", "parser",
"quick-error",
"rust_decimal", "rust_decimal",
"safer-ffi", "safer-ffi",
"thiserror",
"tokenizer", "tokenizer",
] ]
@@ -926,6 +993,26 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.10.0" version = "1.10.0"
@@ -947,9 +1034,10 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"helpers", "helpers",
"logos",
"lsp-types", "lsp-types",
"quick-error",
"rust_decimal", "rust_decimal",
"thiserror",
] ]
[[package]] [[package]]

View File

@@ -1,13 +1,13 @@
[package] [package]
name = "slang" name = "slang"
version = "0.1.1" version = "0.2.4"
edition = "2021" edition = "2021"
[workspace] [workspace]
members = ["libs/*"] members = ["libs/*"]
[workspace.dependencies] [workspace.dependencies]
quick-error = "2" thiserror = "2"
rust_decimal = "1" rust_decimal = "1"
safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust
lsp-types = { version = "0.97" } # Allows for LSP style reporting to the frontend lsp-types = { version = "0.97" } # Allows for LSP style reporting to the frontend
@@ -36,13 +36,13 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
clap = { version = "^4.5", features = ["derive"] } clap = { version = "^4.5", features = ["derive"] }
lsp-types = { workspace = true } lsp-types = { workspace = true }
quick-error = { workspace = true } thiserror = { workspace = true }
rust_decimal = { workspace = true } rust_decimal = { workspace = true }
tokenizer = { path = "libs/tokenizer" } tokenizer = { path = "libs/tokenizer" }
parser = { path = "libs/parser" } parser = { path = "libs/parser" }
compiler = { path = "libs/compiler" } compiler = { path = "libs/compiler" }
helpers = { path = "libs/helpers" } helpers = { path = "libs/helpers" }
safer-ffi = { workspace = true } safer-ffi = { workspace = true }
anyhow = { version = "^1.0", features = ["backtrace"] }
[dev-dependencies] [dev-dependencies]
anyhow = { version = "^1.0", features = ["backtrace"] }

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
quick-error = { workspace = true } thiserror = { workspace = true }
parser = { path = "../parser" } parser = { path = "../parser" }
tokenizer = { path = "../tokenizer" } tokenizer = { path = "../tokenizer" }
helpers = { path = "../helpers" } helpers = { path = "../helpers" }

View File

@@ -3,4 +3,4 @@ mod test;
mod v1; mod v1;
mod variable_manager; mod variable_manager;
pub use v1::{Compiler, CompilerConfig, Error}; pub use v1::{CompilationResult, Compiler, CompilerConfig, Error};

View File

@@ -1,9 +1,10 @@
use crate::compile; use crate::compile;
use anyhow::Result;
use indoc::indoc; use indoc::indoc;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn simple_binary_expression() -> anyhow::Result<()> { fn simple_binary_expression() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -26,7 +27,7 @@ fn simple_binary_expression() -> anyhow::Result<()> {
} }
#[test] #[test]
fn nested_binary_expressions() -> anyhow::Result<()> { fn nested_binary_expressions() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -51,6 +52,8 @@ fn nested_binary_expressions() -> anyhow::Result<()> {
add r1 r10 r9 add r1 r10 r9
mul r2 r1 r8 mul r2 r1 r8
move r15 r2 move r15 r2
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -71,7 +74,7 @@ fn nested_binary_expressions() -> anyhow::Result<()> {
} }
#[test] #[test]
fn stress_test_constant_folding() -> anyhow::Result<()> { fn stress_test_constant_folding() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -94,7 +97,7 @@ fn stress_test_constant_folding() -> anyhow::Result<()> {
} }
#[test] #[test]
fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> { fn test_constant_folding_with_variables_mixed_in() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
r#" r#"
@@ -120,3 +123,55 @@ fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_ternary_expression() -> Result<()> {
let compiled = compile! {
debug
r#"
let i = 1 > 2 ? 15 : 20;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
sgt r1 1 2
select r2 r1 15 20
move r8 r2 #i
"
}
);
Ok(())
}
#[test]
fn test_ternary_expression_assignment() -> Result<()> {
let compiled = compile! {
debug
r#"
let i = 0;
i = 1 > 2 ? 15 : 20;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #i
sgt r1 1 2
select r2 r1 15 20
move r8 r2 #i
"
}
);
Ok(())
}

View File

@@ -17,6 +17,7 @@ fn no_arguments() -> anyhow::Result<()> {
j main j main
doSomething: doSomething:
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -34,14 +35,17 @@ fn no_arguments() -> anyhow::Result<()> {
#[test] #[test]
fn let_var_args() -> anyhow::Result<()> { fn let_var_args() -> anyhow::Result<()> {
// !IMPORTANT this needs to be stabilized as it currently incorrectly calculates sp offset at
// both ends of the cleanup lifecycle
let compiled = compile! { let compiled = compile! {
debug debug
" "
fn doSomething(arg1) {}; fn mul2(arg1) {
return arg1 * 2;
};
loop {
let arg1 = 123; let arg1 = 123;
let i = doSomething(arg1); let i = mul2(arg1);
i = i ** 2;
}
" "
}; };
@@ -50,23 +54,31 @@ fn let_var_args() -> anyhow::Result<()> {
indoc! { indoc! {
" "
j main j main
doSomething: mul2:
pop r8 #arg1 pop r8 #arg1
push ra push ra
mul r1 r8 2
move r15 r1
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
j ra j ra
main: main:
L2:
move r8 123 #arg1 move r8 123 #arg1
push r8 push r8
push r8 push r8
jal doSomething jal mul2
sub r0 sp 1 sub r0 sp 1
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #i move r9 r15 #i
sub sp sp 1 pow r1 r9 2
move r9 r1 #i
j L2
L3:
" "
} }
); );
@@ -97,7 +109,9 @@ fn inline_literal_args() -> anyhow::Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
fn doSomething(arg1, arg2) {}; fn doSomething(arg1, arg2) {
return 5;
};
let thisVariableShouldStayInPlace = 123; let thisVariableShouldStayInPlace = 123;
let returnedValue = doSomething(12, 34); let returnedValue = doSomething(12, 34);
" "
@@ -112,6 +126,9 @@ fn inline_literal_args() -> anyhow::Result<()> {
pop r8 #arg2 pop r8 #arg2
pop r9 #arg1 pop r9 #arg1
push ra push ra
move r15 5 #returnValue
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -126,7 +143,6 @@ fn inline_literal_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #returnedValue move r9 r15 #returnedValue
sub sp sp 1
" "
} }
); );
@@ -154,6 +170,7 @@ fn mixed_args() -> anyhow::Result<()> {
pop r8 #arg2 pop r8 #arg2
pop r9 #arg1 pop r9 #arg1
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -168,7 +185,6 @@ fn mixed_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #returnValue move r9 r15 #returnValue
sub sp sp 1
" "
} }
); );
@@ -198,6 +214,8 @@ fn with_return_statement() -> anyhow::Result<()> {
pop r8 #arg1 pop r8 #arg1
push ra push ra
move r15 456 #returnValue move r15 456 #returnValue
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -233,6 +251,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
doSomething: doSomething:
push ra push ra
move r15 -1 #returnValue move r15 -1 #returnValue
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1

View File

@@ -133,6 +133,8 @@ fn test_boolean_return() -> anyhow::Result<()> {
getTrue: getTrue:
push ra push ra
move r15 1 #returnValue move r15 1 #returnValue
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1

View File

@@ -21,6 +21,7 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
pop r13 #arg4 pop r13 #arg4
pop r14 #arg3 pop r14 #arg3
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 3 sub sp sp 3
@@ -31,6 +32,48 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_early_return() -> anyhow::Result<()> {
let compiled = compile!(debug r#"
// This is a test function declaration with no body
fn doSomething() {
if (1 == 1) {
return;
}
let i = 1 + 2;
return;
};
doSomething();
"#);
assert_eq!(
compiled,
indoc! {
"
j main
doSomething:
push ra
seq r1 1 1
beq r1 0 L2
j L1
L2:
move r8 3 #i
j L1
L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
j ra
main:
jal doSomething
move r1 r15 #__binary_temp_2
"
}
);
Ok(())
}
#[test] #[test]
fn test_function_declaration_with_register_params() -> anyhow::Result<()> { fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#" let compiled = compile!(debug r#"
@@ -47,6 +90,7 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
pop r8 #arg2 pop r8 #arg2
pop r9 #arg1 pop r9 #arg1
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1

View File

@@ -243,6 +243,32 @@ fn test_max() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_max_from_game() -> Result<()> {
let compiled = compile! {
debug
r#"
let item = 0;
item = max(1 + 2, 2);
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #item
max r15 3 2
move r8 r15 #item
"
}
);
Ok(())
}
#[test] #[test]
fn test_min() -> Result<()> { fn test_min() -> Result<()> {
let compiled = compile! { let compiled = compile! {

View File

@@ -22,17 +22,17 @@ macro_rules! compile {
(result $source:expr) => {{ (result $source:expr) => {{
let mut writer = std::io::BufWriter::new(Vec::new()); let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = crate::Compiler::new( let compiler = crate::Compiler::new(
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), parser::Parser::new(tokenizer::Tokenizer::from($source)),
&mut writer, &mut writer,
Some(crate::CompilerConfig { debug: true }), Some(crate::CompilerConfig { debug: true }),
); );
compiler.compile() compiler.compile().errors
}}; }};
(debug $source:expr) => {{ (debug $source:expr) => {{
let mut writer = std::io::BufWriter::new(Vec::new()); let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = crate::Compiler::new( let compiler = crate::Compiler::new(
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), parser::Parser::new(tokenizer::Tokenizer::from($source)),
&mut writer, &mut writer,
Some(crate::CompilerConfig { debug: true }), Some(crate::CompilerConfig { debug: true }),
); );

View File

@@ -157,3 +157,54 @@ fn test_load_from_device() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_load_from_slot() -> anyhow::Result<()> {
let compiled = compile! {
debug
r#"
device airCon = "d0";
let setting = ls(airCon, 0, "Occupied");
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
ls r15 d0 0 Occupied
move r8 r15 #setting
"
}
);
Ok(())
}
#[test]
fn test_set_slot() -> anyhow::Result<()> {
let compiled = compile! {
debug
r#"
device airCon = "d0";
ss(airCon, 0, "Occupied", true);
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
ss d0 0 Occupied 1
"
}
);
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,28 +5,28 @@
use lsp_types::{Diagnostic, DiagnosticSeverity}; use lsp_types::{Diagnostic, DiagnosticSeverity};
use parser::tree_node::{Literal, Span}; use parser::tree_node::{Literal, Span};
use quick_error::quick_error; use std::{
use std::collections::{HashMap, VecDeque}; borrow::Cow,
collections::{HashMap, VecDeque},
};
use thiserror::Error;
const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7]; const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14]; const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14];
quick_error! { #[derive(Error, Debug)]
#[derive(Debug)] pub enum Error<'a> {
pub enum Error { #[error("{0} already exists.")]
DuplicateVariable(var: String, span: Option<Span>) { DuplicateVariable(Cow<'a, str>, Option<Span>),
display("{var} already exists.")
} #[error("{0} does not exist.")]
UnknownVariable(var: String, span: Option<Span>) { UnknownVariable(Cow<'a, str>, Option<Span>),
display("{var} does not exist.")
} #[error("{0}")]
Unknown(reason: String, span: Option<Span>) { Unknown(Cow<'a, str>, Option<Span>),
display("{reason}")
}
}
} }
impl From<Error> for lsp_types::Diagnostic { impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
fn from(value: Error) -> Self { fn from(value: Error) -> Self {
match value { match value {
Error::DuplicateVariable(_, span) Error::DuplicateVariable(_, span)
@@ -52,8 +52,8 @@ pub enum LocationRequest {
Stack, Stack,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum VariableLocation { pub enum VariableLocation<'a> {
/// Represents a temporary register (r1 - r7) /// Represents a temporary register (r1 - r7)
Temporary(u8), Temporary(u8),
/// Represents a persistant register (r8 - r14) /// Represents a persistant register (r8 - r14)
@@ -61,20 +61,20 @@ pub enum VariableLocation {
/// Represents a a stack offset (current stack - offset = variable loc) /// Represents a a stack offset (current stack - offset = variable loc)
Stack(u16), Stack(u16),
/// Represents a constant value and should be directly substituted as such. /// Represents a constant value and should be directly substituted as such.
Constant(Literal), Constant(Literal<'a>),
/// Represents a device pin. This will contain the exact `d0-d5` string /// Represents a device pin. This will contain the exact `d0-d5` string
Device(String), Device(Cow<'a, str>),
} }
pub struct VariableScope<'a> { pub struct VariableScope<'a, 'b> {
temporary_vars: VecDeque<u8>, temporary_vars: VecDeque<u8>,
persistant_vars: VecDeque<u8>, persistant_vars: VecDeque<u8>,
var_lookup_table: HashMap<String, VariableLocation>, var_lookup_table: HashMap<Cow<'a, str>, VariableLocation<'a>>,
stack_offset: u16, stack_offset: u16,
parent: Option<&'a VariableScope<'a>>, parent: Option<&'b VariableScope<'a, 'b>>,
} }
impl<'a> Default for VariableScope<'a> { impl<'a, 'b> Default for VariableScope<'a, 'b> {
fn default() -> Self { fn default() -> Self {
Self { Self {
parent: None, parent: None,
@@ -86,7 +86,7 @@ impl<'a> Default for VariableScope<'a> {
} }
} }
impl<'a> VariableScope<'a> { impl<'a, 'b> VariableScope<'a, 'b> {
#[allow(dead_code)] #[allow(dead_code)]
pub const TEMP_REGISTER_COUNT: u8 = 7; pub const TEMP_REGISTER_COUNT: u8 = 7;
pub const PERSIST_REGISTER_COUNT: u8 = 7; pub const PERSIST_REGISTER_COUNT: u8 = 7;
@@ -94,22 +94,24 @@ impl<'a> VariableScope<'a> {
pub const RETURN_REGISTER: u8 = 15; pub const RETURN_REGISTER: u8 = 15;
pub const TEMP_STACK_REGISTER: u8 = 0; pub const TEMP_STACK_REGISTER: u8 = 0;
pub fn registers(&self) -> impl Iterator<Item = &u8> { pub fn registers(&self) -> Vec<u8> {
self.var_lookup_table let mut used = Vec::new();
.values()
.filter(|val| { for r in TEMP {
matches!( if !self.temporary_vars.contains(&r) {
val, used.push(r);
VariableLocation::Temporary(_) | VariableLocation::Persistant(_) }
)
})
.map(|loc| match loc {
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => reg,
_ => unreachable!(),
})
} }
pub fn scoped(parent: &'a VariableScope<'a>) -> Self { for r in PERSIST {
if !self.persistant_vars.contains(&r) {
used.push(r);
}
}
used
}
pub fn scoped(parent: &'b VariableScope<'a, 'b>) -> Self {
Self { Self {
parent: Option::Some(parent), parent: Option::Some(parent),
temporary_vars: parent.temporary_vars.clone(), temporary_vars: parent.temporary_vars.clone(),
@@ -126,12 +128,11 @@ impl<'a> VariableScope<'a> {
/// to the stack. /// to the stack.
pub fn add_variable( pub fn add_variable(
&mut self, &mut self,
var_name: impl Into<String>, var_name: Cow<'a, str>,
location: LocationRequest, location: LocationRequest,
span: Option<Span>, span: Option<Span>,
) -> Result<VariableLocation, Error> { ) -> Result<VariableLocation<'a>, Error<'a>> {
let var_name = var_name.into(); if self.var_lookup_table.contains_key(&var_name) {
if self.var_lookup_table.contains_key(var_name.as_str()) {
return Err(Error::DuplicateVariable(var_name, span)); return Err(Error::DuplicateVariable(var_name, span));
} }
let var_location = match location { let var_location = match location {
@@ -166,11 +167,10 @@ impl<'a> VariableScope<'a> {
pub fn define_const( pub fn define_const(
&mut self, &mut self,
var_name: impl Into<String>, var_name: Cow<'a, str>,
value: Literal, value: Literal<'a>,
span: Option<Span>, span: Option<Span>,
) -> Result<VariableLocation, Error> { ) -> Result<VariableLocation<'a>, Error<'a>> {
let var_name = var_name.into();
if self.var_lookup_table.contains_key(&var_name) { if self.var_lookup_table.contains_key(&var_name) {
return Err(Error::DuplicateVariable(var_name, span)); return Err(Error::DuplicateVariable(var_name, span));
} }
@@ -183,13 +183,11 @@ impl<'a> VariableScope<'a> {
pub fn get_location_of( pub fn get_location_of(
&self, &self,
var_name: impl Into<String>, var_name: &Cow<'a, str>,
span: Option<Span>, span: Option<Span>,
) -> Result<VariableLocation, Error> { ) -> Result<VariableLocation<'a>, Error<'a>> {
let var_name = var_name.into();
// 1. Check this scope // 1. Check this scope
if let Some(var) = self.var_lookup_table.get(var_name.as_str()) { if let Some(var) = self.var_lookup_table.get(var_name) {
if let VariableLocation::Stack(inserted_at_offset) = var { if let VariableLocation::Stack(inserted_at_offset) = var {
// Return offset relative to CURRENT sp // Return offset relative to CURRENT sp
return Ok(VariableLocation::Stack( return Ok(VariableLocation::Stack(
@@ -210,7 +208,7 @@ impl<'a> VariableScope<'a> {
return Ok(loc); return Ok(loc);
} }
Err(Error::UnknownVariable(var_name, span)) Err(Error::UnknownVariable(var_name.clone(), span))
} }
pub fn has_parent(&self) -> bool { pub fn has_parent(&self) -> bool {
@@ -220,11 +218,10 @@ impl<'a> VariableScope<'a> {
#[allow(dead_code)] #[allow(dead_code)]
pub fn free_temp( pub fn free_temp(
&mut self, &mut self,
var_name: impl Into<String>, var_name: Cow<'a, str>,
span: Option<Span>, span: Option<Span>,
) -> Result<(), Error> { ) -> Result<(), Error<'a>> {
let var_name = var_name.into(); let Some(location) = self.var_lookup_table.remove(&var_name) else {
let Some(location) = self.var_lookup_table.remove(var_name.as_str()) else {
return Err(Error::UnknownVariable(var_name, span)); return Err(Error::UnknownVariable(var_name, span));
}; };
@@ -234,7 +231,7 @@ impl<'a> VariableScope<'a> {
} }
VariableLocation::Persistant(_) => { VariableLocation::Persistant(_) => {
return Err(Error::UnknownVariable( return Err(Error::UnknownVariable(
String::from("Attempted to free a `let` variable."), Cow::from("Attempted to free a `let` variable."),
span, span,
)); ));
} }

View File

@@ -0,0 +1,41 @@
// Pressure numbers are in KPa
device self = "db";
device emergencyRelief = "d0";
device greenhouseSensor = "d1";
device recycleValve = "d2";
const MAX_INTERIOR_PRESSURE = 80;
const MAX_INTERIOR_TEMP = 28c;
const MIN_INTERIOR_PRESSURE = 75;
const MIN_INTERIOR_TEMP = 25c;
const daylightSensor = 1076425094;
const growLight = hash("StructureGrowLight");
const wallLight = hash("StructureLightLong");
const lightRound = hash("StructureLightRound");
let shouldPurge = false;
loop {
let interiorPress = greenhouseSensor.Pressure;
let interiorTemp = greenhouseSensor.Temperature;
shouldPurge = (
interiorPress > MAX_INTERIOR_PRESSURE ||
interiorTemp > MAX_INTERIOR_TEMP
) || shouldPurge;
emergencyRelief.On = shouldPurge;
recycleValve.On = !shouldPurge;
if (shouldPurge && (interiorPress < MIN_INTERIOR_PRESSURE && interiorTemp < MIN_INTERIOR_TEMP)) {
shouldPurge = false;
}
let solarAngle = lb(daylightSensor, "SolarAngle", "Average");
let isDaylight = solarAngle < 90;
sb(growLight, "On", isDaylight);
sb(wallLight, "On", !isDaylight);
sb(lightRound, "On", !isDaylight);
}

View File

@@ -3,15 +3,10 @@ macro_rules! documented {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Internal Helper: Filter doc comments // Internal Helper: Filter doc comments
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Case 1: Doc comment. Return Some("string").
// We match the specific structure of a doc attribute.
(@doc_filter #[doc = $doc:expr]) => { (@doc_filter #[doc = $doc:expr]) => {
Some($doc) Some($doc)
}; };
// Case 2: Other attributes (derives, etc.). Return None.
// We catch any other token sequence inside the brackets.
(@doc_filter #[$($attr:tt)*]) => { (@doc_filter #[$($attr:tt)*]) => {
None None
}; };
@@ -30,23 +25,59 @@ macro_rules! documented {
}; };
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Main Macro Entry Point // Entry Point 1: Enum with a single Lifetime (e.g. enum Foo<'a>)
// -------------------------------------------------------------------------
(
$(#[$enum_attr:meta])* $vis:vis enum $name:ident < $lt:lifetime > {
$($body:tt)*
}
) => {
documented!(@generate
meta: [$(#[$enum_attr])*],
vis: [$vis],
name: [$name],
generics: [<$lt>],
body: [$($body)*]
);
};
// -------------------------------------------------------------------------
// Entry Point 2: Regular Enum (No Generics)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
( (
$(#[$enum_attr:meta])* $vis:vis enum $name:ident { $(#[$enum_attr:meta])* $vis:vis enum $name:ident {
$($body:tt)*
}
) => {
documented!(@generate
meta: [$(#[$enum_attr])*],
vis: [$vis],
name: [$name],
generics: [],
body: [$($body)*]
);
};
// -------------------------------------------------------------------------
// Code Generator (Shared Logic)
// -------------------------------------------------------------------------
(@generate
meta: [$(#[$enum_attr:meta])*],
vis: [$vis:vis],
name: [$name:ident],
generics: [$($generics:tt)*],
body: [
$( $(
// Capture attributes as a sequence of token trees inside brackets
// to avoid "local ambiguity" and handle multi-token attributes (like doc="...").
$(#[ $($variant_attr:tt)* ])* $(#[ $($variant_attr:tt)* ])*
$variant:ident $variant:ident
$( ($($tuple:tt)*) )? $( ($($tuple:tt)*) )?
$( {$($structure:tt)*} )? $( {$($structure:tt)*} )?
),* $(,)? ),* $(,)?
} ]
) => { ) => {
// 1. Generate the actual Enum definition // 1. Generate the Enum Definition
$(#[$enum_attr])* $(#[$enum_attr])*
$vis enum $name { $vis enum $name $($generics)* {
$( $(
$(#[ $($variant_attr)* ])* $(#[ $($variant_attr)* ])*
$variant $variant
@@ -55,20 +86,19 @@ macro_rules! documented {
)* )*
} }
// 2. Implement the Documentation Trait // 2. Implement Documentation Trait
impl Documentation for $name { // We apply the captured generics (e.g., <'a>) to both the impl and the type
impl $($generics)* Documentation for $name $($generics)* {
fn docs(&self) -> String { fn docs(&self) -> String {
match self { match self {
$( $(
documented!(@arm $name $variant $( ($($tuple)*) )? $( {$($structure)*} )? ) => { documented!(@arm $name $variant $( ($($tuple)*) )? $( {$($structure)*} )? ) => {
// Create a temporary array of Option<&str> for all attributes
let doc_lines: &[Option<&str>] = &[ let doc_lines: &[Option<&str>] = &[
$( $(
documented!(@doc_filter #[ $($variant_attr)* ]) documented!(@doc_filter #[ $($variant_attr)* ])
),* ),*
]; ];
// Filter out the Nones (non-doc attributes), join, and return
doc_lines.iter() doc_lines.iter()
.filter_map(|&d| d) .filter_map(|&d| d)
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -80,7 +110,6 @@ macro_rules! documented {
} }
} }
// 3. Implement Static Documentation Provider
#[allow(dead_code)] #[allow(dead_code)]
fn get_all_documentation() -> Vec<(&'static str, String)> { fn get_all_documentation() -> Vec<(&'static str, String)> {
vec![ vec![
@@ -88,7 +117,6 @@ macro_rules! documented {
( (
stringify!($variant), stringify!($variant),
{ {
// Re-use the same extraction logic
let doc_lines: &[Option<&str>] = &[ let doc_lines: &[Option<&str>] = &[
$( $(
documented!(@doc_filter #[ $($variant_attr)* ]) documented!(@doc_filter #[ $($variant_attr)* ])

View File

@@ -9,9 +9,11 @@ macro_rules! with_syscalls {
"load", "load",
"loadBatched", "loadBatched",
"loadBatchedNamed", "loadBatchedNamed",
"loadSlot",
"set", "set",
"setBatched", "setBatched",
"setBatchedNamed", "setBatchedNamed",
"setSlot",
"acos", "acos",
"asin", "asin",
"atan", "atan",
@@ -32,9 +34,11 @@ macro_rules! with_syscalls {
"l", "l",
"lb", "lb",
"lbn", "lbn",
"ls",
"s", "s",
"sb", "sb",
"sbn" "sbn",
"ss"
); );
}; };
} }

View File

@@ -4,10 +4,11 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
quick-error = { workspace = true }
tokenizer = { path = "../tokenizer" } tokenizer = { path = "../tokenizer" }
helpers = { path = "../helpers" } helpers = { path = "../helpers" }
lsp-types = { workspace = true } lsp-types = { workspace = true }
safer-ffi = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies] [dev-dependencies]

File diff suppressed because it is too large Load Diff

View File

@@ -4,73 +4,73 @@ use helpers::prelude::*;
documented! { documented! {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Math { pub enum Math<'a> {
/// Returns the angle in radians whose cosine is the specified number. /// Returns the angle in radians whose cosine is the specified number.
/// ## IC10 /// ## IC10
/// `acos r? a(r?|num)` /// `acos r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = acos(number|var|expression);` /// `let item = acos(number|var|expression);`
Acos(Box<Spanned<Expression>>), Acos(Box<Spanned<Expression<'a>>>),
/// Returns the angle in radians whose sine is the specified number. /// Returns the angle in radians whose sine is the specified number.
/// ## IC10 /// ## IC10
/// `asin r? a(r?|num)` /// `asin r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = asin(number|var|expression);` /// `let item = asin(number|var|expression);`
Asin(Box<Spanned<Expression>>), Asin(Box<Spanned<Expression<'a>>>),
/// Returns the angle in radians whose tangent is the specified number. /// Returns the angle in radians whose tangent is the specified number.
/// ## IC10 /// ## IC10
/// `atan r? a(r?|num)` /// `atan r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = atan(number|var|expression);` /// `let item = atan(number|var|expression);`
Atan(Box<Spanned<Expression>>), Atan(Box<Spanned<Expression<'a>>>),
/// Returns the angle in radians whose tangent is the quotient of the specified numbers. /// Returns the angle in radians whose tangent is the quotient of the specified numbers.
/// ## IC10 /// ## IC10
/// `atan2 r? a(r?|num) b(r?|num)` /// `atan2 r? a(r?|num) b(r?|num)`
/// ## Slang /// ## Slang
/// `let item = atan2((number|var|expression), (number|var|expression));` /// `let item = atan2((number|var|expression), (number|var|expression));`
Atan2(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Atan2(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
/// Gets the absolute value of a number. /// Gets the absolute value of a number.
/// ## IC10 /// ## IC10
/// `abs r? a(r?|num)` /// `abs r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = abs((number|var|expression));` /// `let item = abs((number|var|expression));`
Abs(Box<Spanned<Expression>>), Abs(Box<Spanned<Expression<'a>>>),
/// Rounds a number up to the nearest whole number. /// Rounds a number up to the nearest whole number.
/// ## IC10 /// ## IC10
/// `ceil r? a(r?|num)` /// `ceil r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = ceil((number|var|expression));` /// `let item = ceil((number|var|expression));`
Ceil(Box<Spanned<Expression>>), Ceil(Box<Spanned<Expression<'a>>>),
/// Returns the cosine of the specified angle in radians. /// Returns the cosine of the specified angle in radians.
/// ## IC10 /// ## IC10
/// `cos r? a(r?|num)` /// `cos r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = cos((number|var|expression));` /// `let item = cos((number|var|expression));`
Cos(Box<Spanned<Expression>>), Cos(Box<Spanned<Expression<'a>>>),
/// Rounds a number down to the nearest whole number. /// Rounds a number down to the nearest whole number.
/// ## IC10 /// ## IC10
/// `floor r? a(r?|num)` /// `floor r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = floor((number|var|expression));` /// `let item = floor((number|var|expression));`
Floor(Box<Spanned<Expression>>), Floor(Box<Spanned<Expression<'a>>>),
/// Computes the natural logarithm of a number. /// Computes the natural logarithm of a number.
/// ## IC10 /// ## IC10
/// `log r? a(r?|num)` /// `log r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = log((number|var|expression));` /// `let item = log((number|var|expression));`
Log(Box<Spanned<Expression>>), Log(Box<Spanned<Expression<'a>>>),
/// Computes the maximum of two numbers. /// Computes the maximum of two numbers.
/// ## IC10 /// ## IC10
/// `max r? a(r?|num) b(r?|num)` /// `max r? a(r?|num) b(r?|num)`
/// ## Slang /// ## Slang
/// `let item = max((number|var|expression), (number|var|expression));` /// `let item = max((number|var|expression), (number|var|expression));`
Max(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Max(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
/// Computes the minimum of two numbers. /// Computes the minimum of two numbers.
/// ## IC10 /// ## IC10
/// `min r? a(r?|num) b(r?|num)` /// `min r? a(r?|num) b(r?|num)`
/// ## Slang /// ## Slang
/// `let item = min((number|var|expression), (number|var|expression));` /// `let item = min((number|var|expression), (number|var|expression));`
Min(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Min(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
/// Gets a random number between 0 and 1. /// Gets a random number between 0 and 1.
/// ## IC10 /// ## IC10
/// `rand r?` /// `rand r?`
@@ -82,29 +82,29 @@ documented! {
/// `sin r? a(r?|num)` /// `sin r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = sin((number|var|expression));` /// `let item = sin((number|var|expression));`
Sin(Box<Spanned<Expression>>), Sin(Box<Spanned<Expression<'a>>>),
/// Computes the square root of a number. /// Computes the square root of a number.
/// ## IC10 /// ## IC10
/// `sqrt r? a(r?|num)` /// `sqrt r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = sqrt((number|var|expression));` /// `let item = sqrt((number|var|expression));`
Sqrt(Box<Spanned<Expression>>), Sqrt(Box<Spanned<Expression<'a>>>),
/// Returns the tangent of the specified angle in radians. /// Returns the tangent of the specified angle in radians.
/// ## IC10 /// ## IC10
/// `tan r? a(r?|num)` /// `tan r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = tan((number|var|expression));` /// `let item = tan((number|var|expression));`
Tan(Box<Spanned<Expression>>), Tan(Box<Spanned<Expression<'a>>>),
/// Truncates a number by removing the decimal portion. /// Truncates a number by removing the decimal portion.
/// ## IC10 /// ## IC10
/// `trunc r? a(r?|num)` /// `trunc r? a(r?|num)`
/// ## Slang /// ## Slang
/// `let item = trunc((number|var|expression));` /// `let item = trunc((number|var|expression));`
Trunc(Box<Spanned<Expression>>), Trunc(Box<Spanned<Expression<'a>>>),
} }
} }
impl std::fmt::Display for Math { impl<'a> std::fmt::Display for Math<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Math::Acos(a) => write!(f, "acos({})", a), Math::Acos(a) => write!(f, "acos({})", a),
@@ -129,7 +129,7 @@ impl std::fmt::Display for Math {
documented! { documented! {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum System { pub enum System<'a> {
/// Pauses execution for exactly 1 tick and then resumes. /// Pauses execution for exactly 1 tick and then resumes.
/// ## IC10 /// ## IC10
/// `yield` /// `yield`
@@ -141,7 +141,7 @@ documented! {
/// `sleep a(r?|num)` /// `sleep a(r?|num)`
/// ## Slang /// ## Slang
/// `sleep(number|var);` /// `sleep(number|var);`
Sleep(Box<Spanned<Expression>>), Sleep(Box<Spanned<Expression<'a>>>),
/// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY /// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY
/// optimized away unless you bind it to a `let` variable. If you use a `const` variable /// optimized away unless you bind it to a `let` variable. If you use a `const` variable
/// however, the hash is correctly computed at compile time and substitued automatically. /// however, the hash is correctly computed at compile time and substitued automatically.
@@ -155,7 +155,7 @@ documented! {
/// const compDoor = hash("StructureCompositeDoor"); /// const compDoor = hash("StructureCompositeDoor");
/// setOnDeviceBatched(compDoor, "Lock", true); /// setOnDeviceBatched(compDoor, "Lock", true);
/// ``` /// ```
Hash(Spanned<Literal>), Hash(Spanned<Literal<'a>>),
/// Represents a function which loads a device variable into a register. /// Represents a function which loads a device variable into a register.
/// ## IC10 /// ## IC10
/// `l r? d? var` /// `l r? d? var`
@@ -163,7 +163,7 @@ documented! {
/// `let item = load(deviceHash, "LogicType");` /// `let item = load(deviceHash, "LogicType");`
/// `let item = l(deviceHash, "LogicType");` /// `let item = l(deviceHash, "LogicType");`
/// `let item = deviceAlias.LogicType;` /// `let item = deviceAlias.LogicType;`
LoadFromDevice(Spanned<LiteralOrVariable>, Spanned<Literal>), LoadFromDevice(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>),
/// Function which gets a LogicType from all connected network devices that match /// Function which gets a LogicType from all connected network devices that match
/// the provided device hash and name, aggregating them via a batchMode /// the provided device hash and name, aggregating them via a batchMode
/// ## IC10 /// ## IC10
@@ -172,10 +172,10 @@ documented! {
/// `loadBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");` /// `loadBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");`
/// `lbn(deviceHash, deviceName, "LogicType", "BatchMode");` /// `lbn(deviceHash, deviceName, "LogicType", "BatchMode");`
LoadBatchNamed( LoadBatchNamed(
Spanned<LiteralOrVariable>, Spanned<LiteralOrVariable<'a>>,
Spanned<LiteralOrVariable>, Spanned<LiteralOrVariable<'a>>,
Spanned<Literal>, Spanned<Literal<'a>>,
Spanned<Literal>, Spanned<Literal<'a>>,
), ),
/// Loads a LogicType from all connected network devices, aggregating them via a /// Loads a LogicType from all connected network devices, aggregating them via a
/// BatchMode /// BatchMode
@@ -184,7 +184,7 @@ documented! {
/// ## Slang /// ## Slang
/// `loadBatched(deviceHash, "Variable", "LogicType");` /// `loadBatched(deviceHash, "Variable", "LogicType");`
/// `lb(deviceHash, "Variable", "LogicType");` /// `lb(deviceHash, "Variable", "LogicType");`
LoadBatch(Spanned<LiteralOrVariable>, Spanned<Literal>, Spanned<Literal>), LoadBatch(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>, Spanned<Literal<'a>>),
/// Represents a function which stores a setting into a specific device. /// Represents a function which stores a setting into a specific device.
/// ## IC10 /// ## IC10
/// `s d? logicType r?` /// `s d? logicType r?`
@@ -192,7 +192,7 @@ documented! {
/// `set(deviceHash, "LogicType", (number|var));` /// `set(deviceHash, "LogicType", (number|var));`
/// `s(deviceHash, "LogicType", (number|var));` /// `s(deviceHash, "LogicType", (number|var));`
/// `deviceAlias.LogicType = (number|var);` /// `deviceAlias.LogicType = (number|var);`
SetOnDevice(Spanned<LiteralOrVariable>, Spanned<Literal>, Box<Spanned<Expression>>), SetOnDevice(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>, Box<Spanned<Expression<'a>>>),
/// Represents a function which stores a setting to all devices that match /// Represents a function which stores a setting to all devices that match
/// the given deviceHash /// the given deviceHash
/// ## IC10 /// ## IC10
@@ -200,7 +200,7 @@ documented! {
/// ## Slang /// ## Slang
/// `setBatched(deviceHash, "LogicType", (number|var));` /// `setBatched(deviceHash, "LogicType", (number|var));`
/// `sb(deviceHash, "LogicType", (number|var));` /// `sb(deviceHash, "LogicType", (number|var));`
SetOnDeviceBatched(Spanned<LiteralOrVariable>, Spanned<Literal>, Box<Spanned<Expression>>), SetOnDeviceBatched(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>, Box<Spanned<Expression<'a>>>),
/// Represents a function which stores a setting to all devices that match /// Represents a function which stores a setting to all devices that match
/// both the given deviceHash AND the given nameHash /// both the given deviceHash AND the given nameHash
/// ## IC10 /// ## IC10
@@ -209,15 +209,39 @@ documented! {
/// `setBatchedNamed(deviceHash, nameHash, "LogicType", (number|var));` /// `setBatchedNamed(deviceHash, nameHash, "LogicType", (number|var));`
/// `sbn(deviceHash, nameHash, "LogicType", (number|var));` /// `sbn(deviceHash, nameHash, "LogicType", (number|var));`
SetOnDeviceBatchedNamed( SetOnDeviceBatchedNamed(
Spanned<LiteralOrVariable>, Spanned<LiteralOrVariable<'a>>,
Spanned<LiteralOrVariable>, Spanned<LiteralOrVariable<'a>>,
Spanned<Literal>, Spanned<Literal<'a>>,
Box<Spanned<Expression>>, Box<Spanned<Expression<'a>>>,
), ),
/// Loads slot LogicSlotType from device into a variable
///
/// ## IC10
/// `ls r0 d0 2 Occupied`
/// ## Slang
/// `let isOccupied = loadSlot(deviceHash, 2, "Occupied");`
/// `let isOccupied = ls(deviceHash, 2, "Occupied");`
LoadSlot(
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Spanned<Literal<'a>>
),
/// Stores a value of LogicType on a device by the index value
/// ## IC10
/// `ss d0 0 "Open" 1`
/// ## Slang
/// `setSlot(deviceHash, 0, "Open", true);`
/// `ss(deviceHash, 0, "Open", true);`
SetSlot(
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>
)
} }
} }
impl std::fmt::Display for System { impl<'a> std::fmt::Display for System<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
System::Yield => write!(f, "yield()"), System::Yield => write!(f, "yield()"),
@@ -235,6 +259,8 @@ impl std::fmt::Display for System {
System::SetOnDeviceBatchedNamed(a, b, c, d) => { System::SetOnDeviceBatchedNamed(a, b, c, d) => {
write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d) write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d)
} }
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
} }
} }
} }
@@ -242,13 +268,13 @@ impl std::fmt::Display for System {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
/// This represents built in functions that cannot be overwritten, but can be invoked by the user as functions. /// This represents built in functions that cannot be overwritten, but can be invoked by the user as functions.
pub enum SysCall { pub enum SysCall<'a> {
System(System), System(System<'a>),
/// Represents any mathmatical function that can be called. /// Represents any mathmatical function that can be called.
Math(Math), Math(Math<'a>),
} }
impl Documentation for SysCall { impl<'a> Documentation for SysCall<'a> {
fn docs(&self) -> String { fn docs(&self) -> String {
match self { match self {
Self::System(s) => s.docs(), Self::System(s) => s.docs(),
@@ -264,7 +290,7 @@ impl Documentation for SysCall {
} }
} }
impl std::fmt::Display for SysCall { impl<'a> std::fmt::Display for SysCall<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
SysCall::System(s) => write!(f, "{}", s), SysCall::System(s) => write!(f, "{}", s),
@@ -273,7 +299,7 @@ impl std::fmt::Display for SysCall {
} }
} }
impl SysCall { impl<'a> SysCall<'a> {
pub fn is_syscall(identifier: &str) -> bool { pub fn is_syscall(identifier: &str) -> bool {
tokenizer::token::is_syscall(identifier) tokenizer::token::is_syscall(identifier)
} }

View File

@@ -160,3 +160,37 @@ fn test_negative_literal_const() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_ternary_expression() -> Result<()> {
let expr = parser!(r#"let i = x ? 1 : 2;"#).parse()?.unwrap();
assert_eq!("(let i = (x ? 1 : 2))", expr.to_string());
Ok(())
}
#[test]
fn test_complex_binary_with_ternary() -> Result<()> {
let expr = parser!("let i = (x ? 1 : 3) * 2;").parse()?.unwrap();
assert_eq!("(let i = ((x ? 1 : 3) * 2))", expr.to_string());
Ok(())
}
#[test]
fn test_operator_prescedence_with_ternary() -> Result<()> {
let expr = parser!("let x = x ? 1 : 3 * 2;").parse()?.unwrap();
assert_eq!("(let x = (x ? 1 : (3 * 2)))", expr.to_string());
Ok(())
}
#[test]
fn test_nested_ternary_right_associativity() -> Result<()> {
let expr = parser!("let i = a ? b : c ? d : e;").parse()?.unwrap();
assert_eq!("(let i = (a ? b : (c ? d : e)))", expr.to_string());
Ok(())
}

View File

@@ -1,24 +1,23 @@
use std::ops::Deref;
use crate::sys_call;
use super::sys_call::SysCall; use super::sys_call::SysCall;
use crate::sys_call;
use safer_ffi::prelude::*;
use std::{borrow::Cow, ops::Deref};
use tokenizer::token::Number; use tokenizer::token::Number;
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum Literal { pub enum Literal<'a> {
Number(Number), Number(Number),
String(String), String(Cow<'a, str>),
Boolean(bool), Boolean(bool),
} }
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum LiteralOr<T> { pub enum LiteralOr<'a, T> {
Literal(Spanned<Literal>), Literal(Spanned<Literal<'a>>),
Or(Spanned<T>), Or(Spanned<T>),
} }
impl<T: std::fmt::Display> std::fmt::Display for LiteralOr<T> { impl<'a, T: std::fmt::Display> std::fmt::Display for LiteralOr<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Literal(l) => write!(f, "{l}"), Self::Literal(l) => write!(f, "{l}"),
@@ -27,7 +26,7 @@ impl<T: std::fmt::Display> std::fmt::Display for LiteralOr<T> {
} }
} }
impl std::fmt::Display for Literal { impl<'a> std::fmt::Display for Literal<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Literal::Number(n) => write!(f, "{}", n), Literal::Number(n) => write!(f, "{}", n),
@@ -38,16 +37,16 @@ impl std::fmt::Display for Literal {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum BinaryExpression { pub enum BinaryExpression<'a> {
Add(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Add(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Multiply(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Multiply(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Divide(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Divide(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Subtract(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Subtract(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Exponent(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Exponent(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Modulo(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Modulo(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
} }
impl std::fmt::Display for BinaryExpression { impl<'a> std::fmt::Display for BinaryExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
BinaryExpression::Add(l, r) => write!(f, "({} + {})", l, r), BinaryExpression::Add(l, r) => write!(f, "({} + {})", l, r),
@@ -61,19 +60,19 @@ impl std::fmt::Display for BinaryExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum LogicalExpression { pub enum LogicalExpression<'a> {
And(Box<Spanned<Expression>>, Box<Spanned<Expression>>), And(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Or(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Or(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Not(Box<Spanned<Expression>>), Not(Box<Spanned<Expression<'a>>>),
Equal(Box<Spanned<Expression>>, Box<Spanned<Expression>>), Equal(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
NotEqual(Box<Spanned<Expression>>, Box<Spanned<Expression>>), NotEqual(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
GreaterThan(Box<Spanned<Expression>>, Box<Spanned<Expression>>), GreaterThan(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
GreaterThanOrEqual(Box<Spanned<Expression>>, Box<Spanned<Expression>>), GreaterThanOrEqual(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
LessThan(Box<Spanned<Expression>>, Box<Spanned<Expression>>), LessThan(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
LessThanOrEqual(Box<Spanned<Expression>>, Box<Spanned<Expression>>), LessThanOrEqual(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
} }
impl std::fmt::Display for LogicalExpression { impl<'a> std::fmt::Display for LogicalExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
LogicalExpression::And(l, r) => write!(f, "({} && {})", l, r), LogicalExpression::And(l, r) => write!(f, "({} && {})", l, r),
@@ -90,25 +89,25 @@ impl std::fmt::Display for LogicalExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct AssignmentExpression { pub struct AssignmentExpression<'a> {
pub assignee: Box<Spanned<Expression>>, pub assignee: Box<Spanned<Expression<'a>>>,
pub expression: Box<Spanned<Expression>>, pub expression: Box<Spanned<Expression<'a>>>,
} }
impl std::fmt::Display for AssignmentExpression { impl<'a> std::fmt::Display for AssignmentExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({} = {})", self.assignee, self.expression) write!(f, "({} = {})", self.assignee, self.expression)
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct FunctionExpression { pub struct FunctionExpression<'a> {
pub name: Spanned<String>, pub name: Spanned<Cow<'a, str>>,
pub arguments: Vec<Spanned<String>>, pub arguments: Vec<Spanned<Cow<'a, str>>>,
pub body: BlockExpression, pub body: BlockExpression<'a>,
} }
impl std::fmt::Display for FunctionExpression { impl<'a> std::fmt::Display for FunctionExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -125,9 +124,9 @@ impl std::fmt::Display for FunctionExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct BlockExpression(pub Vec<Spanned<Expression>>); pub struct BlockExpression<'a>(pub Vec<Spanned<Expression<'a>>>);
impl std::fmt::Display for BlockExpression { impl<'a> std::fmt::Display for BlockExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -142,12 +141,12 @@ impl std::fmt::Display for BlockExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct InvocationExpression { pub struct InvocationExpression<'a> {
pub name: Spanned<String>, pub name: Spanned<Cow<'a, str>>,
pub arguments: Vec<Spanned<Expression>>, pub arguments: Vec<Spanned<Expression<'a>>>,
} }
impl std::fmt::Display for InvocationExpression { impl<'a> std::fmt::Display for InvocationExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -163,25 +162,25 @@ impl std::fmt::Display for InvocationExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct MemberAccessExpression { pub struct MemberAccessExpression<'a> {
pub object: Box<Spanned<Expression>>, pub object: Box<Spanned<Expression<'a>>>,
pub member: Spanned<String>, pub member: Spanned<Cow<'a, str>>,
} }
impl std::fmt::Display for MemberAccessExpression { impl<'a> std::fmt::Display for MemberAccessExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.object, self.member) write!(f, "{}.{}", self.object, self.member)
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct MethodCallExpression { pub struct MethodCallExpression<'a> {
pub object: Box<Spanned<Expression>>, pub object: Box<Spanned<Expression<'a>>>,
pub method: Spanned<String>, pub method: Spanned<Cow<'a, str>>,
pub arguments: Vec<Spanned<Expression>>, pub arguments: Vec<Spanned<Expression<'a>>>,
} }
impl std::fmt::Display for MethodCallExpression { impl<'a> std::fmt::Display for MethodCallExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -198,12 +197,12 @@ impl std::fmt::Display for MethodCallExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum LiteralOrVariable { pub enum LiteralOrVariable<'a> {
Literal(Literal), Literal(Literal<'a>),
Variable(Spanned<String>), Variable(Spanned<Cow<'a, str>>),
} }
impl std::fmt::Display for LiteralOrVariable { impl<'a> std::fmt::Display for LiteralOrVariable<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
LiteralOrVariable::Literal(l) => write!(f, "{}", l), LiteralOrVariable::Literal(l) => write!(f, "{}", l),
@@ -213,46 +212,46 @@ impl std::fmt::Display for LiteralOrVariable {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ConstDeclarationExpression { pub struct ConstDeclarationExpression<'a> {
pub name: Spanned<String>, pub name: Spanned<Cow<'a, str>>,
pub value: LiteralOr<SysCall>, pub value: LiteralOr<'a, SysCall<'a>>,
} }
impl ConstDeclarationExpression { impl<'a> ConstDeclarationExpression<'a> {
pub fn is_syscall_supported(call: &SysCall) -> bool { pub fn is_syscall_supported(call: &SysCall) -> bool {
use sys_call::System; use sys_call::System;
matches!(call, SysCall::System(sys) if matches!(sys, System::Hash(_))) matches!(call, SysCall::System(sys) if matches!(sys, System::Hash(_)))
} }
} }
impl std::fmt::Display for ConstDeclarationExpression { impl<'a> std::fmt::Display for ConstDeclarationExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(const {} = {})", self.name, self.value) write!(f, "(const {} = {})", self.name, self.value)
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct DeviceDeclarationExpression { pub struct DeviceDeclarationExpression<'a> {
/// any variable-like name /// any variable-like name
pub name: Spanned<String>, pub name: Spanned<Cow<'a, str>>,
/// The device port, ex. (db, d0, d1, d2, d3, d4, d5) /// The device port, ex. (db, d0, d1, d2, d3, d4, d5)
pub device: String, pub device: Cow<'a, str>,
} }
impl std::fmt::Display for DeviceDeclarationExpression { impl<'a> std::fmt::Display for DeviceDeclarationExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(device {} = {})", self.name, self.device) write!(f, "(device {} = {})", self.name, self.device)
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct IfExpression { pub struct IfExpression<'a> {
pub condition: Box<Spanned<Expression>>, pub condition: Box<Spanned<Expression<'a>>>,
pub body: Spanned<BlockExpression>, pub body: Spanned<BlockExpression<'a>>,
pub else_branch: Option<Box<Spanned<Expression>>>, pub else_branch: Option<Box<Spanned<Expression<'a>>>>,
} }
impl std::fmt::Display for IfExpression { impl<'a> std::fmt::Display for IfExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(if ({}) {}", self.condition, self.body)?; write!(f, "(if ({}) {}", self.condition, self.body)?;
if let Some(else_branch) = &self.else_branch { if let Some(else_branch) = &self.else_branch {
@@ -263,23 +262,40 @@ impl std::fmt::Display for IfExpression {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct LoopExpression { pub struct LoopExpression<'a> {
pub body: Spanned<BlockExpression>, pub body: Spanned<BlockExpression<'a>>,
} }
impl std::fmt::Display for LoopExpression { impl<'a> std::fmt::Display for LoopExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(loop {})", self.body) write!(f, "(loop {})", self.body)
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct WhileExpression { pub struct WhileExpression<'a> {
pub condition: Box<Spanned<Expression>>, pub condition: Box<Spanned<Expression<'a>>>,
pub body: BlockExpression, pub body: BlockExpression<'a>,
} }
impl std::fmt::Display for WhileExpression { #[derive(Debug, PartialEq, Eq)]
pub struct TernaryExpression<'a> {
pub condition: Box<Spanned<Expression<'a>>>,
pub true_value: Box<Spanned<Expression<'a>>>,
pub false_value: Box<Spanned<Expression<'a>>>,
}
impl<'a> std::fmt::Display for TernaryExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"({} ? {} : {})",
self.condition, self.true_value, self.false_value
)
}
}
impl<'a> std::fmt::Display for WhileExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(while {} {})", self.condition, self.body) write!(f, "(while {} {})", self.condition, self.body)
} }
@@ -347,32 +363,33 @@ impl<T> Deref for Spanned<T> {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Expression { pub enum Expression<'a> {
Assignment(Spanned<AssignmentExpression>), Assignment(Spanned<AssignmentExpression<'a>>),
Binary(Spanned<BinaryExpression>), Binary(Spanned<BinaryExpression<'a>>),
Block(Spanned<BlockExpression>), Block(Spanned<BlockExpression<'a>>),
Break(Span), Break(Span),
ConstDeclaration(Spanned<ConstDeclarationExpression>), ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>),
Continue(Span), Continue(Span),
Declaration(Spanned<String>, Box<Spanned<Expression>>), Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>),
DeviceDeclaration(Spanned<DeviceDeclarationExpression>), DeviceDeclaration(Spanned<DeviceDeclarationExpression<'a>>),
Function(Spanned<FunctionExpression>), Function(Spanned<FunctionExpression<'a>>),
If(Spanned<IfExpression>), If(Spanned<IfExpression<'a>>),
Invocation(Spanned<InvocationExpression>), Invocation(Spanned<InvocationExpression<'a>>),
Literal(Spanned<Literal>), Literal(Spanned<Literal<'a>>),
Logical(Spanned<LogicalExpression>), Logical(Spanned<LogicalExpression<'a>>),
Loop(Spanned<LoopExpression>), Loop(Spanned<LoopExpression<'a>>),
MemberAccess(Spanned<MemberAccessExpression>), MemberAccess(Spanned<MemberAccessExpression<'a>>),
MethodCall(Spanned<MethodCallExpression>), MethodCall(Spanned<MethodCallExpression<'a>>),
Negation(Box<Spanned<Expression>>), Negation(Box<Spanned<Expression<'a>>>),
Priority(Box<Spanned<Expression>>), Priority(Box<Spanned<Expression<'a>>>),
Return(Box<Spanned<Expression>>), Return(Option<Box<Spanned<Expression<'a>>>>),
Syscall(Spanned<SysCall>), Syscall(Spanned<SysCall<'a>>),
Variable(Spanned<String>), Ternary(Spanned<TernaryExpression<'a>>),
While(Spanned<WhileExpression>), Variable(Spanned<Cow<'a, str>>),
While(Spanned<WhileExpression<'a>>),
} }
impl std::fmt::Display for Expression { impl<'a> std::fmt::Display for Expression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Expression::Assignment(e) => write!(f, "{}", e), Expression::Assignment(e) => write!(f, "{}", e),
@@ -393,8 +410,17 @@ impl std::fmt::Display for Expression {
Expression::MethodCall(e) => write!(f, "{}", e), Expression::MethodCall(e) => write!(f, "{}", e),
Expression::Negation(e) => write!(f, "(-{})", e), Expression::Negation(e) => write!(f, "(-{})", e),
Expression::Priority(e) => write!(f, "({})", e), Expression::Priority(e) => write!(f, "({})", e),
Expression::Return(e) => write!(f, "(return {})", e), Expression::Return(e) => write!(
f,
"(return {})",
if let Some(e) = e {
e.to_string()
} else {
"".to_string()
}
),
Expression::Syscall(e) => write!(f, "{}", e), Expression::Syscall(e) => write!(f, "{}", e),
Expression::Ternary(e) => write!(f, "{}", e),
Expression::Variable(id) => write!(f, "{}", id), Expression::Variable(id) => write!(f, "{}", id),
Expression::While(e) => write!(f, "{}", e), Expression::While(e) => write!(f, "{}", e),
} }

View File

@@ -5,9 +5,10 @@ edition = "2024"
[dependencies] [dependencies]
rust_decimal = { workspace = true } rust_decimal = { workspace = true }
quick-error = { workspace = true }
lsp-types = { workspace = true } lsp-types = { workspace = true }
thiserror = { workspace = true }
helpers = { path = "../helpers" } helpers = { path = "../helpers" }
logos = "0.16"
[dev-dependencies] [dev-dependencies]
anyhow = { version = "^1" } anyhow = { version = "^1" }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,59 @@
use std::borrow::Cow;
use helpers::prelude::*; use helpers::prelude::*;
use logos::{Lexer, Logos, Skip, Span};
use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use thiserror::Error;
#[derive(Debug, Error, Default, Clone, PartialEq)]
pub enum LexError {
#[error("Attempted to parse an invalid number: {2}")]
NumberParse(usize, Span, String),
#[error("An invalid character was found in token stream: {2}")]
InvalidInput(usize, Span, String),
#[default]
#[error("An unknown error occurred")]
Other,
}
impl From<LexError> for Diagnostic {
fn from(value: LexError) -> Self {
match value {
LexError::NumberParse(line, col, str) | LexError::InvalidInput(line, col, str) => {
Diagnostic {
range: Range {
start: Position {
character: col.start as u32,
line: line as u32,
},
end: Position {
line: line as u32,
character: col.end as u32,
},
},
severity: Some(DiagnosticSeverity::ERROR),
message: str,
..Default::default()
}
}
_ => Diagnostic::default(),
}
}
}
impl LexError {
pub fn from_lexer<'a>(lex: &mut Lexer<'a, TokenType<'a>>) -> Self {
let mut span = lex.span();
let line = lex.extras.line_count;
span.start -= lex.extras.line_start_index;
span.end -= lex.extras.line_start_index;
Self::InvalidInput(line, span, lex.slice().chars().as_str().to_string())
}
}
// Define a local macro to consume the list // Define a local macro to consume the list
macro_rules! generate_check { macro_rules! generate_check {
@@ -10,29 +64,40 @@ macro_rules! generate_check {
} }
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Default)]
pub struct Token { pub struct Extras {
/// The type of the token pub line_count: usize,
pub token_type: TokenType, pub line_start_index: usize,
/// The line where the token was found
pub line: usize,
/// The column where the token was found
pub column: usize,
pub original_string: Option<String>,
} }
impl Token { fn update_line_index<'a>(lex: &mut Lexer<'a, TokenType<'a>>) -> Skip {
pub fn new( lex.extras.line_count += 1;
token_type: TokenType, lex.extras.line_start_index = lex.span().end;
line: usize, Skip
column: usize, }
original: Option<String>,
) -> Self { #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Token<'a> {
/// The type of the token
pub token_type: TokenType<'a>,
/// The line where the token was found
pub line: usize,
/// The span where the token starts and ends
pub span: Span,
}
impl<'a> std::fmt::Display for Token<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.token_type)
}
}
impl<'a> Token<'a> {
pub fn new(token_type: TokenType<'a>, line: usize, span: Span) -> Self {
Self { Self {
token_type, token_type,
line, line,
column, span,
original_string: original,
} }
} }
} }
@@ -79,25 +144,187 @@ impl Temperature {
} }
} }
#[derive(Debug, PartialEq, Hash, Eq, Clone)] macro_rules! symbol {
pub enum TokenType { ($var:ident) => {
|_| Symbol::$var
};
}
macro_rules! keyword {
($var:ident) => {
|_| Keyword::$var
};
}
#[derive(Debug, PartialEq, Hash, Eq, Clone, Logos)]
#[logos(skip r"[ \t\f]+")]
#[logos(extras = Extras)]
#[logos(error(LexError, LexError::from_lexer))]
pub enum TokenType<'a> {
#[regex(r"\n", update_line_index)]
Newline,
// matches strings with double quotes
#[regex(r#""(?:[^"\\]|\\.)*""#, |v| {
let str = v.slice();
Cow::from(&str[1..str.len() - 1])
})]
// matches strings with single quotes
#[regex(r#"'(?:[^'\\]|\\.)*'"#, |v| {
let str = v.slice();
Cow::from(&str[1..str.len() - 1])
})]
/// Represents a string token /// Represents a string token
String(String), String(Cow<'a, str>),
#[regex(r"[0-9][0-9_]*(\.[0-9][0-9_]*)?([cfk])?", parse_number)]
/// Represents a number token /// Represents a number token
Number(Number), Number(Number),
#[token("true", |_| true)]
#[token("false", |_| false)]
/// Represents a boolean token /// Represents a boolean token
Boolean(bool), Boolean(bool),
#[token("continue", keyword!(Continue))]
#[token("const", keyword!(Const))]
#[token("let", keyword!(Let))]
#[token("fn", keyword!(Fn))]
#[token("if", keyword!(If))]
#[token("device", keyword!(Device))]
#[token("else", keyword!(Else))]
#[token("return", keyword!(Return))]
#[token("enum", keyword!(Enum))]
#[token("loop", keyword!(Loop))]
#[token("break", keyword!(Break))]
#[token("while", keyword!(While))]
/// Represents a keyword token /// Represents a keyword token
Keyword(Keyword), Keyword(Keyword),
#[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |v| Cow::from(v.slice()))]
/// Represents an identifier token /// Represents an identifier token
Identifier(String), Identifier(Cow<'a, str>),
#[token("(", symbol!(LParen))]
#[token(")", symbol!(RParen))]
#[token("{", symbol!(LBrace))]
#[token("}", symbol!(RBrace))]
#[token("[", symbol!(LBracket))]
#[token("]", symbol!(RBracket))]
#[token(";", symbol!(Semicolon))]
#[token(":", symbol!(Colon))]
#[token(",", symbol!(Comma))]
#[token("+", symbol!(Plus))]
#[token("-", symbol!(Minus))]
#[token("*", symbol!(Asterisk))]
#[token("/", symbol!(Slash))]
#[token("<", symbol!(LessThan))]
#[token(">", symbol!(GreaterThan))]
#[token("=", symbol!(Assign))]
#[token("!", symbol!(LogicalNot))]
#[token(".", symbol!(Dot))]
#[token("^", symbol!(Caret))]
#[token("%", symbol!(Percent))]
#[token("?", symbol!(Question))]
#[token("==", symbol!(Equal))]
#[token("!=", symbol!(NotEqual))]
#[token("&&", symbol!(LogicalAnd))]
#[token("||", symbol!(LogicalOr))]
#[token("<=", symbol!(LessThanOrEqual))]
#[token(">=", symbol!(GreaterThanOrEqual))]
#[token("**", symbol!(Exp))]
/// Represents a symbol token /// Represents a symbol token
Symbol(Symbol), Symbol(Symbol),
#[token("//", |lex| Comment::Line(read_line(lex)))]
#[token("///", |lex| Comment::Doc(read_line(lex)))]
/// Represents a comment, both a line comment and a doc comment
Comment(Comment<'a>),
#[end]
/// Represents an end of file token /// Represents an end of file token
EOF, EOF,
} }
impl Documentation for TokenType { fn read_line<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Cow<'a, str> {
let rem = lexer.remainder();
let len = rem.find('\n').unwrap_or(rem.len());
let content = rem[..len].trim().to_string();
lexer.bump(len);
Cow::from(content)
}
#[derive(Hash, Debug, Eq, PartialEq, Clone)]
pub enum Comment<'a> {
Line(Cow<'a, str>),
Doc(Cow<'a, str>),
}
fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> {
let slice = lexer.slice();
let last_char = slice.chars().last().unwrap_or_default();
let (num_str, suffix) = match last_char {
'c' | 'k' | 'f' => (&slice[..slice.len() - 1], Some(last_char)),
_ => (slice, None),
};
let clean_str = if num_str.contains('_') {
num_str.replace('_', "")
} else {
num_str.to_string()
};
let line = lexer.extras.line_count;
let mut span = lexer.span();
span.end -= lexer.extras.line_start_index;
span.start -= lexer.extras.line_start_index;
let num = if clean_str.contains('.') {
Number::Decimal(
clean_str
.parse::<Decimal>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
)
} else {
Number::Integer(
clean_str
.parse::<i128>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
)
};
if let Some(suffix) = suffix {
Ok(match suffix {
'c' => Temperature::Celsius(num),
'f' => Temperature::Fahrenheit(num),
'k' => Temperature::Kelvin(num),
_ => unreachable!(),
}
.to_kelvin())
} else {
Ok(num)
}
}
impl<'a> std::fmt::Display for Comment<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Line(c) => write!(f, "// {}", c),
Self::Doc(d) => {
let lines = d
.split('\n')
.map(|s| format!("/// {s}"))
.collect::<Vec<_>>()
.join("\n");
write!(f, "{}", lines)
}
}
}
}
impl<'a> Documentation for TokenType<'a> {
fn docs(&self) -> String { fn docs(&self) -> String {
match self { match self {
Self::Keyword(k) => k.docs(), Self::Keyword(k) => k.docs(),
@@ -112,7 +339,7 @@ impl Documentation for TokenType {
helpers::with_syscalls!(generate_check); helpers::with_syscalls!(generate_check);
impl From<TokenType> for u32 { impl<'a> From<TokenType<'a>> for u32 {
fn from(value: TokenType) -> Self { fn from(value: TokenType) -> Self {
match value { match value {
TokenType::String(_) => 1, TokenType::String(_) => 1,
@@ -128,6 +355,7 @@ impl From<TokenType> for u32 {
| Keyword::Return => 4, | Keyword::Return => 4,
_ => 5, _ => 5,
}, },
TokenType::Comment(_) => 8,
TokenType::Identifier(s) => { TokenType::Identifier(s) => {
if is_syscall(&s) { if is_syscall(&s) {
10 10
@@ -146,12 +374,12 @@ impl From<TokenType> for u32 {
7 7
} }
} }
TokenType::EOF => 0, _ => 0,
} }
} }
} }
impl std::fmt::Display for TokenType { impl<'a> std::fmt::Display for TokenType<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
TokenType::String(s) => write!(f, "{}", s), TokenType::String(s) => write!(f, "{}", s),
@@ -160,7 +388,9 @@ impl std::fmt::Display for TokenType {
TokenType::Keyword(k) => write!(f, "{:?}", k), TokenType::Keyword(k) => write!(f, "{:?}", k),
TokenType::Identifier(i) => write!(f, "{}", i), TokenType::Identifier(i) => write!(f, "{}", i),
TokenType::Symbol(s) => write!(f, "{}", s), TokenType::Symbol(s) => write!(f, "{}", s),
TokenType::Comment(c) => write!(f, "{}", c),
TokenType::EOF => write!(f, "EOF"), TokenType::EOF => write!(f, "EOF"),
_ => write!(f, ""),
} }
} }
} }
@@ -306,6 +536,8 @@ pub enum Symbol {
Caret, Caret,
/// Represents the `%` symbol /// Represents the `%` symbol
Percent, Percent,
/// Represents the `?` symbol
Question,
// Double Character Symbols // Double Character Symbols
/// Represents the `==` symbol /// Represents the `==` symbol
@@ -372,6 +604,7 @@ impl std::fmt::Display for Symbol {
Self::Asterisk => write!(f, "*"), Self::Asterisk => write!(f, "*"),
Self::Slash => write!(f, "/"), Self::Slash => write!(f, "/"),
Self::LessThan => write!(f, "<"), Self::LessThan => write!(f, "<"),
Self::Question => write!(f, "?"),
Self::LessThanOrEqual => write!(f, "<="), Self::LessThanOrEqual => write!(f, "<="),
Self::GreaterThan => write!(f, ">"), Self::GreaterThan => write!(f, ">"),
Self::GreaterThanOrEqual => write!(f, ">="), Self::GreaterThanOrEqual => write!(f, ">="),

View File

@@ -1,6 +1,6 @@
use compiler::Compiler; use compiler::{CompilationResult, Compiler};
use helpers::Documentation; use helpers::Documentation;
use parser::{sys_call::SysCall, Parser}; use parser::{sys_call::SysCall, tree_node::Span, Parser};
use safer_ffi::prelude::*; use safer_ffi::prelude::*;
use std::io::BufWriter; use std::io::BufWriter;
use tokenizer::{ use tokenizer::{
@@ -8,6 +8,20 @@ use tokenizer::{
Tokenizer, Tokenizer,
}; };
#[derive_ReprC]
#[repr(C)]
pub struct FfiSourceMapEntry {
pub line_number: u32,
pub span: FfiRange,
}
#[derive_ReprC]
#[repr(C)]
pub struct FfiCompilationResult {
pub output_code: safer_ffi::String,
pub source_map: safer_ffi::Vec<FfiSourceMapEntry>,
}
#[derive_ReprC] #[derive_ReprC]
#[repr(C)] #[repr(C)]
pub struct FfiToken { pub struct FfiToken {
@@ -34,6 +48,17 @@ pub struct FfiDocumentedItem {
docs: safer_ffi::String, docs: safer_ffi::String,
} }
impl From<Span> for FfiRange {
fn from(value: Span) -> Self {
Self {
start_line: value.start_line as u32,
end_line: value.end_line as u32,
start_col: value.start_col as u32,
end_col: value.end_col as u32,
}
}
}
impl From<lsp_types::Range> for FfiRange { impl From<lsp_types::Range> for FfiRange {
fn from(value: lsp_types::Range) -> Self { fn from(value: lsp_types::Range) -> Self {
Self { Self {
@@ -69,6 +94,11 @@ impl From<lsp_types::Diagnostic> for FfiDiagnostic {
} }
} }
#[ffi_export]
pub fn free_ffi_compilation_result(input: FfiCompilationResult) {
drop(input)
}
#[ffi_export] #[ffi_export]
pub fn free_ffi_token_vec(v: safer_ffi::Vec<FfiToken>) { pub fn free_ffi_token_vec(v: safer_ffi::Vec<FfiToken>) {
drop(v) drop(v)
@@ -94,33 +124,61 @@ pub fn free_docs_vec(v: safer_ffi::Vec<FfiDocumentedItem>) {
/// This should result in the ability to compile many times without triggering frame drops /// 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#. /// from the GC from a `GetBytes()` call on a string in C#.
#[ffi_export] #[ffi_export]
pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::String { pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> FfiCompilationResult {
let res = std::panic::catch_unwind(|| { let res = std::panic::catch_unwind(|| {
let input = String::from_utf16_lossy(input.as_slice());
let mut writer = BufWriter::new(Vec::new()); let mut writer = BufWriter::new(Vec::new());
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice())); let tokenizer = Tokenizer::from(input.as_str());
let parser = Parser::new(tokenizer); let parser = Parser::new(tokenizer);
let compiler = Compiler::new(parser, &mut writer, None); let compiler = Compiler::new(parser, &mut writer, None);
if !compiler.compile().is_empty() { let res = compiler.compile();
return safer_ffi::String::EMPTY;
if !res.errors.is_empty() {
return (safer_ffi::String::EMPTY, res.source_map);
} }
let Ok(compiled_vec) = writer.into_inner() else { let Ok(compiled_vec) = writer.into_inner() else {
return safer_ffi::String::EMPTY; return (safer_ffi::String::EMPTY, res.source_map);
}; };
// Safety: I know the compiler only outputs valid utf8 // Safety: I know the compiler only outputs valid utf8
safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) }) (
safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) }),
res.source_map,
)
}); });
res.unwrap_or("".into()) if let Ok((res_str, source_map)) = res {
FfiCompilationResult {
source_map: source_map
.into_iter()
.flat_map(|(k, v)| {
v.into_iter()
.map(|span| FfiSourceMapEntry {
span: span.into(),
line_number: k as u32,
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
.into(),
output_code: res_str,
}
} else {
FfiCompilationResult {
output_code: "".into(),
source_map: vec![].into(),
}
}
} }
#[ffi_export] #[ffi_export]
pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiToken> { pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiToken> {
let res = std::panic::catch_unwind(|| { let res = std::panic::catch_unwind(|| {
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice())); let input = String::from_utf16_lossy(input.as_slice());
let tokenizer = Tokenizer::from(input.as_str());
let mut tokens = Vec::new(); let mut tokens = Vec::new();
@@ -136,34 +194,31 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
} }
match token { match token {
Err(ref e) => { Err(ref e) => {
use tokenizer::token::LexError;
use tokenizer::Error::*; use tokenizer::Error::*;
let (err_str, col, og) = match e { let (err_str, _, span) = match e {
NumberParseError(_, _, col, og) LexError(LexError::NumberParse(line, span, err))
| DecimalParseError(_, _, col, og) | LexError(LexError::InvalidInput(line, span, err)) => {
| UnknownSymbolError(_, _, col, og) (err.to_string(), line, span)
| UnknownKeywordOrIdentifierError(_, _, col, og) => {
(e.to_string(), col, og)
} }
_ => continue, _ => continue,
}; };
tokens.push(FfiToken { tokens.push(FfiToken {
column: *col as i32, column: span.start as i32,
error: err_str.into(), error: err_str.into(),
tooltip: "".into(), tooltip: "".into(),
length: og.len() as i32, length: (span.end - span.start) as i32,
token_kind: 0, token_kind: 0,
}) })
} }
Ok(Token { Ok(Token {
column, span, token_type, ..
original_string,
token_type,
..
}) => tokens.push(FfiToken { }) => tokens.push(FfiToken {
column: column as i32, column: span.start as i32,
error: "".into(), error: "".into(),
length: (original_string.unwrap_or_default().len()) as i32, length: (span.end - span.start) as i32,
tooltip: token_type.docs().into(), tooltip: token_type.docs().into(),
token_kind: token_type.into(), token_kind: token_type.into(),
}), }),
@@ -179,11 +234,15 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
#[ffi_export] #[ffi_export]
pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiDiagnostic> { pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiDiagnostic> {
let res = std::panic::catch_unwind(|| { let res = std::panic::catch_unwind(|| {
let input = String::from_utf16_lossy(input.as_slice());
let mut writer = BufWriter::new(Vec::new()); let mut writer = BufWriter::new(Vec::new());
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice())); let tokenizer = Tokenizer::from(input.as_str());
let compiler = Compiler::new(Parser::new(tokenizer), &mut writer, None); let compiler = Compiler::new(Parser::new(tokenizer), &mut writer, None);
let diagnosis = compiler.compile(); let CompilationResult {
errors: diagnosis, ..
} = compiler.compile();
let mut result_vec: Vec<FfiDiagnostic> = Vec::with_capacity(diagnosis.len()); let mut result_vec: Vec<FfiDiagnostic> = Vec::with_capacity(diagnosis.len());

View File

@@ -1,37 +1,46 @@
#![allow(clippy::result_large_err)] #![allow(clippy::result_large_err)]
#[macro_use]
extern crate quick_error;
use clap::Parser; use clap::Parser;
use compiler::Compiler; use compiler::{CompilationResult, Compiler};
use parser::Parser as ASTParser; use parser::Parser as ASTParser;
use std::{ use std::{
fs::File, fs::File,
io::{stderr, BufWriter, Read, Write}, io::{stderr, BufWriter, Read, Write},
path::PathBuf, path::PathBuf,
}; };
use thiserror::Error;
use tokenizer::{self, Tokenizer}; use tokenizer::{self, Tokenizer};
quick_error! { #[derive(Error, Debug)]
#[derive(Debug)] enum Error<'a> {
enum StationlangError { #[error(transparent)]
TokenizerError(err: tokenizer::Error) { Tokenizer(tokenizer::Error),
from()
display("Tokenizer error: {}", err) #[error(transparent)]
Parser(parser::Error<'a>),
#[error(transparent)]
Compile(compiler::Error<'a>),
#[error(transparent)]
IO(#[from] std::io::Error),
}
impl<'a> From<parser::Error<'a>> for Error<'a> {
fn from(value: parser::Error<'a>) -> Self {
Self::Parser(value)
} }
ParserError(err: parser::Error) { }
from()
display("Parser error: {}", err) impl<'a> From<compiler::Error<'a>> for Error<'a> {
} fn from(value: compiler::Error<'a>) -> Self {
CompileError(err: compiler::Error) { Self::Compile(value)
from()
display("Compile error: {}", err)
}
IoError(err: std::io::Error) {
from()
display("IO error: {}", err)
} }
}
impl<'a> From<tokenizer::Error> for Error<'a> {
fn from(value: tokenizer::Error) -> Self {
Self::Tokenizer(value)
} }
} }
@@ -46,12 +55,17 @@ struct Args {
output_file: Option<PathBuf>, output_file: Option<PathBuf>,
} }
fn run_logic() -> Result<(), StationlangError> { fn run_logic<'a>() -> Result<(), Error<'a>> {
let args = Args::parse(); let args = Args::parse();
let input_file = args.input_file; let input_file = args.input_file;
let tokenizer: Tokenizer = match input_file { let input_string = match input_file {
Some(input_file) => Tokenizer::from_path(&input_file)?, Some(input_path) => {
let mut buf = String::new();
let mut file = std::fs::File::open(input_path).unwrap();
file.read_to_string(&mut buf).unwrap();
buf
}
None => { None => {
let mut buf = String::new(); let mut buf = String::new();
let stdin = std::io::stdin(); let stdin = std::io::stdin();
@@ -62,10 +76,11 @@ fn run_logic() -> Result<(), StationlangError> {
return Ok(()); return Ok(());
} }
Tokenizer::from(buf) buf
} }
}; };
let tokenizer = Tokenizer::from(input_string.as_str());
let parser = ASTParser::new(tokenizer); let parser = ASTParser::new(tokenizer);
let mut writer: BufWriter<Box<dyn Write>> = match args.output_file { let mut writer: BufWriter<Box<dyn Write>> = match args.output_file {
@@ -75,20 +90,17 @@ fn run_logic() -> Result<(), StationlangError> {
let compiler = Compiler::new(parser, &mut writer, None); let compiler = Compiler::new(parser, &mut writer, None);
let mut errors = compiler.compile(); let CompilationResult { errors, .. } = compiler.compile();
if !errors.is_empty() { if !errors.is_empty() {
let mut std_error = stderr(); let mut std_error = stderr();
let last = errors.pop(); let errors = errors.into_iter().map(Error::from);
let errors = errors.into_iter().map(StationlangError::from);
std_error.write_all(b"Compilation error:\n")?; std_error.write_all(b"Compilation error:\n")?;
for err in errors { for err in errors {
std_error.write_all(format!("{}\n", err).as_bytes())?; std_error.write_all(format!("{}\n", err).as_bytes())?;
} }
return Err(StationlangError::from(last.unwrap()));
} }
writer.flush()?; writer.flush()?;
@@ -96,7 +108,7 @@ fn run_logic() -> Result<(), StationlangError> {
Ok(()) Ok(())
} }
fn main() -> Result<(), StationlangError> { fn main() -> anyhow::Result<()> {
run_logic()?; run_logic()?;
Ok(()) Ok(())