Merge pull request #11 from dbidwell94/dot-notation
Dot notation and bug improvements
This commit is contained in:
69
.github/workflows/build.yml
vendored
Normal file
69
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
|
# Trigger conditions
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["master"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# JOB 1: RUN TESTS
|
||||||
|
# This runs on every PR and every push to master.
|
||||||
|
# It validates that the Rust code is correct.
|
||||||
|
test:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# 1. Build the Docker Image (Cached)
|
||||||
|
- name: Build Docker Image
|
||||||
|
run: docker build -t slang-builder -f Dockerfile.build .
|
||||||
|
|
||||||
|
# 2. Run Rust Tests
|
||||||
|
# --manifest-path: Point to the nested Cargo.toml
|
||||||
|
# --workspace: Test all crates (compiler, parser, tokenizer, helpers)
|
||||||
|
# --all-targets: Test lib, bin, and tests folder (skips doc-tests because they are not valid Rust)
|
||||||
|
- name: Run Rust Tests
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
-u $(id -u):$(id -g) \
|
||||||
|
-v "$PWD":/app \
|
||||||
|
slang-builder \
|
||||||
|
cargo test --manifest-path rust_compiler/Cargo.toml --workspace --all-targets
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: test
|
||||||
|
runs-on: self-hosted
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# 1. Build Image (Fast, uses cache from previous job if available/local)
|
||||||
|
- name: Build Docker Image
|
||||||
|
run: docker build -t slang-builder -f Dockerfile.build .
|
||||||
|
|
||||||
|
# 2. Run the Build Script
|
||||||
|
# Mounts the references from the server's secure storage
|
||||||
|
- name: Build Release Artifacts
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
-u $(id -u):$(id -g) \
|
||||||
|
-v "$PWD":/app \
|
||||||
|
-v "/home/github-runner/permanent-refs":/app/csharp_mod/refs \
|
||||||
|
slang-builder \
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
# 3. Fix Permissions
|
||||||
|
# Docker writes files as root. We need to own them to upload them.
|
||||||
|
- name: Fix Permissions
|
||||||
|
if: always()
|
||||||
|
run: sudo chown -R $USER:$USER release/
|
||||||
|
|
||||||
|
# 4. Upload to GitHub
|
||||||
|
- name: Upload Release Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: StationeersSlang-Release
|
||||||
|
path: release/
|
||||||
19
Dockerfile.build
Normal file
19
Dockerfile.build
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM rust:latest
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
mingw-w64 \
|
||||||
|
wget \
|
||||||
|
apt-transport-https \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN wget --progress=dot:giga https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \
|
||||||
|
&& chmod +x ./dotnet-install.sh \
|
||||||
|
&& ./dotnet-install.sh --channel 8.0 --install-dir /usr/share/dotnet \
|
||||||
|
&& ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet
|
||||||
|
|
||||||
|
RUN rustup target add x86_64-pc-windows-gnu && rustup target add x86_64-unknown-linux-gnu
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# The command will be provided at runtime
|
||||||
|
CMD ["./build.sh"]
|
||||||
2
build.sh
2
build.sh
@@ -39,7 +39,6 @@ echo "--------------------"
|
|||||||
RUST_WIN_EXE="$RUST_DIR/target/x86_64-pc-windows-gnu/release/slang.exe"
|
RUST_WIN_EXE="$RUST_DIR/target/x86_64-pc-windows-gnu/release/slang.exe"
|
||||||
RUST_LINUX_BIN="$RUST_DIR/target/x86_64-unknown-linux-gnu/release/slang"
|
RUST_LINUX_BIN="$RUST_DIR/target/x86_64-unknown-linux-gnu/release/slang"
|
||||||
CHARP_DLL="$CSHARP_DIR/bin/Release/net48/StationeersSlang.dll"
|
CHARP_DLL="$CSHARP_DIR/bin/Release/net48/StationeersSlang.dll"
|
||||||
CHARP_PDB="$CSHARP_DIR/bin/Release/net48/StationeersSlang.pdb"
|
|
||||||
|
|
||||||
# Check if the release dir exists, if not: create it.
|
# Check if the release dir exists, if not: create it.
|
||||||
if [[ ! -d "$RELEASE_DIR" ]]; then
|
if [[ ! -d "$RELEASE_DIR" ]]; then
|
||||||
@@ -49,4 +48,3 @@ fi
|
|||||||
cp "$RUST_WIN_EXE" "$RELEASE_DIR/slang.exe"
|
cp "$RUST_WIN_EXE" "$RELEASE_DIR/slang.exe"
|
||||||
cp "$RUST_LINUX_BIN" "$RELEASE_DIR/slang"
|
cp "$RUST_LINUX_BIN" "$RELEASE_DIR/slang"
|
||||||
cp "$CHARP_DLL" "$RELEASE_DIR/StationeersSlang.dll"
|
cp "$CHARP_DLL" "$RELEASE_DIR/StationeersSlang.dll"
|
||||||
cp "$CHARP_PDB" "$RELEASE_DIR/StationeersSlang.pdb"
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ namespace Slang;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Assets.Scripts.UI;
|
||||||
using StationeersIC10Editor;
|
using StationeersIC10Editor;
|
||||||
|
|
||||||
public static unsafe class SlangExtensions
|
public static unsafe class SlangExtensions
|
||||||
@@ -63,7 +64,9 @@ public static unsafe class SlangExtensions
|
|||||||
colIndex,
|
colIndex,
|
||||||
token.length,
|
token.length,
|
||||||
color,
|
color,
|
||||||
token.token_kind
|
token.token_kind,
|
||||||
|
0,
|
||||||
|
token.tooltip.AsString()
|
||||||
);
|
);
|
||||||
|
|
||||||
string errMsg = token.error.AsString();
|
string errMsg = token.error.AsString();
|
||||||
@@ -115,20 +118,63 @@ public static unsafe class SlangExtensions
|
|||||||
{
|
{
|
||||||
switch (kind)
|
switch (kind)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1: // Strings
|
||||||
return SlangFormatter.ColorString; // String
|
return SlangFormatter.ColorString;
|
||||||
case 2:
|
case 2: // Numbers
|
||||||
return SlangFormatter.ColorString; // Number
|
return SlangFormatter.ColorNumber;
|
||||||
case 3:
|
case 3: // Booleans
|
||||||
return SlangFormatter.ColorInstruction; // Boolean
|
return SlangFormatter.ColorBoolean;
|
||||||
case 4:
|
|
||||||
return SlangFormatter.ColorSelection; // Keyword
|
case 4: // (if, else, loop)
|
||||||
case 5:
|
return SlangFormatter.ColorControl;
|
||||||
return SlangFormatter.ColorLineNumber; // Identifier
|
case 5: // (let, const, device)
|
||||||
case 6:
|
return SlangFormatter.ColorDeclaration;
|
||||||
return SlangFormatter.ColorDefault; // Symbol
|
|
||||||
|
case 6: // (variables)
|
||||||
|
return SlangFormatter.ColorIdentifier;
|
||||||
|
case 7: // (punctuation)
|
||||||
|
return SlangFormatter.ColorDefault;
|
||||||
|
|
||||||
|
case 10: // (syscalls)
|
||||||
|
return SlangFormatter.ColorFunction;
|
||||||
|
|
||||||
|
case 11: // Comparisons
|
||||||
|
case 12: // Math
|
||||||
|
case 13: // Logic
|
||||||
|
return SlangFormatter.ColorOperator;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return SlangFormatter.ColorDefault;
|
return SlangFormatter.ColorDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static unsafe List<StationpediaPage> ToList(this Vec_FfiDocumentedItem_t vec)
|
||||||
|
{
|
||||||
|
var toReturn = new List<StationpediaPage>((int)vec.len);
|
||||||
|
|
||||||
|
var currentPtr = vec.ptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)vec.len; i++)
|
||||||
|
{
|
||||||
|
var doc = currentPtr[i];
|
||||||
|
var docItemName = doc.item_name.AsString();
|
||||||
|
|
||||||
|
var formattedText = TextMeshProFormatter.FromMarkdown(doc.docs.AsString());
|
||||||
|
|
||||||
|
var pediaPage = new StationpediaPage(
|
||||||
|
$"slang-item-{docItemName}",
|
||||||
|
docItemName,
|
||||||
|
formattedText
|
||||||
|
);
|
||||||
|
|
||||||
|
pediaPage.Text = formattedText;
|
||||||
|
pediaPage.Description = formattedText;
|
||||||
|
pediaPage.ParsePage();
|
||||||
|
|
||||||
|
toReturn.Add(pediaPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ffi.free_docs_vec(vec);
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,31 @@ public unsafe partial class Ffi {
|
|||||||
slice_ref_uint16_t input);
|
slice_ref_uint16_t input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 48)]
|
||||||
|
public unsafe struct FfiDocumentedItem_t {
|
||||||
|
public Vec_uint8_t item_name;
|
||||||
|
|
||||||
|
public Vec_uint8_t docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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_FfiDocumentedItem_t {
|
||||||
|
public FfiDocumentedItem_t * ptr;
|
||||||
|
|
||||||
|
public UIntPtr len;
|
||||||
|
|
||||||
|
public UIntPtr cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe partial class Ffi {
|
||||||
|
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
|
||||||
|
void free_docs_vec (
|
||||||
|
Vec_FfiDocumentedItem_t v);
|
||||||
|
}
|
||||||
|
|
||||||
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 (
|
||||||
@@ -164,6 +189,11 @@ public unsafe partial class Ffi {
|
|||||||
Vec_uint8_t s);
|
Vec_uint8_t s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe partial class Ffi {
|
||||||
|
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
|
||||||
|
Vec_FfiDocumentedItem_t get_docs ();
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
Vec_FfiToken_t tokenize_line (
|
Vec_FfiToken_t tokenize_line (
|
||||||
|
|||||||
@@ -4,26 +4,65 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
using StationeersIC10Editor;
|
using StationeersIC10Editor;
|
||||||
|
|
||||||
public class SlangFormatter : ICodeFormatter
|
public class SlangFormatter : ICodeFormatter
|
||||||
{
|
{
|
||||||
private CancellationTokenSource? _lspCancellationToken;
|
private CancellationTokenSource? _lspCancellationToken;
|
||||||
private readonly SynchronizationContext? _mainThreadContext;
|
private object _tokenLock = new();
|
||||||
private volatile bool IsDiagnosing = false;
|
|
||||||
|
|
||||||
public static readonly uint ColorInstruction = ColorFromHTML("#ffff00");
|
// VS Code Dark Theme Palette
|
||||||
public static readonly uint ColorString = ColorFromHTML("#ce9178");
|
public static readonly uint ColorControl = ColorFromHTML("#C586C0"); // Pink (if, return, loop)
|
||||||
|
public static readonly uint ColorDeclaration = ColorFromHTML("#569CD6"); // Blue (let, device, fn)
|
||||||
|
public static readonly uint ColorFunction = ColorFromHTML("#DCDCAA"); // Yellow (syscalls)
|
||||||
|
public static readonly uint ColorString = ColorFromHTML("#CE9178"); // Orange
|
||||||
|
public static new readonly uint ColorNumber = ColorFromHTML("#B5CEA8"); // Light Green
|
||||||
|
public static readonly uint ColorBoolean = ColorFromHTML("#569CD6"); // Blue (true/false)
|
||||||
|
public static readonly uint ColorIdentifier = ColorFromHTML("#9CDCFE"); // Light Blue (variables)
|
||||||
|
public static new readonly uint ColorDefault = ColorFromHTML("#D4D4D4"); // White (punctuation ; { } )
|
||||||
|
|
||||||
|
// Operators are often the same color as default text in VS Code Dark,
|
||||||
|
// but having a separate definition lets you tweak it (e.g. make them slightly darker or distinct)
|
||||||
|
public static readonly uint ColorOperator = ColorFromHTML("#D4D4D4");
|
||||||
|
|
||||||
private HashSet<uint> _linesWithErrors = new();
|
private HashSet<uint> _linesWithErrors = new();
|
||||||
|
|
||||||
public SlangFormatter()
|
public SlangFormatter()
|
||||||
|
: base()
|
||||||
{
|
{
|
||||||
// 1. Capture the Main Thread context.
|
OnCodeChanged += HandleCodeChanged;
|
||||||
// This works because the Editor instantiates this class on the main thread.
|
}
|
||||||
_mainThreadContext = SynchronizationContext.Current;
|
|
||||||
|
public static double MatchingScore(string input)
|
||||||
|
{
|
||||||
|
// Empty input is not valid Slang
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
return 0d;
|
||||||
|
|
||||||
|
// Run the compiler to get diagnostics
|
||||||
|
var diagnostics = Marshal.DiagnoseSource(input);
|
||||||
|
|
||||||
|
// Count the number of actual Errors (Severity 1).
|
||||||
|
// We ignore Warnings (2), Info (3), etc.
|
||||||
|
double errorCount = diagnostics.Count(d => d.Severity == 1);
|
||||||
|
|
||||||
|
// Get the total line count to calculate error density
|
||||||
|
double lineCount = input.Split('\n').Length;
|
||||||
|
|
||||||
|
// Prevent division by zero
|
||||||
|
if (lineCount == 0)
|
||||||
|
return 0d;
|
||||||
|
|
||||||
|
// Calculate score: Start at 1.0 (100%) and subtract the ratio of errors per line.
|
||||||
|
// Example: 10 lines with 0 errors = 1.0
|
||||||
|
// Example: 10 lines with 2 errors = 0.8
|
||||||
|
// Example: 10 lines with 10+ errors = 0.0
|
||||||
|
double score = 1.0d - (errorCount / lineCount);
|
||||||
|
|
||||||
|
// Clamp the result between 0 and 1
|
||||||
|
return Math.Max(0d, Math.Min(1d, score));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Compile()
|
public override string Compile()
|
||||||
@@ -33,65 +72,62 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
|
|
||||||
public override Line ParseLine(string line)
|
public override Line ParseLine(string line)
|
||||||
{
|
{
|
||||||
HandleCodeChanged();
|
|
||||||
return Marshal.TokenizeLine(line);
|
return Marshal.TokenizeLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCodeChanged()
|
private void HandleCodeChanged()
|
||||||
{
|
{
|
||||||
if (IsDiagnosing)
|
CancellationToken token;
|
||||||
return;
|
string inputSrc;
|
||||||
|
lock (_tokenLock)
|
||||||
|
{
|
||||||
|
_lspCancellationToken?.Cancel();
|
||||||
|
_lspCancellationToken = new CancellationTokenSource();
|
||||||
|
token = _lspCancellationToken.Token;
|
||||||
|
inputSrc = this.RawText;
|
||||||
|
}
|
||||||
|
|
||||||
_lspCancellationToken?.Cancel();
|
HandleLsp(inputSrc, token).Forget();
|
||||||
_lspCancellationToken?.Dispose();
|
|
||||||
|
|
||||||
_lspCancellationToken = new CancellationTokenSource();
|
|
||||||
|
|
||||||
_ = Task.Run(() => HandleLsp(_lspCancellationToken.Token), _lspCancellationToken.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimerElapsed(object sender, ElapsedEventArgs e) { }
|
private void OnTimerElapsed(object sender, ElapsedEventArgs e) { }
|
||||||
|
|
||||||
private async Task HandleLsp(CancellationToken cancellationToken)
|
private async UniTaskVoid HandleLsp(string inputSrc, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(200, cancellationToken);
|
await UniTask.SwitchToThreadPool();
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Dispatch the UI update to the Main Thread
|
await System.Threading.Tasks.Task.Delay(200, cancellationToken: cancellationToken);
|
||||||
if (_mainThreadContext != null)
|
|
||||||
{
|
if (cancellationToken.IsCancellationRequested)
|
||||||
// Post ensures ApplyDiagnostics runs on the captured thread (Main Thread)
|
return;
|
||||||
_mainThreadContext.Post(_ => ApplyDiagnostics(), null);
|
|
||||||
}
|
var dict = Marshal
|
||||||
else
|
.DiagnoseSource(inputSrc)
|
||||||
{
|
.GroupBy(d => d.Range.StartLine)
|
||||||
// Fallback: If context is null (rare in Unity), try running directly
|
.ToDictionary(g => g.Key);
|
||||||
// but warn, as this might crash if not thread-safe.
|
|
||||||
L.Warning("SynchronizationContext was null. Attempting direct update (risky).");
|
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken);
|
||||||
ApplyDiagnostics();
|
|
||||||
}
|
ApplyDiagnostics(dict);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
L.Error(ex.Message);
|
||||||
}
|
}
|
||||||
finally { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This runs on the Main Thread
|
// This runs on the Main Thread
|
||||||
private void ApplyDiagnostics()
|
private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict)
|
||||||
{
|
{
|
||||||
List<Diagnostic> diagnosis = Marshal.DiagnoseSource(this.RawText);
|
|
||||||
|
|
||||||
var dict = diagnosis.GroupBy(d => d.Range.StartLine).ToDictionary(g => g.Key);
|
|
||||||
|
|
||||||
var linesToRefresh = new HashSet<uint>(dict.Keys);
|
var linesToRefresh = new HashSet<uint>(dict.Keys);
|
||||||
linesToRefresh.UnionWith(_linesWithErrors);
|
linesToRefresh.UnionWith(_linesWithErrors);
|
||||||
|
|
||||||
IsDiagnosing = true;
|
|
||||||
|
|
||||||
foreach (var lineIndex in linesToRefresh)
|
foreach (var lineIndex in linesToRefresh)
|
||||||
{
|
{
|
||||||
// safety check for out of bounds (in case lines were deleted)
|
// safety check for out of bounds (in case lines were deleted)
|
||||||
@@ -134,7 +170,5 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
_linesWithErrors = new HashSet<uint>(dict.Keys);
|
_linesWithErrors = new HashSet<uint>(dict.Keys);
|
||||||
|
|
||||||
IsDiagnosing = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Assets.Scripts.UI;
|
||||||
using StationeersIC10Editor;
|
using StationeersIC10Editor;
|
||||||
|
|
||||||
public struct Range
|
public struct Range
|
||||||
@@ -151,6 +152,14 @@ public static class Marshal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently documented items from the Slang compiler and returns new StationpediaPages with correct formatting.
|
||||||
|
/// </summary>
|
||||||
|
public static unsafe List<StationpediaPage> GetSlangDocs()
|
||||||
|
{
|
||||||
|
return Ffi.get_docs().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private static string ExtractNativeLibrary(string libName)
|
private static string ExtractNativeLibrary(string libName)
|
||||||
{
|
{
|
||||||
string destinationPath = Path.Combine(Path.GetTempPath(), libName);
|
string destinationPath = Path.Combine(Path.GetTempPath(), libName);
|
||||||
|
|||||||
@@ -214,7 +214,6 @@ public static class SlangPatches
|
|||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
public static void isc_ButtonInputCancel()
|
public static void isc_ButtonInputCancel()
|
||||||
{
|
{
|
||||||
L.Info("ButtonInputCancel called on the InputSourceCode static instance.");
|
|
||||||
if (_currentlyEditingMotherboard is null || _motherboardCachedCode is null)
|
if (_currentlyEditingMotherboard is null || _motherboardCachedCode is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -225,4 +224,14 @@ public static class SlangPatches
|
|||||||
_currentlyEditingMotherboard = null;
|
_currentlyEditingMotherboard = null;
|
||||||
_motherboardCachedCode = null;
|
_motherboardCachedCode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))]
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void Stationpedia_Regenerate()
|
||||||
|
{
|
||||||
|
foreach (var page in Marshal.GetSlangDocs())
|
||||||
|
{
|
||||||
|
Stationpedia.Register(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,15 @@ namespace Slang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[BepInPlugin(PluginGuid, PluginName, "0.1.0")]
|
[BepInPlugin(PluginGuid, PluginName, PluginVersion)]
|
||||||
[BepInDependency(StationeersIC10Editor.IC10EditorPlugin.PluginGuid)]
|
[BepInDependency(StationeersIC10Editor.IC10EditorPlugin.PluginGuid)]
|
||||||
public class SlangPlugin : BaseUnityPlugin
|
public class SlangPlugin : BaseUnityPlugin
|
||||||
{
|
{
|
||||||
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.0";
|
||||||
|
|
||||||
public static Mod MOD = new Mod(PluginName, "0.1.0");
|
public static Mod MOD = new Mod(PluginName, PluginVersion);
|
||||||
|
|
||||||
private Harmony? _harmony;
|
private Harmony? _harmony;
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using Assets.Scripts.UI;
|
|
||||||
|
|
||||||
namespace Slang
|
|
||||||
{
|
|
||||||
public static class SlangDocs
|
|
||||||
{
|
|
||||||
public static StationpediaPage[] Pages
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return
|
|
||||||
[
|
|
||||||
new StationpediaPage(
|
|
||||||
"slang-init",
|
|
||||||
"Slang",
|
|
||||||
"Slang is a new high level language built specifically for Stationeers"
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
csharp_mod/TmpFormatter.cs
Normal file
54
csharp_mod/TmpFormatter.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Slang;
|
||||||
|
|
||||||
|
public static class TextMeshProFormatter
|
||||||
|
{
|
||||||
|
private const string CODE_COLOR = "#FFD700";
|
||||||
|
|
||||||
|
public static string FromMarkdown(string markdown)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(markdown))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// 1. Normalize Line Endings
|
||||||
|
string text = markdown.Replace("\r\n", "\n");
|
||||||
|
|
||||||
|
// 2. Handle Code Blocks (```)
|
||||||
|
text = Regex.Replace(
|
||||||
|
text,
|
||||||
|
@"```\s*(.*?)\s*```",
|
||||||
|
match =>
|
||||||
|
{
|
||||||
|
var codeContent = match.Groups[1].Value;
|
||||||
|
return $"<color={CODE_COLOR}>{codeContent}</color>"; // Gold color for code
|
||||||
|
},
|
||||||
|
RegexOptions.Singleline
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Handle Headers (## Header)
|
||||||
|
// Convert ## Header to large bold text
|
||||||
|
text = Regex.Replace(
|
||||||
|
text,
|
||||||
|
@"^##(\s+)?(.+)$",
|
||||||
|
"<size=120%><b>$1</b></size>",
|
||||||
|
RegexOptions.Multiline
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Handle Inline Code (`code`)
|
||||||
|
text = Regex.Replace(text, @"`([^`]+)`", $"<color={CODE_COLOR}>$1</color>");
|
||||||
|
|
||||||
|
// 5. Handle Bold (**text**)
|
||||||
|
text = Regex.Replace(text, @"\*\*(.+?)\*\*", "<b>$1</b>");
|
||||||
|
|
||||||
|
// 6. Handle Italics (*text*)
|
||||||
|
text = Regex.Replace(text, @"\*(.+?)\*", "<i>$1</i>");
|
||||||
|
|
||||||
|
// 7. Convert Newlines to TMP Line Breaks
|
||||||
|
// Stationpedia needs <br> or explicit newlines.
|
||||||
|
// Often just ensuring \n is preserved is enough, but <br> is safer for HTML-like parsers.
|
||||||
|
text = text.Replace("\n", "<br>");
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,34 +11,36 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ManagedDir>$(STATIONEERS_DIR)/rocketstation_Data/Managed</ManagedDir>
|
|
||||||
<BepInExDir>$(STATIONEERS_DIR)/BepInEx/core</BepInExDir>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="netstandard">
|
<Reference Include="netstandard">
|
||||||
<HintPath>$(ManagedDir)/netstandard.dll</HintPath>
|
<HintPath>./ref/netstandard.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="BepInEx">
|
<Reference Include="BepInEx">
|
||||||
<HintPath>$(BepInExDir)/BepInEx.dll</HintPath>
|
<HintPath>./ref/BepInEx.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="0Harmony">
|
<Reference Include="0Harmony">
|
||||||
<HintPath>$(BepInExDir)/0Harmony.dll</HintPath>
|
<HintPath>./ref/0Harmony.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
||||||
<Reference Include="UnityEngine">
|
<Reference Include="UnityEngine">
|
||||||
<HintPath>$(ManagedDir)/UnityEngine.dll</HintPath>
|
<HintPath>./ref/UnityEngine.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="UnityEngine.CoreModule">
|
<Reference Include="UnityEngine.CoreModule">
|
||||||
<HintPath>$(ManagedDir)/UnityEngine.CoreModule.dll</HintPath>
|
<HintPath>./ref/UnityEngine.CoreModule.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Assembly-CSharp">
|
<Reference Include="Assembly-CSharp">
|
||||||
<HintPath>$(ManagedDir)/Assembly-CSharp.dll</HintPath>
|
<HintPath>./ref/Assembly-CSharp.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UniTask">
|
||||||
|
<HintPath>./ref/UniTask.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
||||||
@@ -47,11 +49,11 @@
|
|||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="LaunchPadBooster.dll">
|
<Reference Include="LaunchPadBooster.dll">
|
||||||
<HintPath>$(STATIONEERS_DIR)/BepInEx/plugins/StationeersLaunchPad/LaunchPadBooster.dll</HintPath>
|
<HintPath>./ref/LaunchPadBooster.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="StationeersMods.Interface.dll">
|
<Reference Include="StationeersMods.Interface.dll">
|
||||||
<HintPath>$(STATIONEERS_DIR)/BepInEx/plugins/StationeersLaunchPad/StationeersMods.Interface.dll</HintPath>
|
<HintPath>./ref/StationeersMods.Interface.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
16
rust_compiler/Cargo.lock
generated
16
rust_compiler/Cargo.lock
generated
@@ -360,6 +360,10 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.12.1"
|
version = "2.12.1"
|
||||||
@@ -412,9 +416,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.177"
|
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 = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp-types"
|
name = "lsp-types"
|
||||||
@@ -495,7 +499,9 @@ name = "parser"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"helpers",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
|
"pretty_assertions",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"tokenizer",
|
"tokenizer",
|
||||||
]
|
]
|
||||||
@@ -828,6 +834,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"compiler",
|
"compiler",
|
||||||
|
"helpers",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"parser",
|
"parser",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
@@ -925,6 +932,7 @@ name = "tokenizer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"helpers",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
@@ -989,9 +997,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.18.1"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ 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" }
|
||||||
safer-ffi = { workspace = true }
|
safer-ffi = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
|
|||||||
sub r0 sp 1
|
sub r0 sp 1
|
||||||
put db r0 99 #h
|
put db r0 99 #h
|
||||||
L1:
|
L1:
|
||||||
|
sub sp sp 1
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ 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
|
||||||
"
|
"
|
||||||
@@ -64,6 +66,7 @@ fn let_var_args() -> anyhow::Result<()> {
|
|||||||
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
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -123,6 +126,7 @@ 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
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -164,6 +168,7 @@ 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
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()>
|
|||||||
push 7 #h
|
push 7 #h
|
||||||
push 8 #i
|
push 8 #i
|
||||||
push 9 #j
|
push 9 #j
|
||||||
|
sub sp sp 3
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ use parser::{
|
|||||||
Parser as ASTParser,
|
Parser as ASTParser,
|
||||||
sys_call::{SysCall, System},
|
sys_call::{SysCall, System},
|
||||||
tree_node::{
|
tree_node::{
|
||||||
AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression,
|
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
||||||
Expression, FunctionExpression, IfExpression, InvocationExpression, Literal,
|
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
||||||
LiteralOrVariable, LogicalExpression, LoopExpression, Span, Spanned, WhileExpression,
|
InvocationExpression, Literal, LiteralOrVariable, LogicalExpression, LoopExpression,
|
||||||
|
MemberAccessExpression, Span, Spanned, WhileExpression,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use quick_error::quick_error;
|
use quick_error::quick_error;
|
||||||
@@ -33,6 +34,20 @@ macro_rules! debug {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_literal(literal: Literal, allow_strings: bool) -> Result<String, Error> {
|
||||||
|
if !allow_strings && matches!(literal, Literal::String(_)) {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
"Literal strings are not allowed in this context".to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(match literal {
|
||||||
|
Literal::String(s) => s,
|
||||||
|
Literal::Number(n) => n.to_string(),
|
||||||
|
Literal::Boolean(b) => if b { "1" } else { "0" }.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
quick_error! {
|
quick_error! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@@ -57,6 +72,9 @@ quick_error! {
|
|||||||
AgrumentMismatch(func_name: String, span: Span) {
|
AgrumentMismatch(func_name: String, span: Span) {
|
||||||
display("Incorrect number of arguments passed into `{func_name}`")
|
display("Incorrect number of arguments passed into `{func_name}`")
|
||||||
}
|
}
|
||||||
|
ConstAssignment(ident: String, span: Span) {
|
||||||
|
display("Attempted to re-assign a value to const variable `{ident}`")
|
||||||
|
}
|
||||||
Unknown(reason: String, span: Option<Span>) {
|
Unknown(reason: String, span: Option<Span>) {
|
||||||
display("{reason}")
|
display("{reason}")
|
||||||
}
|
}
|
||||||
@@ -83,6 +101,7 @@ impl From<Error> for lsp_types::Diagnostic {
|
|||||||
DuplicateIdentifier(_, span)
|
DuplicateIdentifier(_, span)
|
||||||
| UnknownIdentifier(_, span)
|
| UnknownIdentifier(_, span)
|
||||||
| InvalidDevice(_, span)
|
| InvalidDevice(_, span)
|
||||||
|
| ConstAssignment(_, span)
|
||||||
| AgrumentMismatch(_, span) => Diagnostic {
|
| AgrumentMismatch(_, span) => Diagnostic {
|
||||||
range: span.into(),
|
range: span.into(),
|
||||||
message: value.to_string(),
|
message: value.to_string(),
|
||||||
@@ -266,6 +285,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
// decl_expr is Box<Spanned<Expression>>
|
// decl_expr is Box<Spanned<Expression>>
|
||||||
self.expression_declaration(var_name, *decl_expr, scope)
|
self.expression_declaration(var_name, *decl_expr, scope)
|
||||||
}
|
}
|
||||||
|
Expression::ConstDeclaration(const_decl_expr) => {
|
||||||
|
self.expression_const_declaration(const_decl_expr.node, scope)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
Expression::Assignment(assign_expr) => {
|
Expression::Assignment(assign_expr) => {
|
||||||
self.expression_assignment(assign_expr.node, scope)?;
|
self.expression_assignment(assign_expr.node, scope)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -332,6 +355,42 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expression::MemberAccess(access) => {
|
||||||
|
// "load" behavior (e.g. `let x = d0.On`)
|
||||||
|
let MemberAccessExpression { object, member } = access.node;
|
||||||
|
|
||||||
|
// 1. Resolve the object to a device string (e.g., "d0" or "rX")
|
||||||
|
let (device_str, cleanup) = self.resolve_device(*object, scope)?;
|
||||||
|
|
||||||
|
// 2. Allocate a temp register for the result
|
||||||
|
let result_name = self.next_temp_name();
|
||||||
|
let loc = scope.add_variable(&result_name, LocationRequest::Temp)?;
|
||||||
|
let reg = self.resolve_register(&loc)?;
|
||||||
|
|
||||||
|
// 3. Emit load instruction: l rX device member
|
||||||
|
self.write_output(format!("l {} {} {}", reg, device_str, member.node))?;
|
||||||
|
|
||||||
|
// 4. Cleanup
|
||||||
|
if let Some(c) = cleanup {
|
||||||
|
scope.free_temp(c)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(CompilationResult {
|
||||||
|
location: loc,
|
||||||
|
temp_name: Some(result_name),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Expression::MethodCall(call) => {
|
||||||
|
// Methods are not yet fully supported (e.g. `d0.SomeFunc()`).
|
||||||
|
// This would likely map to specialized syscalls or batch instructions.
|
||||||
|
Err(Error::Unknown(
|
||||||
|
format!(
|
||||||
|
"Method calls are not yet supported: {}",
|
||||||
|
call.node.method.node
|
||||||
|
),
|
||||||
|
Some(call.span),
|
||||||
|
))
|
||||||
|
}
|
||||||
Expression::Priority(inner_expr) => self.expression(*inner_expr, scope),
|
Expression::Priority(inner_expr) => self.expression(*inner_expr, scope),
|
||||||
Expression::Negation(inner_expr) => {
|
Expression::Negation(inner_expr) => {
|
||||||
// Compile negation as 0 - inner
|
// Compile negation as 0 - inner
|
||||||
@@ -361,6 +420,24 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves an expression to a device identifier string for use in instructions like `s` or `l`.
|
||||||
|
/// Returns (device_string, optional_cleanup_temp_name).
|
||||||
|
fn resolve_device<'v>(
|
||||||
|
&mut self,
|
||||||
|
expr: Spanned<Expression>,
|
||||||
|
scope: &mut VariableScope<'v>,
|
||||||
|
) -> Result<(String, Option<String>), Error> {
|
||||||
|
// If it's a direct variable reference, check if it's a known device alias first
|
||||||
|
if let Expression::Variable(ref name) = expr.node
|
||||||
|
&& let Some(device_id) = self.devices.get(&name.node)
|
||||||
|
{
|
||||||
|
return Ok((device_id.clone(), None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, compile it as an operand (e.g. it might be a register holding a device hash/id)
|
||||||
|
self.compile_operand(expr, scope)
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_variable_assignment(
|
fn emit_variable_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
var_name: &str,
|
var_name: &str,
|
||||||
@@ -380,6 +457,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
VariableLocation::Stack(_) => {
|
VariableLocation::Stack(_) => {
|
||||||
self.write_output(format!("push {}{debug_tag}", source_value.into()))?;
|
self.write_output(format!("push {}{debug_tag}", source_value.into()))?;
|
||||||
}
|
}
|
||||||
|
VariableLocation::Constant(_) => {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
r#"Attempted to emit a variable assignent for a constant value.
|
||||||
|
This is a Compiler bug and should be reported to the developer."#
|
||||||
|
.into(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -526,6 +611,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
))?;
|
))?;
|
||||||
format!("r{}", VariableScope::TEMP_STACK_REGISTER)
|
format!("r{}", VariableScope::TEMP_STACK_REGISTER)
|
||||||
}
|
}
|
||||||
|
VariableLocation::Constant(_) => unreachable!(),
|
||||||
};
|
};
|
||||||
self.emit_variable_assignment(&name_str, &var_loc, src_str)?;
|
self.emit_variable_assignment(&name_str, &var_loc, src_str)?;
|
||||||
(var_loc, None)
|
(var_loc, None)
|
||||||
@@ -540,6 +626,35 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
scope,
|
scope,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Expression::MemberAccess(access) => {
|
||||||
|
// Compile the member access (load instruction)
|
||||||
|
let result = self.expression(
|
||||||
|
Spanned {
|
||||||
|
node: Expression::MemberAccess(access),
|
||||||
|
span: name_span, // Use declaration span roughly
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Result is in a temp register
|
||||||
|
let Some(comp_res) = result else {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
"Member access did not return a value".into(),
|
||||||
|
Some(name_span),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?;
|
||||||
|
let result_reg = self.resolve_register(&comp_res.location)?;
|
||||||
|
|
||||||
|
self.emit_variable_assignment(&name_str, &var_loc, result_reg)?;
|
||||||
|
|
||||||
|
if let Some(temp) = comp_res.temp_name {
|
||||||
|
scope.free_temp(temp)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
(var_loc, None)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
format!("`{name_str}` declaration of this type is not supported/implemented."),
|
format!("`{name_str}` declaration of this type is not supported/implemented."),
|
||||||
@@ -554,55 +669,101 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expression_const_declaration<'v>(
|
||||||
|
&mut self,
|
||||||
|
expr: ConstDeclarationExpression,
|
||||||
|
scope: &mut VariableScope<'v>,
|
||||||
|
) -> Result<CompilationResult, Error> {
|
||||||
|
let ConstDeclarationExpression {
|
||||||
|
name: const_name,
|
||||||
|
value: const_value,
|
||||||
|
} = expr;
|
||||||
|
|
||||||
|
Ok(CompilationResult {
|
||||||
|
location: scope.define_const(const_name.node, const_value.node)?,
|
||||||
|
temp_name: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn expression_assignment<'v>(
|
fn expression_assignment<'v>(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: AssignmentExpression,
|
expr: AssignmentExpression,
|
||||||
scope: &mut VariableScope<'v>,
|
scope: &mut VariableScope<'v>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let AssignmentExpression {
|
let AssignmentExpression {
|
||||||
identifier,
|
assignee,
|
||||||
expression,
|
expression,
|
||||||
} = expr;
|
} = expr;
|
||||||
|
|
||||||
let location = match scope.get_location_of(&identifier.node) {
|
match assignee.node {
|
||||||
Ok(l) => l,
|
Expression::Variable(identifier) => {
|
||||||
Err(_) => {
|
let location = match scope.get_location_of(&identifier.node) {
|
||||||
self.errors.push(Error::UnknownIdentifier(
|
Ok(l) => l,
|
||||||
identifier.node.clone(),
|
Err(_) => {
|
||||||
identifier.span,
|
self.errors.push(Error::UnknownIdentifier(
|
||||||
|
identifier.node.clone(),
|
||||||
|
identifier.span,
|
||||||
|
));
|
||||||
|
VariableLocation::Temporary(0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (val_str, cleanup) = self.compile_operand(*expression, scope)?;
|
||||||
|
|
||||||
|
let debug_tag = if self.config.debug {
|
||||||
|
format!(" #{}", identifier.node)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
match location {
|
||||||
|
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
|
||||||
|
self.write_output(format!("move r{reg} {val_str}{debug_tag}"))?;
|
||||||
|
}
|
||||||
|
VariableLocation::Stack(offset) => {
|
||||||
|
// Calculate address: sp - offset
|
||||||
|
self.write_output(format!(
|
||||||
|
"sub r{0} sp {offset}",
|
||||||
|
VariableScope::TEMP_STACK_REGISTER
|
||||||
|
))?;
|
||||||
|
// Store value to stack/db at address
|
||||||
|
self.write_output(format!(
|
||||||
|
"put db r{0} {val_str}{debug_tag}",
|
||||||
|
VariableScope::TEMP_STACK_REGISTER
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
VariableLocation::Constant(_) => {
|
||||||
|
return Err(Error::ConstAssignment(identifier.node, identifier.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = cleanup {
|
||||||
|
scope.free_temp(name)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::MemberAccess(access) => {
|
||||||
|
// Set instruction: s device member value
|
||||||
|
let MemberAccessExpression { object, member } = access.node;
|
||||||
|
|
||||||
|
let (device_str, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||||
|
let (val_str, val_cleanup) = self.compile_operand(*expression, scope)?;
|
||||||
|
|
||||||
|
self.write_output(format!("s {} {} {}", device_str, member.node, val_str))?;
|
||||||
|
|
||||||
|
if let Some(c) = dev_cleanup {
|
||||||
|
scope.free_temp(c)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = val_cleanup {
|
||||||
|
scope.free_temp(c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
"Invalid assignment target. Only variables and member access are supported."
|
||||||
|
.into(),
|
||||||
|
Some(assignee.span),
|
||||||
));
|
));
|
||||||
VariableLocation::Temporary(0)
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let (val_str, cleanup) = self.compile_operand(*expression, scope)?;
|
|
||||||
|
|
||||||
let debug_tag = if self.config.debug {
|
|
||||||
format!(" #{}", identifier.node)
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
match location {
|
|
||||||
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
|
|
||||||
self.write_output(format!("move r{reg} {val_str}{debug_tag}"))?;
|
|
||||||
}
|
|
||||||
VariableLocation::Stack(offset) => {
|
|
||||||
// Calculate address: sp - offset
|
|
||||||
self.write_output(format!(
|
|
||||||
"sub r{0} sp {offset}",
|
|
||||||
VariableScope::TEMP_STACK_REGISTER
|
|
||||||
))?;
|
|
||||||
// Store value to stack/db at address
|
|
||||||
self.write_output(format!(
|
|
||||||
"put db r{0} {val_str}{debug_tag}",
|
|
||||||
VariableScope::TEMP_STACK_REGISTER
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(name) = cleanup {
|
|
||||||
scope.free_temp(name)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -671,6 +832,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => {
|
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => {
|
||||||
self.write_output(format!("push r{reg}"))?;
|
self.write_output(format!("push r{reg}"))?;
|
||||||
}
|
}
|
||||||
|
VariableLocation::Constant(lit) => {
|
||||||
|
self.write_output(format!("push {}", extract_literal(lit, false)?))?;
|
||||||
|
}
|
||||||
VariableLocation::Stack(stack_offset) => {
|
VariableLocation::Stack(stack_offset) => {
|
||||||
self.write_output(format!(
|
self.write_output(format!(
|
||||||
"sub r{0} sp {stack_offset}",
|
"sub r{0} sp {stack_offset}",
|
||||||
@@ -705,6 +869,31 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
stack.free_temp(name)?;
|
stack.free_temp(name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expression::MemberAccess(access) => {
|
||||||
|
// Compile member access to temp and push
|
||||||
|
let result_opt = self.expression(
|
||||||
|
Spanned {
|
||||||
|
node: Expression::MemberAccess(access),
|
||||||
|
span: Span {
|
||||||
|
start_col: 0,
|
||||||
|
end_col: 0,
|
||||||
|
start_line: 0,
|
||||||
|
end_line: 0,
|
||||||
|
}, // Dummy span
|
||||||
|
},
|
||||||
|
stack,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(result) = result_opt {
|
||||||
|
let reg_str = self.resolve_register(&result.location)?;
|
||||||
|
self.write_output(format!("push {reg_str}"))?;
|
||||||
|
if let Some(name) = result.temp_name {
|
||||||
|
stack.free_temp(name)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.write_output("push 0")?; // Should fail ideally
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
format!(
|
format!(
|
||||||
@@ -905,6 +1094,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
fn resolve_register(&self, loc: &VariableLocation) -> Result<String, Error> {
|
fn resolve_register(&self, loc: &VariableLocation) -> Result<String, Error> {
|
||||||
match loc {
|
match loc {
|
||||||
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(format!("r{r}")),
|
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(format!("r{r}")),
|
||||||
|
VariableLocation::Constant(_) => Err(Error::Unknown(
|
||||||
|
"Cannot resolve a constant value to register".into(),
|
||||||
|
None,
|
||||||
|
)),
|
||||||
VariableLocation::Stack(_) => Err(Error::Unknown(
|
VariableLocation::Stack(_) => Err(Error::Unknown(
|
||||||
"Cannot resolve Stack location directly to register string without context".into(),
|
"Cannot resolve Stack location directly to register string without context".into(),
|
||||||
None,
|
None,
|
||||||
@@ -954,6 +1147,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => {
|
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => {
|
||||||
Ok((format!("r{r}"), result.temp_name))
|
Ok((format!("r{r}"), result.temp_name))
|
||||||
}
|
}
|
||||||
|
VariableLocation::Constant(lit) => match lit {
|
||||||
|
Literal::Number(n) => Ok((n.to_string(), None)),
|
||||||
|
Literal::Boolean(b) => Ok((if b { "1" } else { "0" }.to_string(), None)),
|
||||||
|
Literal::String(s) => Ok((s, None)),
|
||||||
|
},
|
||||||
VariableLocation::Stack(offset) => {
|
VariableLocation::Stack(offset) => {
|
||||||
// If it's on the stack, we must load it into a temp to use it as an operand
|
// If it's on the stack, we must load it into a temp to use it as an operand
|
||||||
let temp_name = self.next_temp_name();
|
let temp_name = self.next_temp_name();
|
||||||
@@ -1116,25 +1314,34 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
fn expression_block<'v>(
|
fn expression_block<'v>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut expr: BlockExpression,
|
mut expr: BlockExpression,
|
||||||
scope: &mut VariableScope<'v>,
|
parent_scope: &mut VariableScope<'v>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// First, sort the expressions to ensure functions are hoisted
|
// First, sort the expressions to ensure functions are hoisted
|
||||||
expr.0.sort_by(|a, b| {
|
expr.0.sort_by(|a, b| {
|
||||||
if matches!(b.node, Expression::Function(_))
|
if matches!(
|
||||||
&& matches!(a.node, Expression::Function(_))
|
b.node,
|
||||||
{
|
Expression::Function(_) | Expression::ConstDeclaration(_)
|
||||||
|
) && matches!(
|
||||||
|
a.node,
|
||||||
|
Expression::Function(_) | Expression::ConstDeclaration(_)
|
||||||
|
) {
|
||||||
std::cmp::Ordering::Equal
|
std::cmp::Ordering::Equal
|
||||||
} else if matches!(a.node, Expression::Function(_)) {
|
} else if matches!(
|
||||||
|
a.node,
|
||||||
|
Expression::Function(_) | Expression::ConstDeclaration(_)
|
||||||
|
) {
|
||||||
std::cmp::Ordering::Less
|
std::cmp::Ordering::Less
|
||||||
} else {
|
} else {
|
||||||
std::cmp::Ordering::Greater
|
std::cmp::Ordering::Greater
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut scope = VariableScope::scoped(parent_scope);
|
||||||
|
|
||||||
for expr in expr.0 {
|
for expr in expr.0 {
|
||||||
if !self.declared_main
|
if !self.declared_main
|
||||||
&& !matches!(expr.node, Expression::Function(_))
|
&& !matches!(expr.node, Expression::Function(_))
|
||||||
&& !scope.has_parent()
|
&& !parent_scope.has_parent()
|
||||||
{
|
{
|
||||||
self.write_output("main:")?;
|
self.write_output("main:")?;
|
||||||
self.declared_main = true;
|
self.declared_main = true;
|
||||||
@@ -1142,11 +1349,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
|
|
||||||
match expr.node {
|
match expr.node {
|
||||||
Expression::Return(ret_expr) => {
|
Expression::Return(ret_expr) => {
|
||||||
self.expression_return(*ret_expr, scope)?;
|
self.expression_return(*ret_expr, &mut scope)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Swallow errors within expressions so block can continue
|
// Swallow errors within expressions so block can continue
|
||||||
if let Err(e) = self.expression(expr, scope).and_then(|result| {
|
if let Err(e) = self.expression(expr, &mut scope).and_then(|result| {
|
||||||
// If the expression was a statement that returned a temp result (e.g. `1 + 2;` line),
|
// If the expression was a statement that returned a temp result (e.g. `1 + 2;` line),
|
||||||
// we must free it to avoid leaking registers.
|
// we must free it to avoid leaking registers.
|
||||||
if let Some(comp_res) = result
|
if let Some(comp_res) = result
|
||||||
@@ -1162,6 +1369,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scope.stack_offset() > 0 {
|
||||||
|
self.write_output(format!("sub sp sp {}", scope.stack_offset()))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,6 +1401,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
debug!(self, "#returnValue")
|
debug!(self, "#returnValue")
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
VariableLocation::Constant(lit) => {
|
||||||
|
let str = extract_literal(lit, false)?;
|
||||||
|
self.write_output(format!(
|
||||||
|
"move r{} {str} {}",
|
||||||
|
VariableScope::RETURN_REGISTER,
|
||||||
|
debug!(self, "#returnValue")
|
||||||
|
))?
|
||||||
|
}
|
||||||
VariableLocation::Stack(offset) => {
|
VariableLocation::Stack(offset) => {
|
||||||
self.write_output(format!(
|
self.write_output(format!(
|
||||||
"sub r{} sp {offset}",
|
"sub r{} sp {offset}",
|
||||||
@@ -1252,6 +1471,23 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
scope.free_temp(name)?;
|
scope.free_temp(name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expression::MemberAccess(access) => {
|
||||||
|
// Return result of member access
|
||||||
|
let res_opt = self.expression(
|
||||||
|
Spanned {
|
||||||
|
node: Expression::MemberAccess(access),
|
||||||
|
span: expr.span,
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
)?;
|
||||||
|
if let Some(res) = res_opt {
|
||||||
|
let reg = self.resolve_register(&res.location)?;
|
||||||
|
self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, reg))?;
|
||||||
|
if let Some(temp) = res.temp_name {
|
||||||
|
scope.free_temp(temp)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
format!("Unsupported `return` statement: {:?}", expr),
|
format!("Unsupported `return` statement: {:?}", expr),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// r1 - r7 : Temporary Variables
|
// r1 - r7 : Temporary Variables
|
||||||
// r8 - r14 : Persistant Variables
|
// r8 - r14 : Persistant Variables
|
||||||
|
|
||||||
|
use parser::tree_node::Literal;
|
||||||
use quick_error::quick_error;
|
use quick_error::quick_error;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
@@ -43,6 +44,8 @@ pub enum VariableLocation {
|
|||||||
Persistant(u8),
|
Persistant(u8),
|
||||||
/// 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.
|
||||||
|
Constant(Literal),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VariableScope<'a> {
|
pub struct VariableScope<'a> {
|
||||||
@@ -91,6 +94,8 @@ impl<'a> VariableScope<'a> {
|
|||||||
pub fn scoped(parent: &'a VariableScope<'a>) -> Self {
|
pub fn scoped(parent: &'a VariableScope<'a>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
parent: Option::Some(parent),
|
parent: Option::Some(parent),
|
||||||
|
temporary_vars: parent.temporary_vars.clone(),
|
||||||
|
persistant_vars: parent.persistant_vars.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,24 +145,48 @@ impl<'a> VariableScope<'a> {
|
|||||||
Ok(var_location)
|
Ok(var_location)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_location_of(
|
pub fn define_const(
|
||||||
&mut self,
|
&mut self,
|
||||||
var_name: impl Into<String>,
|
var_name: impl Into<String>,
|
||||||
|
value: Literal,
|
||||||
) -> Result<VariableLocation, Error> {
|
) -> Result<VariableLocation, Error> {
|
||||||
let var_name = var_name.into();
|
let var_name = var_name.into();
|
||||||
let var = self
|
if self.var_lookup_table.contains_key(&var_name) {
|
||||||
.var_lookup_table
|
return Err(Error::DuplicateVariable(var_name));
|
||||||
.get(var_name.as_str())
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::UnknownVariable(var_name))?;
|
|
||||||
|
|
||||||
if let VariableLocation::Stack(inserted_at_offset) = var {
|
|
||||||
Ok(VariableLocation::Stack(
|
|
||||||
self.stack_offset - inserted_at_offset,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(var)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let new_value = VariableLocation::Constant(value);
|
||||||
|
|
||||||
|
self.var_lookup_table.insert(var_name, new_value.clone());
|
||||||
|
Ok(new_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_location_of(&self, var_name: impl Into<String>) -> Result<VariableLocation, Error> {
|
||||||
|
let var_name = var_name.into();
|
||||||
|
|
||||||
|
// 1. Check this scope
|
||||||
|
if let Some(var) = self.var_lookup_table.get(var_name.as_str()) {
|
||||||
|
if let VariableLocation::Stack(inserted_at_offset) = var {
|
||||||
|
// Return offset relative to CURRENT sp
|
||||||
|
return Ok(VariableLocation::Stack(
|
||||||
|
self.stack_offset - inserted_at_offset,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Ok(var.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Recursively check parent
|
||||||
|
if let Some(parent) = self.parent {
|
||||||
|
let loc = parent.get_location_of(var_name)?;
|
||||||
|
|
||||||
|
if let VariableLocation::Stack(parent_offset) = loc {
|
||||||
|
return Ok(VariableLocation::Stack(parent_offset + self.stack_offset));
|
||||||
|
}
|
||||||
|
return Ok(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::UnknownVariable(var_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_parent(&self) -> bool {
|
pub fn has_parent(&self) -> bool {
|
||||||
@@ -180,7 +209,7 @@ impl<'a> VariableScope<'a> {
|
|||||||
"Attempted to free a `let` variable.",
|
"Attempted to free a `let` variable.",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
VariableLocation::Stack(_) => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
6
rust_compiler/libs/helpers/Cargo.toml
Normal file
6
rust_compiler/libs/helpers/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
15
rust_compiler/libs/helpers/src/lib.rs
Normal file
15
rust_compiler/libs/helpers/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
mod macros;
|
||||||
|
mod syscall;
|
||||||
|
|
||||||
|
/// This trait will allow the LSP to emit documentation for various tokens and expressions.
|
||||||
|
/// You can easily create documentation for large enums with the `documented!` macro.
|
||||||
|
pub trait Documentation {
|
||||||
|
/// Retreive documentation for this specific item.
|
||||||
|
fn docs(&self) -> String;
|
||||||
|
|
||||||
|
fn get_all_documentation() -> Vec<(&'static str, String)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::{Documentation, documented, with_syscalls};
|
||||||
|
}
|
||||||
111
rust_compiler/libs/helpers/src/macros.rs
Normal file
111
rust_compiler/libs/helpers/src/macros.rs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! documented {
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 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]) => {
|
||||||
|
Some($doc)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Case 2: Other attributes (derives, etc.). Return None.
|
||||||
|
// We catch any other token sequence inside the brackets.
|
||||||
|
(@doc_filter #[$($attr:tt)*]) => {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Internal Helper: Match patterns for `match self`
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
(@arm $name:ident $variant:ident) => {
|
||||||
|
$name::$variant
|
||||||
|
};
|
||||||
|
(@arm $name:ident $variant:ident ( $($tuple:tt)* )) => {
|
||||||
|
$name::$variant(..)
|
||||||
|
};
|
||||||
|
(@arm $name:ident $variant:ident { $($structure:tt)* }) => {
|
||||||
|
$name::$variant{..}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Main Macro Entry Point
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
(
|
||||||
|
$(#[$enum_attr:meta])* $vis:vis enum $name:ident {
|
||||||
|
$(
|
||||||
|
// 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:ident
|
||||||
|
$( ($($tuple:tt)*) )?
|
||||||
|
$( {$($structure:tt)*} )?
|
||||||
|
),* $(,)?
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
// 1. Generate the actual Enum definition
|
||||||
|
$(#[$enum_attr])*
|
||||||
|
$vis enum $name {
|
||||||
|
$(
|
||||||
|
$(#[ $($variant_attr)* ])*
|
||||||
|
$variant
|
||||||
|
$( ($($tuple)*) )?
|
||||||
|
$( {$($structure)*} )?,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Implement the Documentation Trait
|
||||||
|
impl Documentation for $name {
|
||||||
|
fn docs(&self) -> String {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
documented!(@arm $name $variant $( ($($tuple)*) )? $( {$($structure)*} )? ) => {
|
||||||
|
// Create a temporary array of Option<&str> for all attributes
|
||||||
|
let doc_lines: &[Option<&str>] = &[
|
||||||
|
$(
|
||||||
|
documented!(@doc_filter #[ $($variant_attr)* ])
|
||||||
|
),*
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter out the Nones (non-doc attributes), join, and return
|
||||||
|
doc_lines.iter()
|
||||||
|
.filter_map(|&d| d)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Implement Static Documentation Provider
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_all_documentation() -> Vec<(&'static str, String)> {
|
||||||
|
vec![
|
||||||
|
$(
|
||||||
|
(
|
||||||
|
stringify!($variant),
|
||||||
|
{
|
||||||
|
// Re-use the same extraction logic
|
||||||
|
let doc_lines: &[Option<&str>] = &[
|
||||||
|
$(
|
||||||
|
documented!(@doc_filter #[ $($variant_attr)* ])
|
||||||
|
),*
|
||||||
|
];
|
||||||
|
doc_lines.iter()
|
||||||
|
.filter_map(|&d| d)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),*
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
32
rust_compiler/libs/helpers/src/syscall.rs
Normal file
32
rust_compiler/libs/helpers/src/syscall.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! with_syscalls {
|
||||||
|
($matcher:ident) => {
|
||||||
|
$matcher!(
|
||||||
|
"yield",
|
||||||
|
"sleep",
|
||||||
|
"hash",
|
||||||
|
"loadFromDevice",
|
||||||
|
"loadBatchNamed",
|
||||||
|
"loadBatch",
|
||||||
|
"setOnDevice",
|
||||||
|
"setOnDeviceBatched",
|
||||||
|
"setOnDeviceBatchedNamed",
|
||||||
|
"acos",
|
||||||
|
"asin",
|
||||||
|
"atan",
|
||||||
|
"atan2",
|
||||||
|
"abs",
|
||||||
|
"ceil",
|
||||||
|
"cos",
|
||||||
|
"floor",
|
||||||
|
"log",
|
||||||
|
"max",
|
||||||
|
"min",
|
||||||
|
"rand",
|
||||||
|
"sin",
|
||||||
|
"sqrt",
|
||||||
|
"tan",
|
||||||
|
"trunc"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,8 +6,10 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
quick-error = { workspace = true }
|
quick-error = { workspace = true }
|
||||||
tokenizer = { path = "../tokenizer" }
|
tokenizer = { path = "../tokenizer" }
|
||||||
|
helpers = { path = "../helpers" }
|
||||||
lsp-types = { workspace = true }
|
lsp-types = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { version = "1" }
|
anyhow = { version = "1" }
|
||||||
|
pretty_assertions = "1.4"
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ use tokenizer::{
|
|||||||
};
|
};
|
||||||
use tree_node::*;
|
use tree_node::*;
|
||||||
|
|
||||||
|
pub trait Documentation {
|
||||||
|
fn docs(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// A macro to create a boxed value.
|
/// A macro to create a boxed value.
|
||||||
macro_rules! boxed {
|
macro_rules! boxed {
|
||||||
@@ -121,7 +125,6 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates a Span from a given Token reference.
|
/// Calculates a Span from a given Token reference.
|
||||||
/// This is a static helper to avoid borrowing `self` when we already have a token ref.
|
|
||||||
fn token_to_span(t: &Token) -> Span {
|
fn token_to_span(t: &Token) -> Span {
|
||||||
let len = t.original_string.as_ref().map(|s| s.len()).unwrap_or(0);
|
let len = t.original_string.as_ref().map(|s| s.len()).unwrap_or(0);
|
||||||
Span {
|
Span {
|
||||||
@@ -149,7 +152,6 @@ impl<'a> Parser<'a> {
|
|||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Result<T, Error>,
|
F: FnOnce(&mut Self) -> Result<T, Error>,
|
||||||
{
|
{
|
||||||
// Peek at the start token. If no current token (parsing hasn't started), peek the buffer.
|
|
||||||
let start_token = if self.current_token.is_some() {
|
let start_token = if self.current_token.is_some() {
|
||||||
self.current_token.clone()
|
self.current_token.clone()
|
||||||
} else {
|
} else {
|
||||||
@@ -163,7 +165,6 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
let node = parser(self)?;
|
let node = parser(self)?;
|
||||||
|
|
||||||
// The end token is the current_token after parsing.
|
|
||||||
let end_token = self.current_token.as_ref();
|
let end_token = self.current_token.as_ref();
|
||||||
|
|
||||||
let (end_line, end_col) = end_token
|
let (end_line, end_col) = end_token
|
||||||
@@ -184,26 +185,15 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Skips tokens until a statement boundary is found to recover from errors.
|
|
||||||
fn synchronize(&mut self) -> Result<(), Error> {
|
fn synchronize(&mut self) -> Result<(), Error> {
|
||||||
// We advance once to consume the error-causing token if we haven't already
|
|
||||||
// But often the error happens after we consumed something.
|
|
||||||
// Safe bet: consume current, then look.
|
|
||||||
|
|
||||||
// If we assign next, we might be skipping the very token we want to sync on if the error didn't consume it?
|
|
||||||
// Usually, in recursive descent, the error is raised when `current` is unexpected.
|
|
||||||
// We want to discard `current` and move on.
|
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
|
|
||||||
while let Some(token) = &self.current_token {
|
while let Some(token) = &self.current_token {
|
||||||
if token.token_type == TokenType::Symbol(Symbol::Semicolon) {
|
if token.token_type == TokenType::Symbol(Symbol::Semicolon) {
|
||||||
// Consuming the semicolon is a good place to stop and resume parsing next statement
|
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the token looks like the start of a statement.
|
|
||||||
// If so, we don't consume it; we return so the loop in parse_all can try to parse it.
|
|
||||||
match token.token_type {
|
match token.token_type {
|
||||||
TokenType::Keyword(Keyword::Fn)
|
TokenType::Keyword(Keyword::Fn)
|
||||||
| TokenType::Keyword(Keyword::Let)
|
| TokenType::Keyword(Keyword::Let)
|
||||||
@@ -231,7 +221,6 @@ impl<'a> Parser<'a> {
|
|||||||
let mut expressions = Vec::<Spanned<Expression>>::new();
|
let mut expressions = Vec::<Spanned<Expression>>::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Check EOF without unwrapping error
|
|
||||||
match self.tokenizer.peek() {
|
match self.tokenizer.peek() {
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -248,19 +237,13 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.errors.push(e);
|
self.errors.push(e);
|
||||||
// Recover
|
|
||||||
if self.synchronize().is_err() {
|
if self.synchronize().is_err() {
|
||||||
// If sync failed (e.g. EOF during sync), break
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even if we had errors, we return whatever partial AST we managed to build.
|
|
||||||
// If expressions is empty and we had errors, it's a failed parse, but we return a block.
|
|
||||||
|
|
||||||
// Use the last token position for end span, or start if nothing parsed
|
|
||||||
let end_token_opt = self.tokenizer.peek().unwrap_or(None);
|
let end_token_opt = self.tokenizer.peek().unwrap_or(None);
|
||||||
let (end_line, end_col) = end_token_opt
|
let (end_line, end_col) = end_token_opt
|
||||||
.map(|tok| {
|
.map(|tok| {
|
||||||
@@ -285,7 +268,6 @@ impl<'a> Parser<'a> {
|
|||||||
pub fn parse(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
|
pub fn parse(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
|
|
||||||
// If assign_next hit EOF or error?
|
|
||||||
if self.current_token.is_none() {
|
if self.current_token.is_none() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@@ -317,15 +299,18 @@ impl<'a> Parser<'a> {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if the next or current token is an operator, comparison, or logical symbol
|
// Handle Postfix operators (Member Access, Method Call) immediately after unary
|
||||||
|
let lhs = self.parse_postfix(lhs)?;
|
||||||
|
|
||||||
|
// Handle Infix operators (Binary, Logical, Assignment)
|
||||||
if self_matches_peek!(
|
if self_matches_peek!(
|
||||||
self,
|
self,
|
||||||
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
|
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign)
|
||||||
) {
|
) {
|
||||||
return Ok(Some(self.infix(lhs)?));
|
return Ok(Some(self.infix(lhs)?));
|
||||||
} else if self_matches_current!(
|
} else if self_matches_current!(
|
||||||
self,
|
self,
|
||||||
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
|
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign)
|
||||||
) {
|
) {
|
||||||
self.tokenizer.seek(SeekFrom::Current(-1))?;
|
self.tokenizer.seek(SeekFrom::Current(-1))?;
|
||||||
return Ok(Some(self.infix(lhs)?));
|
return Ok(Some(self.infix(lhs)?));
|
||||||
@@ -334,6 +319,116 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Some(lhs))
|
Ok(Some(lhs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles dot notation chains: x.y.z()
|
||||||
|
fn parse_postfix(
|
||||||
|
&mut self,
|
||||||
|
mut lhs: Spanned<Expression>,
|
||||||
|
) -> Result<Spanned<Expression>, Error> {
|
||||||
|
loop {
|
||||||
|
if self_matches_peek!(self, TokenType::Symbol(Symbol::Dot)) {
|
||||||
|
self.assign_next()?; // consume Dot
|
||||||
|
|
||||||
|
let identifier_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
let identifier_span = Self::token_to_span(identifier_token);
|
||||||
|
let identifier = match identifier_token.token_type {
|
||||||
|
TokenType::Identifier(ref id) => id.clone(),
|
||||||
|
_ => {
|
||||||
|
return Err(Error::UnexpectedToken(
|
||||||
|
identifier_span,
|
||||||
|
identifier_token.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for Method Call '()'
|
||||||
|
if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) {
|
||||||
|
// Method Call
|
||||||
|
self.assign_next()?; // consume '('
|
||||||
|
let mut arguments = Vec::<Spanned<Expression>>::new();
|
||||||
|
|
||||||
|
while !token_matches!(
|
||||||
|
self.get_next()?.ok_or(Error::UnexpectedEOF)?,
|
||||||
|
TokenType::Symbol(Symbol::RParen)
|
||||||
|
) {
|
||||||
|
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
|
||||||
|
// Block expressions not allowed in args
|
||||||
|
if let Expression::Block(_) = expression.node {
|
||||||
|
return Err(Error::InvalidSyntax(
|
||||||
|
self.current_span(),
|
||||||
|
String::from("Block expressions are not allowed in method calls"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
arguments.push(expression);
|
||||||
|
|
||||||
|
if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma))
|
||||||
|
&& !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen))
|
||||||
|
{
|
||||||
|
let next_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
return Err(Error::UnexpectedToken(
|
||||||
|
Self::token_to_span(next_token),
|
||||||
|
next_token.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) {
|
||||||
|
self.assign_next()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End span is the ')'
|
||||||
|
let end_span = self.current_span();
|
||||||
|
let combined_span = Span {
|
||||||
|
start_line: lhs.span.start_line,
|
||||||
|
start_col: lhs.span.start_col,
|
||||||
|
end_line: end_span.end_line,
|
||||||
|
end_col: end_span.end_col,
|
||||||
|
};
|
||||||
|
|
||||||
|
lhs = Spanned {
|
||||||
|
span: combined_span,
|
||||||
|
node: Expression::MethodCall(Spanned {
|
||||||
|
span: combined_span,
|
||||||
|
node: MethodCallExpression {
|
||||||
|
object: boxed!(lhs),
|
||||||
|
method: Spanned {
|
||||||
|
span: identifier_span,
|
||||||
|
node: identifier,
|
||||||
|
},
|
||||||
|
arguments,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Member Access
|
||||||
|
let combined_span = Span {
|
||||||
|
start_line: lhs.span.start_line,
|
||||||
|
start_col: lhs.span.start_col,
|
||||||
|
end_line: identifier_span.end_line,
|
||||||
|
end_col: identifier_span.end_col,
|
||||||
|
};
|
||||||
|
|
||||||
|
lhs = Spanned {
|
||||||
|
span: combined_span,
|
||||||
|
node: Expression::MemberAccess(Spanned {
|
||||||
|
span: combined_span,
|
||||||
|
node: MemberAccessExpression {
|
||||||
|
object: boxed!(lhs),
|
||||||
|
member: Spanned {
|
||||||
|
span: identifier_span,
|
||||||
|
node: identifier,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(lhs)
|
||||||
|
}
|
||||||
|
|
||||||
fn unary(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
|
fn unary(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
|
||||||
macro_rules! matches_keyword {
|
macro_rules! matches_keyword {
|
||||||
($keyword:expr, $($pattern:pat),+) => {
|
($keyword:expr, $($pattern:pat),+) => {
|
||||||
@@ -357,10 +452,7 @@ impl<'a> Parser<'a> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenType::Keyword(Keyword::Let) => {
|
TokenType::Keyword(Keyword::Let) => Some(self.spanned(|p| p.declaration())?),
|
||||||
// declaration is wrapped in spanned inside the function, but expects 'let' to be current
|
|
||||||
Some(self.spanned(|p| p.declaration())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenType::Keyword(Keyword::Device) => {
|
TokenType::Keyword(Keyword::Device) => {
|
||||||
let spanned_dev = self.spanned(|p| p.device())?;
|
let spanned_dev = self.spanned(|p| p.device())?;
|
||||||
@@ -370,6 +462,15 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TokenType::Keyword(Keyword::Const) => {
|
||||||
|
let spanned_const = self.spanned(|p| p.const_declaration())?;
|
||||||
|
|
||||||
|
Some(Spanned {
|
||||||
|
span: spanned_const.span,
|
||||||
|
node: Expression::ConstDeclaration(spanned_const),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
TokenType::Keyword(Keyword::Fn) => {
|
TokenType::Keyword(Keyword::Fn) => {
|
||||||
let spanned_fn = self.spanned(|p| p.function())?;
|
let spanned_fn = self.spanned(|p| p.function())?;
|
||||||
Some(Spanned {
|
Some(Spanned {
|
||||||
@@ -404,7 +505,6 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
TokenType::Keyword(Keyword::Break) => {
|
TokenType::Keyword(Keyword::Break) => {
|
||||||
let span = self.current_span();
|
let span = self.current_span();
|
||||||
// make sure the next token is a semi-colon
|
|
||||||
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
|
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) {
|
if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) {
|
||||||
return Err(Error::UnexpectedToken(
|
return Err(Error::UnexpectedToken(
|
||||||
@@ -451,16 +551,6 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenType::Identifier(_)
|
|
||||||
if self_matches_peek!(self, TokenType::Symbol(Symbol::Assign)) =>
|
|
||||||
{
|
|
||||||
let spanned_assign = self.spanned(|p| p.assignment())?;
|
|
||||||
Some(Spanned {
|
|
||||||
span: spanned_assign.span,
|
|
||||||
node: Expression::Assignment(spanned_assign),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenType::Identifier(ref id) => {
|
TokenType::Identifier(ref id) => {
|
||||||
let span = self.current_span();
|
let span = self.current_span();
|
||||||
Some(Spanned {
|
Some(Spanned {
|
||||||
@@ -489,24 +579,36 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TokenType::Symbol(Symbol::LParen) => {
|
TokenType::Symbol(Symbol::LParen) => {
|
||||||
// Priority handles its own spanning
|
|
||||||
self.spanned(|p| p.priority())?.node.map(|node| *node)
|
self.spanned(|p| p.priority())?.node.map(|node| *node)
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenType::Symbol(Symbol::Minus) => {
|
TokenType::Symbol(Symbol::Minus) => {
|
||||||
// Need to handle span manually because unary call is next
|
|
||||||
let start_span = self.current_span();
|
let start_span = self.current_span();
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
|
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
// NOTE: Unary negation can also have postfix applied to the inner expression
|
||||||
|
// But generally -a.b parses as -(a.b), which is what parse_postfix ensures if called here.
|
||||||
|
// However, we call parse_postfix on the RESULT of unary in expression(), so
|
||||||
|
// `expression` sees `Negation`. `parse_postfix` doesn't apply to Negation node unless we allow it?
|
||||||
|
// Actually, `x.y` binds tighter than `-`. `postfix` logic belongs inside `unary` logic or
|
||||||
|
// `expression` logic.
|
||||||
|
// If I have `-x.y`, standard precedence says `-(x.y)`.
|
||||||
|
// `unary` returns `Negation(x)`. Then `expression` calls `postfix` on `Negation(x)`.
|
||||||
|
// `postfix` loop runs on `Negation`. This implies `(-x).y`. This is usually WRONG.
|
||||||
|
// `.` binds tighter than `-`.
|
||||||
|
// So `unary` must call `postfix` on the *operand* of the negation.
|
||||||
|
|
||||||
|
let inner_with_postfix = self.parse_postfix(inner_expr)?;
|
||||||
|
|
||||||
let combined_span = Span {
|
let combined_span = Span {
|
||||||
start_line: start_span.start_line,
|
start_line: start_span.start_line,
|
||||||
start_col: start_span.start_col,
|
start_col: start_span.start_col,
|
||||||
end_line: inner_expr.span.end_line,
|
end_line: inner_with_postfix.span.end_line,
|
||||||
end_col: inner_expr.span.end_col,
|
end_col: inner_with_postfix.span.end_col,
|
||||||
};
|
};
|
||||||
Some(Spanned {
|
Some(Spanned {
|
||||||
span: combined_span,
|
span: combined_span,
|
||||||
node: Expression::Negation(boxed!(inner_expr)),
|
node: Expression::Negation(boxed!(inner_with_postfix)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,17 +616,18 @@ impl<'a> Parser<'a> {
|
|||||||
let start_span = self.current_span();
|
let start_span = self.current_span();
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
|
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
let inner_with_postfix = self.parse_postfix(inner_expr)?;
|
||||||
let combined_span = Span {
|
let combined_span = Span {
|
||||||
start_line: start_span.start_line,
|
start_line: start_span.start_line,
|
||||||
start_col: start_span.start_col,
|
start_col: start_span.start_col,
|
||||||
end_line: inner_expr.span.end_line,
|
end_line: inner_with_postfix.span.end_line,
|
||||||
end_col: inner_expr.span.end_col,
|
end_col: inner_with_postfix.span.end_col,
|
||||||
};
|
};
|
||||||
Some(Spanned {
|
Some(Spanned {
|
||||||
span: combined_span,
|
span: combined_span,
|
||||||
node: Expression::Logical(Spanned {
|
node: Expression::Logical(Spanned {
|
||||||
span: combined_span,
|
span: combined_span,
|
||||||
node: LogicalExpression::Not(boxed!(inner_expr)),
|
node: LogicalExpression::Not(boxed!(inner_with_postfix)),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -543,41 +646,44 @@ impl<'a> Parser<'a> {
|
|||||||
fn get_infix_child_node(&mut self) -> Result<Spanned<tree_node::Expression>, Error> {
|
fn get_infix_child_node(&mut self) -> Result<Spanned<tree_node::Expression>, Error> {
|
||||||
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
|
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
|
||||||
|
|
||||||
match current_token.token_type {
|
let start_span = self.current_span();
|
||||||
|
|
||||||
|
let expr = match current_token.token_type {
|
||||||
TokenType::Number(_) | TokenType::Boolean(_) => {
|
TokenType::Number(_) | TokenType::Boolean(_) => {
|
||||||
let lit = self.spanned(|p| p.literal())?;
|
let lit = self.spanned(|p| p.literal())?;
|
||||||
Ok(Spanned {
|
Spanned {
|
||||||
span: lit.span,
|
span: lit.span,
|
||||||
node: Expression::Literal(lit),
|
node: Expression::Literal(lit),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenType::Identifier(ref ident)
|
TokenType::Identifier(ref ident)
|
||||||
if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
|
if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
|
||||||
{
|
{
|
||||||
|
// This is a Variable. We need to check for Postfix operations on it.
|
||||||
let span = self.current_span();
|
let span = self.current_span();
|
||||||
Ok(Spanned {
|
Spanned {
|
||||||
span,
|
span,
|
||||||
node: Expression::Variable(Spanned {
|
node: Expression::Variable(Spanned {
|
||||||
span,
|
span,
|
||||||
node: ident.clone(),
|
node: ident.clone(),
|
||||||
}),
|
}),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenType::Symbol(Symbol::LParen) => Ok(*self
|
TokenType::Symbol(Symbol::LParen) => *self
|
||||||
.spanned(|p| p.priority())?
|
.spanned(|p| p.priority())?
|
||||||
.node
|
.node
|
||||||
.ok_or(Error::UnexpectedEOF)?),
|
.ok_or(Error::UnexpectedEOF)?,
|
||||||
|
|
||||||
TokenType::Identifier(_)
|
TokenType::Identifier(_)
|
||||||
if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
|
if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
|
||||||
{
|
{
|
||||||
let inv = self.spanned(|p| p.invocation())?;
|
let inv = self.spanned(|p| p.invocation())?;
|
||||||
Ok(Spanned {
|
Spanned {
|
||||||
span: inv.span,
|
span: inv.span,
|
||||||
node: Expression::Invocation(inv),
|
node: Expression::Invocation(inv),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenType::Symbol(Symbol::Minus) => {
|
TokenType::Symbol(Symbol::Minus) => {
|
||||||
let start_span = self.current_span();
|
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
let inner = self.get_infix_child_node()?;
|
let inner = self.get_infix_child_node()?;
|
||||||
let span = Span {
|
let span = Span {
|
||||||
@@ -586,13 +692,12 @@ impl<'a> Parser<'a> {
|
|||||||
end_line: inner.span.end_line,
|
end_line: inner.span.end_line,
|
||||||
end_col: inner.span.end_col,
|
end_col: inner.span.end_col,
|
||||||
};
|
};
|
||||||
Ok(Spanned {
|
Spanned {
|
||||||
span,
|
span,
|
||||||
node: Expression::Negation(boxed!(inner)),
|
node: Expression::Negation(boxed!(inner)),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenType::Symbol(Symbol::LogicalNot) => {
|
TokenType::Symbol(Symbol::LogicalNot) => {
|
||||||
let start_span = self.current_span();
|
|
||||||
self.assign_next()?;
|
self.assign_next()?;
|
||||||
let inner = self.get_infix_child_node()?;
|
let inner = self.get_infix_child_node()?;
|
||||||
let span = Span {
|
let span = Span {
|
||||||
@@ -601,19 +706,25 @@ impl<'a> Parser<'a> {
|
|||||||
end_line: inner.span.end_line,
|
end_line: inner.span.end_line,
|
||||||
end_col: inner.span.end_col,
|
end_col: inner.span.end_col,
|
||||||
};
|
};
|
||||||
Ok(Spanned {
|
Spanned {
|
||||||
span,
|
span,
|
||||||
node: Expression::Logical(Spanned {
|
node: Expression::Logical(Spanned {
|
||||||
span,
|
span,
|
||||||
node: LogicalExpression::Not(boxed!(inner)),
|
node: LogicalExpression::Not(boxed!(inner)),
|
||||||
}),
|
}),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::UnexpectedToken(
|
_ => {
|
||||||
self.current_span(),
|
return Err(Error::UnexpectedToken(
|
||||||
current_token.clone(),
|
self.current_span(),
|
||||||
)),
|
current_token.clone(),
|
||||||
}
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Important: We must check for postfix operations here too
|
||||||
|
// e.g. a + b.c
|
||||||
|
self.parse_postfix(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device(&mut self) -> Result<DeviceDeclarationExpression, Error> {
|
fn device(&mut self) -> Result<DeviceDeclarationExpression, Error> {
|
||||||
@@ -665,39 +776,6 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assignment(&mut self) -> Result<AssignmentExpression, Error> {
|
|
||||||
let identifier_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
|
|
||||||
let identifier_span = Self::token_to_span(identifier_token);
|
|
||||||
let identifier = match identifier_token.token_type {
|
|
||||||
TokenType::Identifier(ref id) => id.clone(),
|
|
||||||
_ => {
|
|
||||||
return Err(Error::UnexpectedToken(
|
|
||||||
self.current_span(),
|
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone();
|
|
||||||
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) {
|
|
||||||
return Err(Error::UnexpectedToken(
|
|
||||||
Self::token_to_span(¤t_token),
|
|
||||||
current_token.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.assign_next()?;
|
|
||||||
|
|
||||||
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
|
||||||
|
|
||||||
Ok(AssignmentExpression {
|
|
||||||
identifier: Spanned {
|
|
||||||
span: identifier_span,
|
|
||||||
node: identifier,
|
|
||||||
},
|
|
||||||
expression: boxed!(expression),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn infix(&mut self, previous: Spanned<Expression>) -> Result<Spanned<Expression>, Error> {
|
fn infix(&mut self, previous: Spanned<Expression>) -> Result<Spanned<Expression>, Error> {
|
||||||
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone();
|
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone();
|
||||||
|
|
||||||
@@ -708,7 +786,9 @@ impl<'a> Parser<'a> {
|
|||||||
| Expression::Priority(_)
|
| Expression::Priority(_)
|
||||||
| Expression::Literal(_)
|
| Expression::Literal(_)
|
||||||
| Expression::Variable(_)
|
| Expression::Variable(_)
|
||||||
| Expression::Negation(_) => {}
|
| Expression::Negation(_)
|
||||||
|
| Expression::MemberAccess(_)
|
||||||
|
| Expression::MethodCall(_) => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidSyntax(
|
return Err(Error::InvalidSyntax(
|
||||||
self.current_span(),
|
self.current_span(),
|
||||||
@@ -722,9 +802,10 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
let mut temp_token = current_token.clone();
|
let mut temp_token = current_token.clone();
|
||||||
|
|
||||||
|
// Include Assign in the operator loop
|
||||||
while token_matches!(
|
while token_matches!(
|
||||||
temp_token,
|
temp_token,
|
||||||
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
|
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign)
|
||||||
) {
|
) {
|
||||||
let operator = match temp_token.token_type {
|
let operator = match temp_token.token_type {
|
||||||
TokenType::Symbol(s) => s,
|
TokenType::Symbol(s) => s,
|
||||||
@@ -955,6 +1036,37 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr));
|
operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr));
|
||||||
|
|
||||||
|
// --- PRECEDENCE LEVEL 8: Assignment (=) ---
|
||||||
|
// Assignment is Right Associative: a = b = c => a = (b = c)
|
||||||
|
// We iterate Right to Left
|
||||||
|
for (i, operator) in operators.iter().enumerate().rev() {
|
||||||
|
if matches!(operator, Symbol::Assign) {
|
||||||
|
let right = expressions.remove(i + 1);
|
||||||
|
let left = expressions.remove(i);
|
||||||
|
let span = Span {
|
||||||
|
start_line: left.span.start_line,
|
||||||
|
start_col: left.span.start_col,
|
||||||
|
end_line: right.span.end_line,
|
||||||
|
end_col: right.span.end_col,
|
||||||
|
};
|
||||||
|
|
||||||
|
expressions.insert(
|
||||||
|
i,
|
||||||
|
Spanned {
|
||||||
|
span,
|
||||||
|
node: Expression::Assignment(Spanned {
|
||||||
|
span,
|
||||||
|
node: AssignmentExpression {
|
||||||
|
assignee: boxed!(left),
|
||||||
|
expression: boxed!(right),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
operators.retain(|symbol| !matches!(symbol, Symbol::Assign));
|
||||||
|
|
||||||
if expressions.len() != 1 || !operators.is_empty() {
|
if expressions.len() != 1 || !operators.is_empty() {
|
||||||
return Err(Error::InvalidSyntax(
|
return Err(Error::InvalidSyntax(
|
||||||
self.current_span(),
|
self.current_span(),
|
||||||
@@ -1117,6 +1229,46 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(BlockExpression(expressions))
|
Ok(BlockExpression(expressions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn const_declaration(&mut self) -> Result<ConstDeclarationExpression, Error> {
|
||||||
|
// const
|
||||||
|
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
|
||||||
|
if !self_matches_current!(self, TokenType::Keyword(Keyword::Const)) {
|
||||||
|
return Err(Error::UnexpectedToken(
|
||||||
|
self.current_span(),
|
||||||
|
current_token.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable_name
|
||||||
|
let ident_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
let ident_span = Self::token_to_span(ident_token);
|
||||||
|
let ident = match ident_token.token_type {
|
||||||
|
TokenType::Identifier(ref id) => id.clone(),
|
||||||
|
_ => return Err(Error::UnexpectedToken(ident_span, ident_token.clone())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// `=`
|
||||||
|
let assign_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone();
|
||||||
|
if !token_matches!(assign_token, TokenType::Symbol(Symbol::Assign)) {
|
||||||
|
return Err(Error::UnexpectedToken(
|
||||||
|
Self::token_to_span(&assign_token),
|
||||||
|
assign_token,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// literal value
|
||||||
|
self.assign_next()?;
|
||||||
|
let lit = self.spanned(|p| p.literal())?;
|
||||||
|
|
||||||
|
Ok(ConstDeclarationExpression {
|
||||||
|
name: Spanned {
|
||||||
|
span: ident_span,
|
||||||
|
node: ident,
|
||||||
|
},
|
||||||
|
value: lit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn declaration(&mut self) -> Result<Expression, Error> {
|
fn declaration(&mut self) -> Result<Expression, Error> {
|
||||||
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
|
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
|
||||||
if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) {
|
if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) {
|
||||||
@@ -1506,7 +1658,6 @@ impl<'a> Parser<'a> {
|
|||||||
Literal::String(variable),
|
Literal::String(variable),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
// ... (implementing other syscalls similarly using patterns above)
|
|
||||||
"setOnDevice" => {
|
"setOnDevice" => {
|
||||||
check_length(self, &invocation.arguments, 3)?;
|
check_length(self, &invocation.arguments, 3)?;
|
||||||
let mut args = invocation.arguments.into_iter();
|
let mut args = invocation.arguments.into_iter();
|
||||||
@@ -1531,23 +1682,10 @@ impl<'a> Parser<'a> {
|
|||||||
boxed!(variable),
|
boxed!(variable),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => Err(Error::UnsupportedKeyword(
|
||||||
// For Math functions or unknown functions
|
self.current_span(),
|
||||||
if SysCall::is_syscall(&invocation.name.node) {
|
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
||||||
// Attempt to parse as math if applicable, or error if strict
|
)),
|
||||||
// Here we are falling back to simple handling or error.
|
|
||||||
// Since Math isn't fully expanded in this snippet, we return Unsupported.
|
|
||||||
Err(Error::UnsupportedKeyword(
|
|
||||||
self.current_span(),
|
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(Error::UnsupportedKeyword(
|
|
||||||
self.current_span(),
|
|
||||||
self.current_token.clone().ok_or(Error::UnexpectedEOF)?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,107 @@
|
|||||||
use crate::tree_node::{Expression, Literal, Spanned};
|
|
||||||
|
|
||||||
use super::LiteralOrVariable;
|
use super::LiteralOrVariable;
|
||||||
|
use crate::tree_node::{Expression, Literal, Spanned};
|
||||||
|
use helpers::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
documented! {
|
||||||
pub enum Math {
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
/// Returns the angle in radians whose cosine is the specified number.
|
pub enum Math {
|
||||||
/// ## In Game
|
/// Returns the angle in radians whose cosine is the specified number.
|
||||||
/// `acos r? a(r?|num)`
|
/// ## IC10
|
||||||
Acos(LiteralOrVariable),
|
/// `acos r? a(r?|num)`
|
||||||
/// Returns the angle in radians whose sine is the specified number.
|
/// ## Slang
|
||||||
/// ## In Game
|
/// `(number|var).acos();`
|
||||||
/// `asin r? a(r?|num)`
|
Acos(LiteralOrVariable),
|
||||||
Asin(LiteralOrVariable),
|
/// Returns the angle in radians whose sine is the specified number.
|
||||||
/// Returns the angle in radians whose tangent is the specified number.
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `asin r? a(r?|num)`
|
||||||
/// `atan r? a(r?|num)`
|
/// ## Slang
|
||||||
Atan(LiteralOrVariable),
|
/// `(number|var).asin();`
|
||||||
/// Returns the angle in radians whose tangent is the quotient of the specified numbers.
|
Asin(LiteralOrVariable),
|
||||||
/// ## In Game
|
/// Returns the angle in radians whose tangent is the specified number.
|
||||||
/// `atan2 r? a(r?|num) b(r?|num)`
|
/// ## IC10
|
||||||
Atan2(LiteralOrVariable, LiteralOrVariable),
|
/// `atan r? a(r?|num)`
|
||||||
/// Gets the absolute value of a number.
|
/// ## Slang
|
||||||
/// ## In Game
|
/// `(number|var).atan();`
|
||||||
/// `abs r? a(r?|num)`
|
Atan(LiteralOrVariable),
|
||||||
Abs(LiteralOrVariable),
|
/// Returns the angle in radians whose tangent is the quotient of the specified numbers.
|
||||||
/// Rounds a number up to the nearest whole number.
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `atan2 r? a(r?|num) b(r?|num)`
|
||||||
/// `ceil r? a(r?|num)`
|
/// ## Slang
|
||||||
Ceil(LiteralOrVariable),
|
/// `(number|var).atan2((number|var));`
|
||||||
/// Returns the cosine of the specified angle in radians.
|
Atan2(LiteralOrVariable, LiteralOrVariable),
|
||||||
/// ## In Game
|
/// Gets the absolute value of a number.
|
||||||
/// cos r? a(r?|num)
|
/// ## IC10
|
||||||
Cos(LiteralOrVariable),
|
/// `abs r? a(r?|num)`
|
||||||
/// Rounds a number down to the nearest whole number.
|
/// ## Slang
|
||||||
/// ## In Game
|
/// `(number|var).abs();`
|
||||||
/// `floor r? a(r?|num)`
|
Abs(LiteralOrVariable),
|
||||||
Floor(LiteralOrVariable),
|
/// Rounds a number up to the nearest whole number.
|
||||||
/// Computes the natural logarithm of a number.
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `ceil r? a(r?|num)`
|
||||||
/// `log r? a(r?|num)`
|
/// ## Slang
|
||||||
Log(LiteralOrVariable),
|
/// `(number|var).ceil();`
|
||||||
/// Computes the maximum of two numbers.
|
Ceil(LiteralOrVariable),
|
||||||
/// ## In Game
|
/// Returns the cosine of the specified angle in radians.
|
||||||
/// `max r? a(r?|num) b(r?|num)`
|
/// ## IC10
|
||||||
Max(LiteralOrVariable, LiteralOrVariable),
|
/// `cos r? a(r?|num)`
|
||||||
/// Computes the minimum of two numbers.
|
/// ## Slang
|
||||||
/// ## In Game
|
/// `(number|var).cos();`
|
||||||
/// `min r? a(r?|num) b(r?|num)`
|
Cos(LiteralOrVariable),
|
||||||
Min(LiteralOrVariable, LiteralOrVariable),
|
/// Rounds a number down to the nearest whole number.
|
||||||
/// Gets a random number between 0 and 1.
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `floor r? a(r?|num)`
|
||||||
/// `rand r?`
|
/// ## Slang
|
||||||
Rand,
|
/// `(number|var).floor();`
|
||||||
/// Returns the sine of the specified angle in radians.
|
Floor(LiteralOrVariable),
|
||||||
/// ## In Game
|
/// Computes the natural logarithm of a number.
|
||||||
/// `sin r? a(r?|num)`
|
/// ## IC10
|
||||||
Sin(LiteralOrVariable),
|
/// `log r? a(r?|num)`
|
||||||
/// Computes the square root of a number.
|
/// ## Slang
|
||||||
/// ## In Game
|
/// `(number|var).log();`
|
||||||
/// `sqrt r? a(r?|num)`
|
Log(LiteralOrVariable),
|
||||||
Sqrt(LiteralOrVariable),
|
/// Computes the maximum of two numbers.
|
||||||
/// Returns the tangent of the specified angle in radians.
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `max r? a(r?|num) b(r?|num)`
|
||||||
/// `tan r? a(r?|num)`
|
/// ## Slang
|
||||||
Tan(LiteralOrVariable),
|
/// `(number|var).max((number|var));`
|
||||||
/// Truncates a number by removing the decimal portion.
|
Max(LiteralOrVariable, LiteralOrVariable),
|
||||||
/// ## In Game
|
/// Computes the minimum of two numbers.
|
||||||
/// `trunc r? a(r?|num)`
|
/// ## IC10
|
||||||
Trunc(LiteralOrVariable),
|
/// `min r? a(r?|num) b(r?|num)`
|
||||||
|
/// ## Slang
|
||||||
|
/// `(number|var).min((number|var));`
|
||||||
|
Min(LiteralOrVariable, LiteralOrVariable),
|
||||||
|
/// Gets a random number between 0 and 1.
|
||||||
|
/// ## IC10
|
||||||
|
/// `rand r?`
|
||||||
|
/// ## Slang
|
||||||
|
/// `rand();`
|
||||||
|
Rand,
|
||||||
|
/// Returns the sine of the specified angle in radians.
|
||||||
|
/// ## IC10
|
||||||
|
/// `sin r? a(r?|num)`
|
||||||
|
/// ## Slang
|
||||||
|
/// `(number|var).sin();`
|
||||||
|
Sin(LiteralOrVariable),
|
||||||
|
/// Computes the square root of a number.
|
||||||
|
/// ## IC10
|
||||||
|
/// `sqrt r? a(r?|num)`
|
||||||
|
/// ## Slang
|
||||||
|
/// `(number|var).sqrt();`
|
||||||
|
Sqrt(LiteralOrVariable),
|
||||||
|
/// Returns the tangent of the specified angle in radians.
|
||||||
|
/// ## IC10
|
||||||
|
/// `tan r? a(r?|num)`
|
||||||
|
/// ## Slang
|
||||||
|
/// `(number|var).tan();`
|
||||||
|
Tan(LiteralOrVariable),
|
||||||
|
/// Truncates a number by removing the decimal portion.
|
||||||
|
/// ## IC10
|
||||||
|
/// `trunc r? a(r?|num)`
|
||||||
|
/// ## Slang
|
||||||
|
/// `(number|var).trunc();`
|
||||||
|
Trunc(LiteralOrVariable),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Math {
|
impl std::fmt::Display for Math {
|
||||||
@@ -93,71 +127,76 @@ impl std::fmt::Display for Math {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
documented! {
|
||||||
pub enum System {
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
/// Pauses execution for exactly 1 tick and then resumes.
|
pub enum System {
|
||||||
/// ## In Game
|
/// Pauses execution for exactly 1 tick and then resumes.
|
||||||
/// yield
|
/// ## IC10
|
||||||
Yield,
|
/// `yield`
|
||||||
/// Represents a function that can be called to sleep for a certain amount of time.
|
/// ## Slang
|
||||||
/// ## In Game
|
/// `yield();`
|
||||||
/// `sleep a(r?|num)`
|
Yield,
|
||||||
Sleep(Box<Spanned<Expression>>),
|
/// Represents a function that can be called to sleep for a certain amount of time.
|
||||||
/// Gets the in-game hash for a specific prefab name.
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `sleep a(r?|num)`
|
||||||
/// `HASH("prefabName")`
|
/// ## Slang
|
||||||
Hash(Literal),
|
/// `sleep(number|var);`
|
||||||
/// Represents a function which loads a device variable into a register.
|
Sleep(Box<Spanned<Expression>>),
|
||||||
/// ## In Game
|
/// Gets the in-game hash for a specific prefab name.
|
||||||
/// `l r? d? var`
|
/// ## IC10
|
||||||
/// ## Examples
|
/// `HASH("prefabName")`
|
||||||
/// `l r0 d0 Setting`
|
/// ## Slang
|
||||||
/// `l r1 d5 Pressure`
|
/// `HASH("prefabName");`
|
||||||
LoadFromDevice(LiteralOrVariable, Literal),
|
Hash(Literal),
|
||||||
/// Function which gets a LogicType from all connected network devices that match
|
/// Represents a function which loads a device variable into a register.
|
||||||
/// the provided device hash and name, aggregating them via a batchMode
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `l r? d? var`
|
||||||
/// lbn r? deviceHash nameHash logicType batchMode
|
/// ## Slang
|
||||||
/// ## Examples
|
/// `loadFromDevice(deviceType, "LogicType");`
|
||||||
/// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum
|
LoadFromDevice(LiteralOrVariable, Literal),
|
||||||
LoadBatchNamed(
|
/// Function which gets a LogicType from all connected network devices that match
|
||||||
LiteralOrVariable,
|
/// the provided device hash and name, aggregating them via a batchMode
|
||||||
Box<Spanned<Expression>>,
|
/// ## IC10
|
||||||
Literal,
|
/// `lbn r? deviceHash nameHash logicType batchMode`
|
||||||
Literal,
|
/// ## Slang
|
||||||
),
|
/// `loadFromDeviceBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");`
|
||||||
/// Loads a LogicType from all connected network devices, aggregating them via a
|
LoadBatchNamed(
|
||||||
/// batchMode
|
LiteralOrVariable,
|
||||||
/// ## In Game
|
Box<Spanned<Expression>>,
|
||||||
/// lb r? deviceHash logicType batchMode
|
Literal,
|
||||||
/// ## Examples
|
Literal,
|
||||||
/// lb r0 HASH("StructureWallLight") On Minimum
|
),
|
||||||
LoadBatch(LiteralOrVariable, Literal, Literal),
|
/// Loads a LogicType from all connected network devices, aggregating them via a
|
||||||
/// Represents a function which stores a setting into a specific device.
|
/// batchMode
|
||||||
/// ## In Game
|
/// ## IC10
|
||||||
/// `s d? logicType r?`
|
/// `lb r? deviceHash logicType batchMode`
|
||||||
/// ## Example
|
/// ## Slang
|
||||||
/// `s d0 Setting r0`
|
/// `loadFromDeviceBatched(deviceHash, "Variable", "LogicType");`
|
||||||
SetOnDevice(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
|
LoadBatch(LiteralOrVariable, Literal, Literal),
|
||||||
/// Represents a function which stores a setting to all devices that match
|
/// Represents a function which stores a setting into a specific device.
|
||||||
/// the given deviceHash
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `s d? logicType r?`
|
||||||
/// `sb deviceHash logicType r?`
|
/// ## Slang
|
||||||
/// ## Example
|
/// `setOnDevice(deviceType, "Variable", (number|var));`
|
||||||
/// `sb HASH("Doors") Lock 1`
|
SetOnDevice(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
|
||||||
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
|
/// 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
|
||||||
/// both the given deviceHash AND the given nameHash
|
/// ## IC10
|
||||||
/// ## In Game
|
/// `sb deviceHash logicType r?`
|
||||||
/// `sbn deviceHash nameHash logicType r?`
|
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
|
||||||
/// ## Example
|
/// Represents a function which stores a setting to all devices that match
|
||||||
/// `sbn HASH("Doors") HASH("Exterior") Lock 1`
|
/// both the given deviceHash AND the given nameHash
|
||||||
SetOnDeviceBatchedNamed(
|
/// ## IC10
|
||||||
LiteralOrVariable,
|
/// `sbn deviceHash nameHash logicType r?`
|
||||||
LiteralOrVariable,
|
/// ## Slang
|
||||||
Literal,
|
/// `setOnDeviceBatchedNamed(deviceType, nameHash, "LogicType", (number|var))`
|
||||||
Box<Spanned<Expression>>,
|
SetOnDeviceBatchedNamed(
|
||||||
),
|
LiteralOrVariable,
|
||||||
|
LiteralOrVariable,
|
||||||
|
Literal,
|
||||||
|
Box<Spanned<Expression>>,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for System {
|
impl std::fmt::Display for System {
|
||||||
@@ -190,6 +229,22 @@ pub enum SysCall {
|
|||||||
Math(Math),
|
Math(Math),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Documentation for SysCall {
|
||||||
|
fn docs(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::System(s) => s.docs(),
|
||||||
|
Self::Math(m) => m.docs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all_documentation() -> Vec<(&'static str, String)> {
|
||||||
|
let mut all_docs = System::get_all_documentation();
|
||||||
|
all_docs.extend(Math::get_all_documentation());
|
||||||
|
|
||||||
|
all_docs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SysCall {
|
impl std::fmt::Display for SysCall {
|
||||||
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 {
|
||||||
@@ -201,32 +256,6 @@ impl std::fmt::Display for SysCall {
|
|||||||
|
|
||||||
impl SysCall {
|
impl SysCall {
|
||||||
pub fn is_syscall(identifier: &str) -> bool {
|
pub fn is_syscall(identifier: &str) -> bool {
|
||||||
matches!(
|
tokenizer::token::is_syscall(identifier)
|
||||||
identifier,
|
|
||||||
"yield"
|
|
||||||
| "sleep"
|
|
||||||
| "hash"
|
|
||||||
| "loadFromDevice"
|
|
||||||
| "setOnDevice"
|
|
||||||
| "setOnDeviceBatched"
|
|
||||||
| "setOnDeviceBatchedNamed"
|
|
||||||
| "acos"
|
|
||||||
| "asin"
|
|
||||||
| "atan"
|
|
||||||
| "atan2"
|
|
||||||
| "abs"
|
|
||||||
| "ceil"
|
|
||||||
| "cos"
|
|
||||||
| "floor"
|
|
||||||
| "log"
|
|
||||||
| "max"
|
|
||||||
| "min"
|
|
||||||
| "rand"
|
|
||||||
| "sin"
|
|
||||||
| "sqrt"
|
|
||||||
| "tan"
|
|
||||||
| "trunc"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! parser {
|
macro_rules! parser {
|
||||||
($input:expr) => {
|
($input:expr) => {
|
||||||
Parser::new(Tokenizer::from($input.to_owned()))
|
Parser::new(Tokenizer::from($input))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ mod blocks;
|
|||||||
use super::Parser;
|
use super::Parser;
|
||||||
use super::Tokenizer;
|
use super::Tokenizer;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unsupported_keywords() -> Result<()> {
|
fn test_unsupported_keywords() -> Result<()> {
|
||||||
@@ -31,7 +32,7 @@ fn test_declarations() -> Result<()> {
|
|||||||
// The below line should fail
|
// The below line should fail
|
||||||
let y = 234
|
let y = 234
|
||||||
"#;
|
"#;
|
||||||
let tokenizer = Tokenizer::from(input.to_owned());
|
let tokenizer = Tokenizer::from(input);
|
||||||
let mut parser = Parser::new(tokenizer);
|
let mut parser = Parser::new(tokenizer);
|
||||||
|
|
||||||
let expression = parser.parse()?.unwrap();
|
let expression = parser.parse()?.unwrap();
|
||||||
@@ -43,6 +44,36 @@ fn test_declarations() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_const_declaration() -> Result<()> {
|
||||||
|
let input = r#"
|
||||||
|
const item = 20c;
|
||||||
|
const decimal = 200.15;
|
||||||
|
const nameConst = "str_lit";
|
||||||
|
"#;
|
||||||
|
let tokenizer = Tokenizer::from(input);
|
||||||
|
let mut parser = Parser::new(tokenizer);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"(const item = 293.15)",
|
||||||
|
parser.parse()?.unwrap().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"(const decimal = 200.15)",
|
||||||
|
parser.parse()?.unwrap().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
r#"(const nameConst = "str_lit")"#,
|
||||||
|
parser.parse()?.unwrap().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(None, parser.parse()?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_function_expression() -> Result<()> {
|
fn test_function_expression() -> Result<()> {
|
||||||
let input = r#"
|
let input = r#"
|
||||||
@@ -52,7 +83,7 @@ fn test_function_expression() -> Result<()> {
|
|||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let tokenizer = Tokenizer::from(input.to_owned());
|
let tokenizer = Tokenizer::from(input);
|
||||||
let mut parser = Parser::new(tokenizer);
|
let mut parser = Parser::new(tokenizer);
|
||||||
|
|
||||||
let expression = parser.parse()?.unwrap();
|
let expression = parser.parse()?.unwrap();
|
||||||
@@ -71,7 +102,7 @@ fn test_function_invocation() -> Result<()> {
|
|||||||
add();
|
add();
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let tokenizer = Tokenizer::from(input.to_owned());
|
let tokenizer = Tokenizer::from(input);
|
||||||
let mut parser = Parser::new(tokenizer);
|
let mut parser = Parser::new(tokenizer);
|
||||||
|
|
||||||
let expression = parser.parse()?.unwrap();
|
let expression = parser.parse()?.unwrap();
|
||||||
@@ -87,7 +118,7 @@ fn test_priority_expression() -> Result<()> {
|
|||||||
let x = (4);
|
let x = (4);
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let tokenizer = Tokenizer::from(input.to_owned());
|
let tokenizer = Tokenizer::from(input);
|
||||||
let mut parser = Parser::new(tokenizer);
|
let mut parser = Parser::new(tokenizer);
|
||||||
|
|
||||||
let expression = parser.parse()?.unwrap();
|
let expression = parser.parse()?.unwrap();
|
||||||
@@ -99,16 +130,16 @@ fn test_priority_expression() -> Result<()> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_binary_expression() -> Result<()> {
|
fn test_binary_expression() -> Result<()> {
|
||||||
let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap();
|
let expr = parser!("4 ** 2 + 5 ** 2;").parse()?.unwrap();
|
||||||
assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string());
|
assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string());
|
||||||
|
|
||||||
let expr = parser!("2 ** 3 ** 4").parse()?.unwrap();
|
let expr = parser!("2 ** 3 ** 4;").parse()?.unwrap();
|
||||||
assert_eq!("(2 ** (3 ** 4))", expr.to_string());
|
assert_eq!("(2 ** (3 ** 4))", expr.to_string());
|
||||||
|
|
||||||
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap();
|
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2;").parse()?.unwrap();
|
||||||
assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());
|
assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());
|
||||||
|
|
||||||
let expr = parser!("(5 - 2) * 10").parse()?.unwrap();
|
let expr = parser!("(5 - 2) * 10;").parse()?.unwrap();
|
||||||
assert_eq!("((5 - 2) * 10)", expr.to_string());
|
assert_eq!("((5 - 2) * 10)", expr.to_string());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -74,13 +74,13 @@ impl std::fmt::Display for LogicalExpression {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct AssignmentExpression {
|
pub struct AssignmentExpression {
|
||||||
pub identifier: Spanned<String>,
|
pub assignee: Box<Spanned<Expression>>,
|
||||||
pub expression: Box<Spanned<Expression>>,
|
pub expression: Box<Spanned<Expression>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for AssignmentExpression {
|
impl std::fmt::Display for AssignmentExpression {
|
||||||
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.identifier, self.expression)
|
write!(f, "({} = {})", self.assignee, self.expression)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +145,41 @@ impl std::fmt::Display for InvocationExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct MemberAccessExpression {
|
||||||
|
pub object: Box<Spanned<Expression>>,
|
||||||
|
pub member: Spanned<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for MemberAccessExpression {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}.{}", self.object, self.member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct MethodCallExpression {
|
||||||
|
pub object: Box<Spanned<Expression>>,
|
||||||
|
pub method: Spanned<String>,
|
||||||
|
pub arguments: Vec<Spanned<Expression>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for MethodCallExpression {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}.{}({})",
|
||||||
|
self.object,
|
||||||
|
self.method,
|
||||||
|
self.arguments
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum LiteralOrVariable {
|
pub enum LiteralOrVariable {
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
@@ -160,6 +195,18 @@ impl std::fmt::Display for LiteralOrVariable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ConstDeclarationExpression {
|
||||||
|
pub name: Spanned<String>,
|
||||||
|
pub value: Spanned<Literal>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ConstDeclarationExpression {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "(const {} = {})", self.name, self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct DeviceDeclarationExpression {
|
pub struct DeviceDeclarationExpression {
|
||||||
/// any variable-like name
|
/// any variable-like name
|
||||||
@@ -281,6 +328,7 @@ pub enum Expression {
|
|||||||
Binary(Spanned<BinaryExpression>),
|
Binary(Spanned<BinaryExpression>),
|
||||||
Block(Spanned<BlockExpression>),
|
Block(Spanned<BlockExpression>),
|
||||||
Break(Span),
|
Break(Span),
|
||||||
|
ConstDeclaration(Spanned<ConstDeclarationExpression>),
|
||||||
Continue(Span),
|
Continue(Span),
|
||||||
Declaration(Spanned<String>, Box<Spanned<Expression>>),
|
Declaration(Spanned<String>, Box<Spanned<Expression>>),
|
||||||
DeviceDeclaration(Spanned<DeviceDeclarationExpression>),
|
DeviceDeclaration(Spanned<DeviceDeclarationExpression>),
|
||||||
@@ -290,6 +338,8 @@ pub enum Expression {
|
|||||||
Literal(Spanned<Literal>),
|
Literal(Spanned<Literal>),
|
||||||
Logical(Spanned<LogicalExpression>),
|
Logical(Spanned<LogicalExpression>),
|
||||||
Loop(Spanned<LoopExpression>),
|
Loop(Spanned<LoopExpression>),
|
||||||
|
MemberAccess(Spanned<MemberAccessExpression>),
|
||||||
|
MethodCall(Spanned<MethodCallExpression>),
|
||||||
Negation(Box<Spanned<Expression>>),
|
Negation(Box<Spanned<Expression>>),
|
||||||
Priority(Box<Spanned<Expression>>),
|
Priority(Box<Spanned<Expression>>),
|
||||||
Return(Box<Spanned<Expression>>),
|
Return(Box<Spanned<Expression>>),
|
||||||
@@ -305,6 +355,7 @@ impl std::fmt::Display for Expression {
|
|||||||
Expression::Binary(e) => write!(f, "{}", e),
|
Expression::Binary(e) => write!(f, "{}", e),
|
||||||
Expression::Block(e) => write!(f, "{}", e),
|
Expression::Block(e) => write!(f, "{}", e),
|
||||||
Expression::Break(_) => write!(f, "break"),
|
Expression::Break(_) => write!(f, "break"),
|
||||||
|
Expression::ConstDeclaration(e) => write!(f, "{}", e),
|
||||||
Expression::Continue(_) => write!(f, "continue"),
|
Expression::Continue(_) => write!(f, "continue"),
|
||||||
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
|
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
|
||||||
Expression::DeviceDeclaration(e) => write!(f, "{}", e),
|
Expression::DeviceDeclaration(e) => write!(f, "{}", e),
|
||||||
@@ -314,6 +365,8 @@ impl std::fmt::Display for Expression {
|
|||||||
Expression::Literal(l) => write!(f, "{}", l),
|
Expression::Literal(l) => write!(f, "{}", l),
|
||||||
Expression::Logical(e) => write!(f, "{}", e),
|
Expression::Logical(e) => write!(f, "{}", e),
|
||||||
Expression::Loop(e) => write!(f, "{}", e),
|
Expression::Loop(e) => write!(f, "{}", e),
|
||||||
|
Expression::MemberAccess(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 {})", e),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
rust_decimal = { workspace = true }
|
rust_decimal = { workspace = true }
|
||||||
quick-error = { workspace = true }
|
quick-error = { workspace = true }
|
||||||
lsp-types = { workspace = true }
|
lsp-types = { workspace = true }
|
||||||
|
helpers = { path = "../helpers" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { version = "^1" }
|
anyhow = { version = "^1" }
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ impl<'a> Tokenizer<'a> {
|
|||||||
.tokenize_symbol(next_char, start_line, start_col)
|
.tokenize_symbol(next_char, start_line, start_col)
|
||||||
.map(Some);
|
.map(Some);
|
||||||
}
|
}
|
||||||
char if char.is_alphabetic() => {
|
char if char.is_alphabetic() || char == '_' => {
|
||||||
return self
|
return self
|
||||||
.tokenize_keyword_or_identifier(next_char, start_line, start_col)
|
.tokenize_keyword_or_identifier(next_char, start_line, start_col)
|
||||||
.map(Some);
|
.map(Some);
|
||||||
@@ -439,14 +439,15 @@ impl<'a> Tokenizer<'a> {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
macro_rules! next_ws {
|
macro_rules! next_ws {
|
||||||
() => { matches!(self.peek_next_char()?, Some(x) if x.is_whitespace() || !x.is_alphanumeric()) || self.peek_next_char()?.is_none() };
|
() => { matches!(self.peek_next_char()?, Some(x) if x.is_whitespace() || (!x.is_alphanumeric()) && x != '_') || self.peek_next_char()?.is_none() };
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buffer = String::with_capacity(16);
|
let mut buffer = String::with_capacity(16);
|
||||||
let mut looped_char = Some(first_char);
|
let mut looped_char = Some(first_char);
|
||||||
|
|
||||||
while let Some(next_char) = looped_char {
|
while let Some(next_char) = looped_char {
|
||||||
if next_char.is_whitespace() || !next_char.is_alphanumeric() {
|
// allow UNDERSCORE_IDENTS
|
||||||
|
if next_char.is_whitespace() || (!next_char.is_alphanumeric() && next_char != '_') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buffer.push(next_char);
|
buffer.push(next_char);
|
||||||
@@ -463,6 +464,7 @@ impl<'a> Tokenizer<'a> {
|
|||||||
"break" if next_ws!() => keyword!(Break),
|
"break" if next_ws!() => keyword!(Break),
|
||||||
"while" if next_ws!() => keyword!(While),
|
"while" if next_ws!() => keyword!(While),
|
||||||
"continue" if next_ws!() => keyword!(Continue),
|
"continue" if next_ws!() => keyword!(Continue),
|
||||||
|
"const" if next_ws!() => keyword!(Const),
|
||||||
"true" if next_ws!() => {
|
"true" if next_ws!() => {
|
||||||
return Ok(Token::new(
|
return Ok(Token::new(
|
||||||
TokenType::Boolean(true),
|
TokenType::Boolean(true),
|
||||||
@@ -837,7 +839,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keyword_parse() -> Result<()> {
|
fn test_keyword_parse() -> Result<()> {
|
||||||
let mut tokenizer = Tokenizer::from(String::from("let fn if else return enum"));
|
let mut tokenizer = Tokenizer::from(String::from(
|
||||||
|
"let fn if else return enum continue break const",
|
||||||
|
));
|
||||||
|
|
||||||
let expected_tokens = vec![
|
let expected_tokens = vec![
|
||||||
TokenType::Keyword(Keyword::Let),
|
TokenType::Keyword(Keyword::Let),
|
||||||
@@ -846,6 +850,9 @@ mod tests {
|
|||||||
TokenType::Keyword(Keyword::Else),
|
TokenType::Keyword(Keyword::Else),
|
||||||
TokenType::Keyword(Keyword::Return),
|
TokenType::Keyword(Keyword::Return),
|
||||||
TokenType::Keyword(Keyword::Enum),
|
TokenType::Keyword(Keyword::Enum),
|
||||||
|
TokenType::Keyword(Keyword::Continue),
|
||||||
|
TokenType::Keyword(Keyword::Break),
|
||||||
|
TokenType::Keyword(Keyword::Const),
|
||||||
];
|
];
|
||||||
|
|
||||||
for expected_token in expected_tokens {
|
for expected_token in expected_tokens {
|
||||||
@@ -859,7 +866,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_identifier_parse() -> Result<()> {
|
fn test_identifier_parse() -> Result<()> {
|
||||||
let mut tokenizer = Tokenizer::from(String::from("fn test"));
|
let mut tokenizer = Tokenizer::from(String::from("fn test fn test_underscores"));
|
||||||
|
|
||||||
let token = tokenizer.next_token()?.unwrap();
|
let token = tokenizer.next_token()?.unwrap();
|
||||||
assert_eq!(token.token_type, TokenType::Keyword(Keyword::Fn));
|
assert_eq!(token.token_type, TokenType::Keyword(Keyword::Fn));
|
||||||
@@ -868,6 +875,13 @@ mod tests {
|
|||||||
token.token_type,
|
token.token_type,
|
||||||
TokenType::Identifier(String::from("test"))
|
TokenType::Identifier(String::from("test"))
|
||||||
);
|
);
|
||||||
|
let token = tokenizer.next_token()?.unwrap();
|
||||||
|
assert_eq!(token.token_type, TokenType::Keyword(Keyword::Fn));
|
||||||
|
let token = tokenizer.next_token()?.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
token.token_type,
|
||||||
|
TokenType::Identifier(String::from("test_underscores"))
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
use helpers::prelude::*;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
// Define a local macro to consume the list
|
||||||
|
macro_rules! generate_check {
|
||||||
|
($($name:literal),*) => {
|
||||||
|
pub fn is_syscall(s: &str) -> bool {
|
||||||
|
matches!(s, $($name)|*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
/// The type of the token
|
/// The type of the token
|
||||||
@@ -87,17 +97,56 @@ pub enum TokenType {
|
|||||||
EOF,
|
EOF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Documentation for TokenType {
|
||||||
|
fn docs(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Keyword(k) => k.docs(),
|
||||||
|
_ => "".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all_documentation() -> Vec<(&'static str, String)> {
|
||||||
|
Keyword::get_all_documentation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helpers::with_syscalls!(generate_check);
|
||||||
|
|
||||||
impl From<TokenType> for u32 {
|
impl From<TokenType> for u32 {
|
||||||
fn from(value: TokenType) -> Self {
|
fn from(value: TokenType) -> Self {
|
||||||
use TokenType::*;
|
|
||||||
match value {
|
match value {
|
||||||
String(_) => 1,
|
TokenType::String(_) => 1,
|
||||||
Number(_) => 2,
|
TokenType::Number(_) => 2,
|
||||||
Boolean(_) => 3,
|
TokenType::Boolean(_) => 3,
|
||||||
Keyword(_) => 4,
|
TokenType::Keyword(k) => match k {
|
||||||
Identifier(_) => 5,
|
Keyword::If
|
||||||
Symbol(_) => 6,
|
| Keyword::Else
|
||||||
EOF => 0,
|
| Keyword::Loop
|
||||||
|
| Keyword::While
|
||||||
|
| Keyword::Break
|
||||||
|
| Keyword::Continue
|
||||||
|
| Keyword::Return => 4,
|
||||||
|
_ => 5,
|
||||||
|
},
|
||||||
|
TokenType::Identifier(s) => {
|
||||||
|
if is_syscall(&s) {
|
||||||
|
10
|
||||||
|
} else {
|
||||||
|
6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenType::Symbol(s) => {
|
||||||
|
if s.is_comparison() {
|
||||||
|
11
|
||||||
|
} else if s.is_operator() {
|
||||||
|
12
|
||||||
|
} else if s.is_logical() {
|
||||||
|
13
|
||||||
|
} else {
|
||||||
|
7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenType::EOF => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,28 +313,147 @@ impl std::fmt::Display for Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
documented! {
|
||||||
pub enum Keyword {
|
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||||
/// Represents the `continue` keyword
|
pub enum Keyword {
|
||||||
Continue,
|
/// Represents the `continue` keyword. This will allow you to bypass the current iteration in a loop and start the next one.
|
||||||
/// Represents the `let` keyword
|
/// ## Example
|
||||||
Let,
|
/// ```
|
||||||
/// Represents the `fn` keyword
|
/// let item = 0;
|
||||||
Fn,
|
/// loop {
|
||||||
/// Represents the `if` keyword
|
/// if (item % 2 == 0) {
|
||||||
If,
|
/// // This will NOT increment `item` and will continue with the next iteration of the
|
||||||
/// Represents the `device` keyword. Useful for defining a device at a specific address (ex. d0, d1, d2, etc.)
|
/// // loop
|
||||||
Device,
|
/// continue;
|
||||||
/// Represents the `else` keyword
|
/// }
|
||||||
Else,
|
/// item = item + 1;
|
||||||
/// Represents the `return` keyword
|
/// }
|
||||||
Return,
|
/// ```
|
||||||
/// Represents the `enum` keyword
|
Continue,
|
||||||
Enum,
|
/// Prepresents the `const` keyword. This allows you to define a variable that will never
|
||||||
/// Represents the `loop` keyword
|
/// change throughout the lifetime of the program, similar to `define` in IC10. If you are
|
||||||
Loop,
|
/// not planning on mutating the variable (changing it), it is recommend you store it as a
|
||||||
/// Represents the `break` keyword
|
/// const, as the compiler will not assign it to a register or stack variable.
|
||||||
Break,
|
///
|
||||||
/// Represents the `while` keyword
|
/// ## Example
|
||||||
While,
|
/// ```
|
||||||
|
/// const targetTemp = 20c;
|
||||||
|
/// device gasSensor = "d0";
|
||||||
|
/// device airCon = "d1";
|
||||||
|
///
|
||||||
|
/// airCon.On = gasSensor.Temperature > targetTemp;
|
||||||
|
/// ```
|
||||||
|
Const,
|
||||||
|
/// Represents the `let` keyword, used to declare variables within Slang.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// // This variable now exists either in a register or the stack depending on how many
|
||||||
|
/// // free registers were available when declaring it.
|
||||||
|
/// let item = 0;
|
||||||
|
/// ```
|
||||||
|
Let,
|
||||||
|
/// Represents the `fn` keyword, used to declare functions within Slang.
|
||||||
|
/// # WARNING
|
||||||
|
/// Functions are currently unstable and are subject to change until stabilized. Use at
|
||||||
|
/// your own risk! (They are also heavily not optimized and produce a LOT of code bloat)
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// // This allows you to now call `doSomething` with specific arguments.
|
||||||
|
/// fn doSomething(arg1, arg2) {
|
||||||
|
///
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
Fn,
|
||||||
|
/// Represents the `if` keyword, allowing you to create branched logic.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// let i = 0;
|
||||||
|
/// if (i == 0) {
|
||||||
|
/// i = 1;
|
||||||
|
/// }
|
||||||
|
/// // At this line, `i` is now `1`
|
||||||
|
/// ```
|
||||||
|
If,
|
||||||
|
/// Represents the `device` keyword. Useful for defining a device at a specific address
|
||||||
|
/// (ex. d0, d1, d2, etc.). This also allows you to perform direct operations ON a device.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// device self = "db";
|
||||||
|
///
|
||||||
|
/// // This is the same as `s db Setting 123`
|
||||||
|
/// self.Setting = 123;
|
||||||
|
/// ```
|
||||||
|
Device,
|
||||||
|
/// Represents the `else` keyword. Useful if you want to check a condition but run run
|
||||||
|
/// seperate logic in case that condition fails.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// device self = "db";
|
||||||
|
/// let i = 0;
|
||||||
|
/// if (i < 0) {
|
||||||
|
/// self.Setting = 0;
|
||||||
|
/// } else {
|
||||||
|
/// self.Setting = 1;
|
||||||
|
/// }
|
||||||
|
/// // Here, the `Setting` on the current housing is `1` because i was NOT less than 0
|
||||||
|
/// ```
|
||||||
|
Else,
|
||||||
|
/// Represents the `return` keyword. Allows you to pass values from a function back to
|
||||||
|
/// the caller.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// fn doSomething() {
|
||||||
|
/// return 1 + 2;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // `returnedValue` now holds the value `3`
|
||||||
|
/// let returnedValue = doSomething();
|
||||||
|
/// ```
|
||||||
|
Return,
|
||||||
|
/// Represents the `enum` keyword. This is currently not supported, but is kept as a
|
||||||
|
/// reserved keyword in the future case that this is implemented.
|
||||||
|
Enum,
|
||||||
|
/// Represents the `loop` keyword. This allows you to create an infinate loop, but can be
|
||||||
|
/// broken with the `break` keyword.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// device self = "db";
|
||||||
|
/// let i = 0;
|
||||||
|
/// loop {
|
||||||
|
/// i = i + 1;
|
||||||
|
/// // The current housing will infinately increment it's `Setting` value.
|
||||||
|
/// self.Setting = i;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
Loop,
|
||||||
|
/// Represents the `break` keyword. This allows you to "break out of" a loop prematurely,
|
||||||
|
/// such as when an if() conditon is true, etc.
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// let i = 0;
|
||||||
|
/// // This loop will run until the value of `i` is greater than 10,000,
|
||||||
|
/// // which will then trigger the `break` keyword and it will stop looping
|
||||||
|
/// loop {
|
||||||
|
/// if (i > 10_000) {
|
||||||
|
/// break;
|
||||||
|
/// }
|
||||||
|
/// i = i + 1;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
Break,
|
||||||
|
/// Represents the `while` keyword. This is similar to the `loop` keyword but different in
|
||||||
|
/// that you don't need an `if` statement to break out of a loop, that is handled
|
||||||
|
/// automatically when invoking `while`
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// let i = 0;
|
||||||
|
/// // This loop will run until the value of `i` is greater than 10,000, in which case the
|
||||||
|
/// // while loop will automatically stop running and code will continue AFTER the last
|
||||||
|
/// // bracket.
|
||||||
|
/// while (i < 10_000) {
|
||||||
|
/// i = i + 1;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
While,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use compiler::Compiler;
|
use compiler::Compiler;
|
||||||
use parser::Parser;
|
use helpers::Documentation;
|
||||||
|
use parser::{sys_call::SysCall, Parser};
|
||||||
use safer_ffi::prelude::*;
|
use safer_ffi::prelude::*;
|
||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use tokenizer::{
|
use tokenizer::{
|
||||||
@@ -26,6 +27,13 @@ pub struct FfiRange {
|
|||||||
end_line: u32,
|
end_line: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FfiDocumentedItem {
|
||||||
|
item_name: safer_ffi::String,
|
||||||
|
docs: safer_ffi::String,
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -76,6 +84,11 @@ pub fn free_string(s: safer_ffi::String) {
|
|||||||
drop(s)
|
drop(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
pub fn free_docs_vec(v: safer_ffi::Vec<FfiDocumentedItem>) {
|
||||||
|
drop(v)
|
||||||
|
}
|
||||||
|
|
||||||
/// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
|
/// 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.
|
/// 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
|
/// This should result in the ability to compile many times without triggering frame drops
|
||||||
@@ -151,8 +164,8 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
|
|||||||
column: column as i32,
|
column: column as i32,
|
||||||
error: "".into(),
|
error: "".into(),
|
||||||
length: (original_string.unwrap_or_default().len()) as i32,
|
length: (original_string.unwrap_or_default().len()) as i32,
|
||||||
|
tooltip: token_type.docs().into(),
|
||||||
token_kind: token_type.into(),
|
token_kind: token_type.into(),
|
||||||
tooltip: "".into(),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,3 +196,26 @@ pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<
|
|||||||
|
|
||||||
res.unwrap_or(vec![].into())
|
res.unwrap_or(vec![].into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
pub fn get_docs() -> safer_ffi::Vec<FfiDocumentedItem> {
|
||||||
|
let res = std::panic::catch_unwind(|| {
|
||||||
|
let mut docs = SysCall::get_all_documentation();
|
||||||
|
docs.extend(TokenType::get_all_documentation());
|
||||||
|
|
||||||
|
docs
|
||||||
|
});
|
||||||
|
|
||||||
|
let Ok(result) = res else {
|
||||||
|
return vec![].into();
|
||||||
|
};
|
||||||
|
|
||||||
|
result
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, doc)| FfiDocumentedItem {
|
||||||
|
item_name: key.into(),
|
||||||
|
docs: doc.into(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user