diff --git a/Changelog.md b/Changelog.md index d563491..46736d6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,11 @@ # Changelog +[0.2.4] + +- Groundwork laid to collect and track source maps +- IC Housing will now display the `Slang` source error line (if available) + instead of the `IC10` source error line + [0.2.3] - Fixed stack underflow with function invocations diff --git a/ModData/About/About.xml b/ModData/About/About.xml index ae84c45..ef16119 100644 --- a/ModData/About/About.xml +++ b/ModData/About/About.xml @@ -2,7 +2,7 @@ Slang JoeDiertay - 0.2.3 + 0.2.4 [h1]Slang: High-Level Programming for Stationeers[/h1] diff --git a/csharp_mod/Extensions.cs b/csharp_mod/Extensions.cs index cefd685..dfab846 100644 --- a/csharp_mod/Extensions.cs +++ b/csharp_mod/Extensions.cs @@ -113,6 +113,34 @@ public static unsafe class SlangExtensions return toReturn; } + public static unsafe List ToList(this Vec_FfiSourceMapEntry_t vec) + { + var toReturn = new List((int)vec.len); + + var currentPtr = vec.ptr; + + for (int i = 0; i < (int)vec.len; i++) + { + var item = currentPtr[i]; + + toReturn.Add( + new SourceMapEntry + { + Ic10Line = item.line_number, + SlangSource = new Range + { + EndCol = item.span.end_col, + EndLine = item.span.end_line, + StartCol = item.span.start_col, + StartLine = item.span.start_line, + }, + } + ); + } + + return toReturn; + } + private static uint GetColorForKind(uint kind) { switch (kind) diff --git a/csharp_mod/FfiGlue.cs b/csharp_mod/FfiGlue.cs index 3489eb4..c2eb521 100644 --- a/csharp_mod/FfiGlue.cs +++ b/csharp_mod/FfiGlue.cs @@ -71,18 +71,6 @@ public unsafe struct Vec_uint8_t { public UIntPtr cap; } -public unsafe partial class Ffi { - /// - /// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because - /// we want to avoid GC. So we pass it to Rust to handle all the memory allocations. - /// This should result in the ability to compile many times without triggering frame drops - /// from the GC from a GetBytes() call on a string in C#. - /// - [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern - Vec_uint8_t compile_from_string ( - slice_ref_uint16_t input); -} - [StructLayout(LayoutKind.Sequential, Size = 16)] public unsafe struct FfiRange_t { public UInt32 start_col; @@ -94,6 +82,44 @@ public unsafe struct FfiRange_t { public UInt32 end_line; } +[StructLayout(LayoutKind.Sequential, Size = 20)] +public unsafe struct FfiSourceMapEntry_t { + public UInt32 line_number; + + public FfiRange_t span; +} + +/// +/// Same as [Vec][rust::Vec], but with guaranteed #[repr(C)] layout +/// +[StructLayout(LayoutKind.Sequential, Size = 24)] +public unsafe struct Vec_FfiSourceMapEntry_t { + public FfiSourceMapEntry_t * ptr; + + public UIntPtr len; + + public UIntPtr cap; +} + +[StructLayout(LayoutKind.Sequential, Size = 48)] +public unsafe struct FfiCompilationResult_t { + public Vec_uint8_t output_code; + + public Vec_FfiSourceMapEntry_t source_map; +} + +public unsafe partial class Ffi { + /// + /// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because + /// we want to avoid GC. So we pass it to Rust to handle all the memory allocations. + /// This should result in the ability to compile many times without triggering frame drops + /// from the GC from a GetBytes() call on a string in C#. + /// + [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern + FfiCompilationResult_t compile_from_string ( + slice_ref_uint16_t input); +} + [StructLayout(LayoutKind.Sequential, Size = 48)] public unsafe struct FfiDiagnostic_t { public Vec_uint8_t message; @@ -146,6 +172,12 @@ public unsafe partial class Ffi { Vec_FfiDocumentedItem_t v); } +public unsafe partial class Ffi { + [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern + void free_ffi_compilation_result ( + FfiCompilationResult_t input); +} + public unsafe partial class Ffi { [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern void free_ffi_diagnostic_vec ( diff --git a/csharp_mod/GlobalCode.cs b/csharp_mod/GlobalCode.cs index 181a2f6..bd9c5b2 100644 --- a/csharp_mod/GlobalCode.cs +++ b/csharp_mod/GlobalCode.cs @@ -15,11 +15,59 @@ public static class GlobalCode // so that save file data is smaller private static Dictionary codeDict = new(); + // This Dictionary stores the source maps for the given SLANG_REF, where + // the key is the IC10 line, and the value is a List of Slang ranges where that + // line would have come from + private static Dictionary>> sourceMaps = new(); + public static void ClearCache() { codeDict.Clear(); } + public static void SetSourceMap(Guid reference, List sourceMapEntries) + { + var builtDictionary = new Dictionary>(); + + foreach (var entry in sourceMapEntries) + { + if (!builtDictionary.ContainsKey(entry.Ic10Line)) + { + builtDictionary[entry.Ic10Line] = new(); + } + builtDictionary[entry.Ic10Line].Add(entry.SlangSource); + } + + sourceMaps[reference] = builtDictionary; + } + + public static bool GetSlangErrorLineFromICError( + Guid reference, + uint icErrorLine, + out uint slangSrc, + out Range slangSpan + ) + { + slangSrc = icErrorLine; + slangSpan = new Range { }; + + if (!sourceMaps.ContainsKey(reference)) + { + return false; + } + + var foundRange = sourceMaps[reference][icErrorLine]; + + if (foundRange is null) + { + return false; + } + + slangSrc = foundRange[0].StartLine; + slangSpan = foundRange[0]; + return true; + } + public static string GetSource(Guid reference) { if (!codeDict.ContainsKey(reference)) diff --git a/csharp_mod/Marshal.cs b/csharp_mod/Marshal.cs index 1163b74..d5fb834 100644 --- a/csharp_mod/Marshal.cs +++ b/csharp_mod/Marshal.cs @@ -10,10 +10,23 @@ using StationeersIC10Editor; public struct Range { - public uint StartCol; - public uint EndCol; - public uint StartLine; - public uint EndLine; + public uint StartCol = 0; + public uint EndCol = 0; + public uint StartLine = 0; + public uint EndLine = 0; + + public Range(uint startLine, uint startCol, uint endLine, uint endCol) + { + StartLine = startLine; + StartCol = startCol; + EndLine = endLine; + EndCol = endCol; + } + + public override string ToString() + { + return $"L{StartLine}C{StartCol} - L{EndLine}C{EndCol}"; + } } public struct Diagnostic @@ -23,6 +36,17 @@ public struct Diagnostic public Range Range; } +public struct SourceMapEntry +{ + public Range SlangSource; + public uint Ic10Line; + + public override string ToString() + { + return $"IC10: {Ic10Line} Slang: `{SlangSource}`"; + } +} + public static class Marshal { private static IntPtr _libraryHandle = IntPtr.Zero; @@ -78,11 +102,16 @@ public static class Marshal } } - public static unsafe bool CompileFromString(string inputString, out string compiledString) + public static unsafe bool CompileFromString( + string inputString, + out string compiledString, + out List sourceMapEntries + ) { if (String.IsNullOrEmpty(inputString) || !EnsureLibLoaded()) { compiledString = String.Empty; + sourceMapEntries = new(); return false; } @@ -95,19 +124,16 @@ public static class Marshal }; var result = Ffi.compile_from_string(input); + try { - if ((ulong)result.len < 1) - { - compiledString = String.Empty; - return false; - } - compiledString = result.AsString(); + sourceMapEntries = result.source_map.ToList(); + compiledString = result.output_code.AsString(); return true; } finally { - result.Drop(); + Ffi.free_ffi_compilation_result(result); } } } diff --git a/csharp_mod/Patches.cs b/csharp_mod/Patches.cs index 7f09926..12fb8a2 100644 --- a/csharp_mod/Patches.cs +++ b/csharp_mod/Patches.cs @@ -1,12 +1,34 @@ namespace Slang; using System; +using System.Runtime.CompilerServices; using Assets.Scripts.Objects; using Assets.Scripts.Objects.Electrical; using Assets.Scripts.Objects.Motherboards; using Assets.Scripts.UI; using HarmonyLib; +class LineErrorData +{ + public AsciiString SourceRef; + public uint IC10ErrorSource; + public string SlangErrorReference; + public Range SlangErrorSpan; + + public LineErrorData( + AsciiString sourceRef, + uint ic10ErrorSource, + string slangErrorRef, + Range slangErrorSpan + ) + { + this.SourceRef = sourceRef; + this.IC10ErrorSource = ic10ErrorSource; + this.SlangErrorReference = slangErrorRef; + this.SlangErrorSpan = slangErrorSpan; + } +} + [HarmonyPatch] public static class SlangPatches { @@ -14,6 +36,9 @@ public static class SlangPatches private static AsciiString? _motherboardCachedCode; private static Guid? _currentlyEditingGuid; + private static ConditionalWeakTable _errorReferenceTable = + new(); + [HarmonyPatch( typeof(ProgrammableChipMotherboard), nameof(ProgrammableChipMotherboard.InputFinished) @@ -26,7 +51,7 @@ public static class SlangPatches // guard to ensure we have valid IC10 before continuing if ( !SlangPlugin.IsSlangSource(ref result) - || !Marshal.CompileFromString(result, out string compiled) + || !Marshal.CompileFromString(result, out var compiled, out var sourceMap) || string.IsNullOrEmpty(compiled) ) { @@ -37,6 +62,7 @@ public static class SlangPatches // Ensure we cache this compiled code for later retreival. GlobalCode.SetSource(thisRef, result); + GlobalCode.SetSourceMap(thisRef, sourceMap); _currentlyEditingGuid = null; @@ -140,6 +166,100 @@ public static class SlangPatches chipData.SourceCode = code; } + [HarmonyPatch( + typeof(ProgrammableChip), + nameof(ProgrammableChip.ErrorLineNumberString), + MethodType.Getter + )] + [HarmonyPostfix] + public static void pgc_ErrorLineNumberString(ProgrammableChip __instance, ref string __result) + { + if ( + String.IsNullOrEmpty(__result) + || !uint.TryParse(__result.Trim(), out var ic10ErrorLineNumber) + ) + { + return; + } + + var sourceAscii = __instance.GetSourceCode(); + + if (_errorReferenceTable.TryGetValue(__instance, out var cache)) + { + if (cache.SourceRef.Equals(sourceAscii) && cache.IC10ErrorSource == ic10ErrorLineNumber) + { + __result = cache.SlangErrorReference; + return; + } + } + + var source = System.Text.Encoding.UTF8.GetString( + System.Text.Encoding.ASCII.GetBytes(__instance.GetSourceCode()) + ); + + var slangIndex = source.LastIndexOf(GlobalCode.SLANG_REF); + + if ( + slangIndex < 0 + || !Guid.TryParse( + source + .Substring( + source.LastIndexOf(GlobalCode.SLANG_REF) + GlobalCode.SLANG_REF.Length + ) + .Trim(), + out var slangGuid + ) + || !GlobalCode.GetSlangErrorLineFromICError( + slangGuid, + ic10ErrorLineNumber, + out var slangErrorLineNumber, + out var slangSpan + ) + ) + { + return; + } + + L.Warning($"IC error at: {__result} -- Slang source error line: {slangErrorLineNumber}"); + __result = slangErrorLineNumber.ToString(); + _errorReferenceTable.Remove(__instance); + _errorReferenceTable.Add( + __instance, + new LineErrorData( + sourceAscii, + ic10ErrorLineNumber, + slangErrorLineNumber.ToString(), + slangSpan + ) + ); + } + + [HarmonyPatch( + typeof(ProgrammableChip), + nameof(ProgrammableChip.SetSourceCode), + new Type[] { typeof(string) } + )] + [HarmonyPostfix] + public static void pgc_SetSourceCode_string(ProgrammableChip __instance, string sourceCode) + { + _errorReferenceTable.Remove(__instance); + } + + [HarmonyPatch( + typeof(ProgrammableChip), + nameof(ProgrammableChip.SetSourceCode), + new Type[] { typeof(string), typeof(ICircuitHolder) } + )] + [HarmonyPostfix] + public static void pgc_SetSourceCode_string_parent( + ProgrammableChip __instance, + string sourceCode, + ICircuitHolder parent + ) + { + _errorReferenceTable.Remove(__instance); + } + [HarmonyPatch( typeof(ProgrammableChipMotherboard), nameof(ProgrammableChipMotherboard.SerializeSave) diff --git a/csharp_mod/Plugin.cs b/csharp_mod/Plugin.cs index fa576fe..6f8266c 100644 --- a/csharp_mod/Plugin.cs +++ b/csharp_mod/Plugin.cs @@ -41,7 +41,7 @@ namespace Slang { public const string PluginGuid = "com.biddydev.slang"; public const string PluginName = "Slang"; - public const string PluginVersion = "0.1.1"; + public const string PluginVersion = "0.2.4"; public static Mod MOD = new Mod(PluginName, PluginVersion); diff --git a/csharp_mod/stationeersSlang.csproj b/csharp_mod/stationeersSlang.csproj index 445cd13..3564bc4 100644 --- a/csharp_mod/stationeersSlang.csproj +++ b/csharp_mod/stationeersSlang.csproj @@ -5,7 +5,7 @@ enable StationeersSlang Slang Compiler Bridge - 0.2.3 + 0.2.4 true latest diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index e60ec2f..ca53d0a 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -571,6 +571,7 @@ dependencies = [ "helpers", "lsp-types", "pretty_assertions", + "safer-ffi", "thiserror", "tokenizer", ] @@ -909,7 +910,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slang" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "clap", diff --git a/rust_compiler/Cargo.toml b/rust_compiler/Cargo.toml index 89fff96..fdf339b 100644 --- a/rust_compiler/Cargo.toml +++ b/rust_compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "slang" -version = "0.2.3" +version = "0.2.4" edition = "2021" [workspace] diff --git a/rust_compiler/libs/compiler/src/lib.rs b/rust_compiler/libs/compiler/src/lib.rs index db2ac3f..34f0c8b 100644 --- a/rust_compiler/libs/compiler/src/lib.rs +++ b/rust_compiler/libs/compiler/src/lib.rs @@ -3,4 +3,4 @@ mod test; mod v1; mod variable_manager; -pub use v1::{Compiler, CompilerConfig, Error}; +pub use v1::{CompilationResult, Compiler, CompilerConfig, Error}; diff --git a/rust_compiler/libs/compiler/src/test/mod.rs b/rust_compiler/libs/compiler/src/test/mod.rs index 77d771a..011e834 100644 --- a/rust_compiler/libs/compiler/src/test/mod.rs +++ b/rust_compiler/libs/compiler/src/test/mod.rs @@ -26,7 +26,7 @@ macro_rules! compile { &mut writer, Some(crate::CompilerConfig { debug: true }), ); - compiler.compile() + compiler.compile().errors }}; (debug $source:expr) => {{ diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 4e2714e..911c29a 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -146,13 +146,18 @@ pub struct CompilerConfig { } #[derive(Debug)] -struct CompilationResult<'a> { +struct CompileLocation<'a> { location: VariableLocation<'a>, /// If Some, this is the name of the temporary variable that holds the result. /// It must be freed by the caller when done. temp_name: Option>, } +pub struct CompilationResult<'a> { + pub errors: Vec>, + pub source_map: HashMap>, +} + pub struct Compiler<'a, 'w, W: std::io::Write> { pub parser: ASTParser<'a>, function_locations: HashMap, usize>, @@ -166,6 +171,9 @@ pub struct Compiler<'a, 'w, W: std::io::Write> { label_counter: usize, loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label) current_return_label: Option>, + /// stores (IC10 `line_num`, `Vec`) + pub source_map: HashMap>, + /// Accumulative errors from the compilation process pub errors: Vec>, } @@ -188,11 +196,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { label_counter: 0, loop_stack: Vec::new(), current_return_label: None, + source_map: HashMap::new(), errors: Vec::new(), } } - pub fn compile(mut self) -> Vec> { + pub fn compile(mut self) -> CompilationResult<'a> { let expr = self.parser.parse_all(); // Copy errors from parser @@ -203,11 +212,19 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { // We treat parse_all result as potentially partial let expr = match expr { Ok(Some(expr)) => expr, - Ok(None) => return self.errors, + Ok(None) => { + return CompilationResult { + source_map: self.source_map, + errors: self.errors, + }; + } Err(e) => { // Should be covered by parser.errors, but just in case self.errors.push(Error::Parse(e)); - return self.errors; + return CompilationResult { + errors: self.errors, + source_map: self.source_map, + }; } }; @@ -225,9 +242,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let spanned_root = Spanned { node: expr, span }; - if let Err(e) = self.write_output("j main") { + if let Err(e) = self.write_output("j main", Some(span)) { self.errors.push(e); - return self.errors; + return CompilationResult { + errors: self.errors, + source_map: self.source_map, + }; } let mut scope = VariableScope::default(); @@ -237,12 +257,27 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.errors.push(e); } - self.errors + CompilationResult { + errors: self.errors, + source_map: self.source_map, + } } - fn write_output(&mut self, output: impl Into) -> Result<(), Error<'a>> { + fn write_output( + &mut self, + output: impl Into, + span: Option, + ) -> Result<(), Error<'a>> { self.output.write_all(output.into().as_bytes())?; self.output.write_all(b"\n")?; + + if let Some(span) = span { + self.source_map + .entry(self.current_line) + .or_default() + .push(span); + } + self.current_line += 1; Ok(()) @@ -262,7 +297,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, - ) -> Result>, Error<'a>> { + ) -> Result>, Error<'a>> { match expr.node { Expression::Function(expr_func) => { self.expression_function(expr_func, scope)?; @@ -286,18 +321,18 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }) => self.expression_syscall_system(system, span, scope), Expression::Syscall(Spanned { node: SysCall::Math(math), - .. - }) => self.expression_syscall_math(math, scope), + span, + }) => self.expression_syscall_math(math, span, scope), Expression::While(expr_while) => { self.expression_while(expr_while.node, scope)?; Ok(None) } - Expression::Break(_) => { - self.expression_break()?; + Expression::Break(span) => { + self.expression_break(span)?; Ok(None) } - Expression::Continue(_) => { - self.expression_continue()?; + Expression::Continue(span) => { + self.expression_continue(span)?; Ok(None) } Expression::DeviceDeclaration(expr_dev) => { @@ -329,7 +364,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &temp_loc, Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)), )?; - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: temp_loc, temp_name: Some(temp_name), })) @@ -351,7 +386,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &loc, Cow::from(num.to_string()), )?; - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: loc, temp_name: Some(temp_name), })) @@ -361,7 +396,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let temp_name = self.next_temp_name(); let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; self.emit_variable_assignment(temp_name.clone(), &loc, Cow::from(val))?; - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: loc, temp_name: Some(temp_name), })) @@ -370,21 +405,21 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }, Expression::Variable(name) => { match scope.get_location_of(&name.node, Some(name.span)) { - Ok(loc) => Ok(Some(CompilationResult { + Ok(loc) => Ok(Some(CompileLocation { location: loc, temp_name: None, // User variable, do not free })), Err(_) => { // fallback, check devices if let Some(device) = self.devices.get(&name.node) { - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Device(device.clone()), temp_name: None, })) } else { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Temporary(0), temp_name: None, })) @@ -405,14 +440,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let reg = self.resolve_register(&loc)?; // 3. Emit load instruction: l rX device member - self.write_output(format!("l {} {} {}", reg, device_str, member.node))?; + self.write_output( + format!("l {} {} {}", reg, device_str, member.node), + Some(expr.span), + )?; // 4. Cleanup if let Some(c) = cleanup { scope.free_temp(c, None)?; } - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: loc, temp_name: Some(result_name), })) @@ -437,13 +475,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; - self.write_output(format!("sub {result_reg} 0 {inner_str}"))?; + self.write_output(format!("sub {result_reg} 0 {inner_str}"), Some(expr.span))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: result_loc, temp_name: Some(result_name), })) @@ -490,10 +528,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output(format!("move r{reg} {}{debug_tag}", source_value))?; + self.write_output(format!("move r{reg} {}{debug_tag}", source_value), None)?; } VariableLocation::Stack(_) => { - self.write_output(format!("push {}{debug_tag}", source_value))?; + self.write_output(format!("push {}{debug_tag}", source_value), None)?; } VariableLocation::Constant(_) => { return Err(Error::Unknown( @@ -521,7 +559,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { var_name: Spanned>, expr: Spanned>, scope: &mut VariableScope<'a, '_>, - ) -> Result>, Error<'a>> { + ) -> Result>, Error<'a>> { let name_str = var_name.node; let name_span = var_name.span; @@ -537,7 +575,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &loc, Cow::from(format!("-{neg_num}")), )?; - return Ok(Some(CompilationResult { + return Ok(Some(CompileLocation { location: loc, temp_name: None, })); @@ -593,7 +631,9 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { SysCall::System(s) => { self.expression_syscall_system(s, spanned_call.span, scope)? } - SysCall::Math(m) => self.expression_syscall_math(m, scope)?, + SysCall::Math(m) => { + self.expression_syscall_math(m, spanned_call.span, scope)? + } }; if res.is_none() { @@ -625,7 +665,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(name_span), )?; - if let CompilationResult { + if let CompileLocation { location: VariableLocation::Constant(Literal::Number(num)), .. } = result @@ -686,14 +726,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { format!("r{r}") } VariableLocation::Stack(offset) => { - self.write_output(format!( - "sub r{0} sp {offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{0} db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!("sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER), + Some(expr.span), + )?; + self.write_output( + format!("get r{0} db r{0}", VariableScope::TEMP_STACK_REGISTER), + Some(expr.span), + )?; format!("r{}", VariableScope::TEMP_STACK_REGISTER) } VariableLocation::Constant(_) | VariableLocation::Device(_) => unreachable!(), @@ -769,7 +809,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } }; - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: loc, temp_name, })) @@ -779,7 +819,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: ConstDeclarationExpression<'a>, scope: &mut VariableScope<'a, '_>, - ) -> Result, Error<'a>> { + ) -> Result, Error<'a>> { let ConstDeclarationExpression { name: const_name, value: const_value, @@ -804,7 +844,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { LiteralOr::Literal(Spanned { node, .. }) => node, }; - Ok(CompilationResult { + Ok(CompileLocation { location: scope.define_const(const_name.node, value, Some(const_name.span))?, temp_name: None, }) @@ -820,6 +860,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { expression, } = expr; + let expr_span = expression.span; + match assignee.node { Expression::Variable(identifier) => { let location = match scope.get_location_of(&identifier.node, Some(identifier.span)) @@ -844,19 +886,25 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output(format!("move r{reg} {val_str}{debug_tag}"))?; + self.write_output( + format!("move r{reg} {val_str}{debug_tag}"), + Some(expr_span), + )?; } VariableLocation::Stack(offset) => { // Calculate address: sp - offset - self.write_output(format!( - "sub r{0} sp {offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!("sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER), + Some(expr_span), + )?; // Store value to stack/db at address - self.write_output(format!( - "put db r{0} {val_str}{debug_tag}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!( + "put db r{0} {val_str}{debug_tag}", + VariableScope::TEMP_STACK_REGISTER + ), + Some(expr_span), + )?; } VariableLocation::Constant(_) => { return Err(Error::ConstAssignment(identifier.node, identifier.span)); @@ -877,7 +925,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { 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))?; + self.write_output( + format!("s {} {} {}", device_str, member.node, val_str,), + Some(member.span), + )?; if let Some(c) = dev_cleanup { scope.free_temp(c, None)?; @@ -938,18 +989,18 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { LocationRequest::Stack, None, )?; - self.write_output(format!("push r{register}"))?; + self.write_output(format!("push r{register}"), Some(name.span))?; } for arg in arguments { match arg.node { Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { let num_str = num.to_string(); - self.write_output(format!("push {num_str}"))?; + self.write_output(format!("push {num_str}"), Some(spanned_lit.span))?; } Literal::Boolean(b) => { let val = if b { "1" } else { "0" }; - self.write_output(format!("push {val}"))?; + self.write_output(format!("push {val}"), Some(spanned_lit.span))?; } _ => {} }, @@ -965,24 +1016,30 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match loc { VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { - self.write_output(format!("push r{reg}"))?; + self.write_output(format!("push r{reg}"), Some(var_name.span))?; } VariableLocation::Constant(lit) => { - self.write_output(format!("push {}", extract_literal(lit, false)?))?; + self.write_output( + format!("push {}", extract_literal(lit, false)?), + Some(var_name.span), + )?; } VariableLocation::Stack(stack_offset) => { - self.write_output(format!( - "sub r{0} sp {stack_offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{0} db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "push r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!( + "sub r{0} sp {stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ), + Some(var_name.span), + )?; + self.write_output( + format!("get r{0} db r{0}", VariableScope::TEMP_STACK_REGISTER), + Some(var_name.span), + )?; + self.write_output( + format!("push r{0}", VariableScope::TEMP_STACK_REGISTER), + Some(var_name.span), + )?; } VariableLocation::Device(_) => { return Err(Error::Unknown( @@ -993,24 +1050,27 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } } Expression::Binary(bin_expr) => { + let span = bin_expr.span; // Compile the binary expression to a temp register let result = self.expression_binary(bin_expr, &mut stack)?; let reg_str = self.resolve_register(&result.location)?; - self.write_output(format!("push {reg_str}"))?; + self.write_output(format!("push {reg_str}"), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } Expression::Logical(log_expr) => { + let span = log_expr.span; // Compile the logical expression to a temp register let result = self.expression_logical(log_expr, &mut stack)?; let reg_str = self.resolve_register(&result.location)?; - self.write_output(format!("push {reg_str}"))?; + self.write_output(format!("push {reg_str}"), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } Expression::MemberAccess(access) => { + let span = access.span; // Compile member access to temp and push let result_opt = self.expression( Spanned { @@ -1027,12 +1087,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { if let Some(result) = result_opt { let reg_str = self.resolve_register(&result.location)?; - self.write_output(format!("push {reg_str}"))?; + self.write_output(format!("push {reg_str}"), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } else { - self.write_output("push 0")?; // Should fail ideally + self.write_output("push 0", Some(span))?; // Should fail ideally } } _ => { @@ -1048,7 +1108,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } // jump to the function and store current line in ra - self.write_output(format!("jal {}", name.node))?; + self.write_output(format!("jal {}", name.node), Some(name.span))?; for register in active_registers { let VariableLocation::Stack(stack_offset) = stack @@ -1061,18 +1121,27 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(name.span), )); }; - self.write_output(format!( - "sub r{0} sp {stack_offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{register} db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!( + "sub r{0} sp {stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ), + Some(name.span), + )?; + self.write_output( + format!( + "get r{register} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ), + Some(name.span), + )?; } if stack.stack_offset() > 0 { - self.write_output(format!("sub sp sp {}", stack.stack_offset()))?; + self.write_output( + format!("sub sp sp {}", stack.stack_offset()), + Some(name.span), + )?; } Ok(()) @@ -1109,11 +1178,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { end_label.clone() }; + let cond_span = expr.condition.span; + // Compile Condition let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE (0), jump to else_label - self.write_output(format!("beq {cond_str} 0 {else_label}"))?; + self.write_output(format!("beq {cond_str} 0 {else_label}"), Some(cond_span))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; @@ -1124,22 +1195,18 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.expression_block(expr.body.node, scope)?; // If we have an else branch, we need to jump over it after the 'if' body - if expr.else_branch.is_some() { - self.write_output(format!("j {end_label}"))?; - self.write_output(format!("{else_label}:"))?; + if let Some(else_branch) = expr.else_branch { + self.write_output(format!("j {end_label}"), Some(else_branch.span))?; + self.write_output(format!("{else_label}:"), Some(else_branch.span))?; - match expr - .else_branch - .ok_or(Error::Unknown("Missing else branch. This should not happen and indicates a Compiler Error. Please report to the author.".into(), None))? - .node - { + match else_branch.node { Expression::Block(block) => self.expression_block(block.node, scope)?, Expression::If(if_expr) => self.expression_if(if_expr.node, scope)?, _ => unreachable!("Parser ensures else branch is Block or If"), } } - self.write_output(format!("{end_label}:"))?; + self.write_output(format!("{end_label}:"), Some(expr.body.span))?; Ok(()) } @@ -1156,14 +1223,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.loop_stack .push((start_label.clone(), end_label.clone())); - self.write_output(format!("{start_label}:"))?; + self.write_output(format!("{start_label}:"), Some(expr.body.span))?; // Compile Body self.expression_block(expr.body.node, scope)?; // Jump back to start - self.write_output(format!("j {start_label}"))?; - self.write_output(format!("{end_label}:"))?; + self.write_output(format!("j {start_label}"), Some(expr.body.span))?; + self.write_output(format!("{end_label}:"), Some(expr.body.span))?; self.loop_stack.pop(); @@ -1182,13 +1249,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.loop_stack .push((start_label.clone(), end_label.clone())); - self.write_output(format!("{start_label}:"))?; + let span = expr.condition.span; + self.write_output(format!("{start_label}:"), Some(span))?; // Compile Condition let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE, jump to end - self.write_output(format!("beq {cond_str} 0 {end_label}"))?; + self.write_output(format!("beq {cond_str} 0 {end_label}"), Some(span))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; @@ -1198,17 +1266,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.expression_block(expr.body, scope)?; // Jump back to start - self.write_output(format!("j {start_label}"))?; - self.write_output(format!("{end_label}:"))?; + self.write_output(format!("j {start_label}"), Some(span))?; + self.write_output(format!("{end_label}:"), Some(span))?; self.loop_stack.pop(); Ok(()) } - fn expression_break(&mut self) -> Result<(), Error<'a>> { + fn expression_break(&mut self, span: Span) -> Result<(), Error<'a>> { if let Some((_, end_label)) = self.loop_stack.last() { - self.write_output(format!("j {end_label}"))?; + self.write_output(format!("j {end_label}"), Some(span))?; Ok(()) } else { Err(Error::Unknown( @@ -1218,9 +1286,9 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } } - fn expression_continue(&mut self) -> Result<(), Error<'a>> { + fn expression_continue(&mut self, span: Span) -> Result<(), Error<'a>> { if let Some((start_label, _)) = self.loop_stack.last() { - self.write_output(format!("j {start_label}"))?; + self.write_output(format!("j {start_label}"), Some(span))?; Ok(()) } else { Err(Error::Unknown( @@ -1234,13 +1302,20 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: TernaryExpression<'a>, scope: &mut VariableScope<'a, '_>, - ) -> Result, Error<'a>> { + ) -> Result, Error<'a>> { let TernaryExpression { condition, true_value, false_value, } = expr; + let span = Span { + start_line: condition.span.start_line, + start_col: condition.span.start_col, + end_line: false_value.span.end_line, + end_col: false_value.span.end_col, + }; + let (cond, cond_clean) = self.compile_operand(*condition, scope)?; let (true_val, true_clean) = self.compile_operand(*true_value, scope)?; let (false_val, false_clean) = self.compile_operand(*false_value, scope)?; @@ -1249,10 +1324,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; - self.write_output(format!( - "select {} {} {} {}", - result_reg, cond, true_val, false_val - ))?; + self.write_output( + format!("select {} {} {} {}", result_reg, cond, true_val, false_val), + Some(span), + )?; if let Some(clean) = cond_clean { scope.free_temp(clean, None)?; @@ -1263,7 +1338,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { if let Some(clean) = false_clean { scope.free_temp(clean, None)?; } - Ok(CompilationResult { + Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) @@ -1349,14 +1424,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; let temp_reg = self.resolve_register(&temp_loc)?; - self.write_output(format!( - "sub r{0} sp {offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get {temp_reg} db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!("sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER), + None, + )?; + self.write_output( + format!("get {temp_reg} db r{0}", VariableScope::TEMP_STACK_REGISTER), + None, + )?; // If the original result had a temp name (unlikely for Stack, but possible logic), // we technically should free it if it's not needed, but Stack usually implies it's safe there. @@ -1399,7 +1474,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, - ) -> Result, Error<'a>> { + ) -> Result, Error<'a>> { fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option { let (lhs, rhs) = match &expr { BinaryExpression::Add(l, r) @@ -1446,7 +1521,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } if let Some(const_lit) = fold_binary_expression(&expr.node) { - return Ok(CompilationResult { + return Ok(CompileLocation { location: VariableLocation::Constant(Literal::Number(const_lit)), temp_name: None, }); @@ -1461,6 +1536,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { BinaryExpression::Modulo(l, r) => ("mod", l, r), }; + let span = Span { + start_line: left_expr.span.start_line, + start_col: left_expr.span.start_col, + end_line: right_expr.span.end_line, + end_col: right_expr.span.end_col, + }; + // Compile LHS let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS @@ -1472,7 +1554,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_reg = self.resolve_register(&result_loc)?; // Emit instruction: op result lhs rhs - self.write_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?; + self.write_output( + format!("{op_str} {result_reg} {lhs_str} {rhs_str}"), + Some(span), + )?; // Clean up operand temps if let Some(name) = lhs_cleanup { @@ -1482,7 +1567,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.free_temp(name, None)?; } - Ok(CompilationResult { + Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) @@ -1492,9 +1577,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, - ) -> Result, Error<'a>> { + ) -> Result, Error<'a>> { match expr.node { LogicalExpression::Not(inner) => { + let span = inner.span; let (inner_str, cleanup) = self.compile_operand(*inner, scope)?; let result_name = self.next_temp_name(); @@ -1503,13 +1589,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_reg = self.resolve_register(&result_loc)?; // seq rX rY 0 => if rY == 0 set rX = 1 else rX = 0 - self.write_output(format!("seq {result_reg} {inner_str} 0"))?; + self.write_output(format!("seq {result_reg} {inner_str} 0"), Some(span))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } - Ok(CompilationResult { + Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) @@ -1527,6 +1613,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { LogicalExpression::Not(_) => unreachable!(), }; + let span = Span { + start_line: left_expr.span.start_line, + start_col: left_expr.span.start_col, + end_line: right_expr.span.end_line, + end_col: right_expr.span.end_col, + }; + // Compile LHS let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS @@ -1539,7 +1632,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_reg = self.resolve_register(&result_loc)?; // Emit instruction: op result lhs rhs - self.write_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?; + self.write_output( + format!("{op_str} {result_reg} {lhs_str} {rhs_str}"), + Some(span), + )?; // Clean up operand temps if let Some(name) = lhs_cleanup { @@ -1549,7 +1645,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.free_temp(name, None)?; } - Ok(CompilationResult { + Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) @@ -1591,7 +1687,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { ) && !parent_scope.has_parent() { - self.write_output("main:")?; + self.write_output("main:", Some(expr.span))?; self.declared_main = true; } @@ -1618,7 +1714,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } if scope.stack_offset() > 0 { - self.write_output(format!("sub sp sp {}", scope.stack_offset()))?; + self.write_output(format!("sub sp sp {}", scope.stack_offset()), None)?; } Ok(()) @@ -1631,6 +1727,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { if let Some(expr) = expr { + let span = expr.span; if let Expression::Negation(neg_expr) = &expr.node && let Expression::Literal(spanned_lit) = &neg_expr.node && let Literal::Number(neg_num) = &spanned_lit.node @@ -1650,30 +1747,42 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Ok(loc) => match loc { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output(format!( - "move r{} r{reg} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#returnValue") - ))?; + self.write_output( + format!( + "move r{} r{reg} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#returnValue") + ), + Some(span), + )?; } VariableLocation::Constant(lit) => { let str = extract_literal(lit, false)?; - self.write_output(format!( - "move r{} {str} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#returnValue") - ))? + self.write_output( + format!( + "move r{} {str} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#returnValue") + ), + Some(span), + )? } VariableLocation::Stack(offset) => { - self.write_output(format!( - "sub r{} sp {offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{} db r{}", - VariableScope::RETURN_REGISTER, - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!( + "sub r{} sp {offset}", + VariableScope::TEMP_STACK_REGISTER + ), + Some(span), + )?; + self.write_output( + format!( + "get r{} db r{}", + VariableScope::RETURN_REGISTER, + VariableScope::TEMP_STACK_REGISTER + ), + Some(span), + )?; } VariableLocation::Device(_) => { return Err(Error::Unknown( @@ -1710,30 +1819,31 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { _ => {} }, Expression::Binary(bin_expr) => { + let span = bin_expr.span; let result = self.expression_binary(bin_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; - self.write_output(format!( - "move r{} {}", - VariableScope::RETURN_REGISTER, - result_reg - ))?; + self.write_output( + format!("move r{} {}", VariableScope::RETURN_REGISTER, result_reg), + Some(span), + )?; if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } } Expression::Logical(log_expr) => { + let span = log_expr.span; let result = self.expression_logical(log_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; - self.write_output(format!( - "move r{} {}", - VariableScope::RETURN_REGISTER, - result_reg - ))?; + self.write_output( + format!("move r{} {}", VariableScope::RETURN_REGISTER, result_reg), + Some(span), + )?; if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } } Expression::MemberAccess(access) => { + let span = access.span; // Return result of member access let res_opt = self.expression( Spanned { @@ -1744,13 +1854,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; if let Some(res) = res_opt { let reg = self.resolve_register(&res.location)?; - self.write_output(format!( - "move r{} {}", - VariableScope::RETURN_REGISTER, - reg - ))?; + self.write_output( + format!("move r{} {}", VariableScope::RETURN_REGISTER, reg), + Some(span), + )?; if let Some(temp) = res.temp_name { - scope.free_temp(temp, None)?; + scope.free_temp(temp, Some(span))?; } } } @@ -1764,7 +1873,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } if let Some(label) = &self.current_return_label { - self.write_output(format!("j {}", label))?; + self.write_output(format!("j {}", label), None)?; } else { return Err(Error::Unknown( "Return statement used outside of function context.".into(), @@ -1782,7 +1891,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { expr: System<'a>, span: Span, scope: &mut VariableScope<'a, '_>, - ) -> Result>, Error<'a>> { + ) -> Result>, Error<'a>> { macro_rules! cleanup { ($($to_clean:expr),*) => { $( @@ -1794,12 +1903,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } match expr { System::Yield => { - self.write_output("yield")?; + self.write_output("yield", Some(span))?; Ok(None) } System::Sleep(amt) => { let (var, var_cleanup) = self.compile_operand(*amt, scope)?; - self.write_output(format!("sleep {var}"))?; + self.write_output(format!("sleep {var}"), Some(span))?; cleanup!(var_cleanup); @@ -1821,7 +1930,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { crc_hash_signed(&str_lit), ))); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: loc, temp_name: None, })) @@ -1866,7 +1975,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )); }; - self.write_output(format!("s {} {} {}", device_val, logic_type, variable))?; + self.write_output( + format!("s {} {} {}", device_val, logic_type, variable), + Some(span), + )?; cleanup!(var_cleanup); @@ -1887,7 +1999,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )); }; - self.write_output(format!("sb {} {} {}", device_hash_val, logic_type, var))?; + self.write_output( + format!("sb {} {} {}", device_hash_val, logic_type, var), + Some(span), + )?; cleanup!(var_cleanup, device_hash_cleanup); @@ -1906,10 +2021,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output(format!( - "sbn {} {} {} {}", - device_hash, name_hash, logic_type, value - ))?; + self.write_output( + format!("sbn {} {} {} {}", device_hash, name_hash, logic_type, value), + Some(span), + )?; cleanup!( value_cleanup, @@ -1958,14 +2073,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )); }; - self.write_output(format!( - "l r{} {} {}", - VariableScope::RETURN_REGISTER, - device_val, - logic_type - ))?; + self.write_output( + format!( + "l r{} {} {}", + VariableScope::RETURN_REGISTER, + device_val, + logic_type + ), + Some(span), + )?; - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Temporary(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -1982,17 +2100,20 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output(format!( - "lb r{} {} {} {}", - VariableScope::RETURN_REGISTER, - device_hash, - logic_type, - batch_mode - ))?; + self.write_output( + format!( + "lb r{} {} {} {}", + VariableScope::RETURN_REGISTER, + device_hash, + logic_type, + batch_mode + ), + Some(span), + )?; cleanup!(device_hash_cleanup, logic_type_cleanup, batch_mode_cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2011,14 +2132,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output(format!( - "lbn r{} {} {} {} {}", - VariableScope::RETURN_REGISTER, - device_hash, - name_hash, - logic_type, - batch_mode - ))?; + self.write_output( + format!( + "lbn r{} {} {} {} {}", + VariableScope::RETURN_REGISTER, + device_hash, + name_hash, + logic_type, + batch_mode + ), + Some(span), + )?; cleanup!( device_hash_cleanup, @@ -2027,7 +2151,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { batch_mode_cleanup ); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2044,17 +2168,20 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output(format!( - "ls r{} {} {} {}", - VariableScope::RETURN_REGISTER, - dev_hash, - slot_index, - logic_type - ))?; + self.write_output( + format!( + "ls r{} {} {} {}", + VariableScope::RETURN_REGISTER, + dev_hash, + slot_index, + logic_type + ), + Some(span), + )?; cleanup!(hash_cleanup, slot_cleanup, logic_cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2072,10 +2199,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; let (var, var_cleanup) = self.compile_operand(*var, scope)?; - self.write_output(format!( - "ss {} {} {} {}", - dev_name, slot_index, logic_type, var - ))?; + self.write_output( + format!("ss {} {} {} {}", dev_name, slot_index, logic_type, var), + Some(span), + )?; cleanup!(name_cleanup, index_cleanup, type_cleanup, var_cleanup); @@ -2087,8 +2214,9 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { fn expression_syscall_math( &mut self, expr: Math<'a>, + span: Span, scope: &mut VariableScope<'a, '_>, - ) -> Result>, Error<'a>> { + ) -> Result>, Error<'a>> { macro_rules! cleanup { ($($to_clean:expr),*) => { $( @@ -2101,30 +2229,39 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match expr { Math::Acos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("acos r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("acos r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Asin(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("asin r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("asin r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Atan(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("atan r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("atan r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2133,64 +2270,82 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let (var1, var1_cleanup) = self.compile_operand(*expr1, scope)?; let (var2, var2_cleanup) = self.compile_operand(*expr2, scope)?; - self.write_output(format!( - "atan2 r{} {} {}", - VariableScope::RETURN_REGISTER, - var1, - var2 - ))?; + self.write_output( + format!( + "atan2 r{} {} {}", + VariableScope::RETURN_REGISTER, + var1, + var2 + ), + Some(span), + )?; cleanup!(var1_cleanup, var2_cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Abs(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("abs r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("abs r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Ceil(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("ceil r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("ceil r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Cos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("cos r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("cos r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Floor(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("floor r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("floor r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Log(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output(format!("log r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("log r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(cleanup); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2198,15 +2353,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Math::Max(expr1, expr2) => { let (var1, clean1) = self.compile_operand(*expr1, scope)?; let (var2, clean2) = self.compile_operand(*expr2, scope)?; - self.write_output(format!( - "max r{} {} {}", - VariableScope::RETURN_REGISTER, - var1, - var2 - ))?; + self.write_output( + format!("max r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2), + Some(span), + )?; cleanup!(clean1, clean2); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2214,63 +2367,76 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Math::Min(expr1, expr2) => { let (var1, clean1) = self.compile_operand(*expr1, scope)?; let (var2, clean2) = self.compile_operand(*expr2, scope)?; - self.write_output(format!( - "min r{} {} {}", - VariableScope::RETURN_REGISTER, - var1, - var2 - ))?; + self.write_output( + format!("min r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2), + Some(span), + )?; cleanup!(clean1, clean2); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Rand => { - self.write_output(format!("rand r{}", VariableScope::RETURN_REGISTER))?; + self.write_output( + format!("rand r{}", VariableScope::RETURN_REGISTER), + Some(span), + )?; - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Sin(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output(format!("sin r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("sin r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(clean); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Sqrt(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output(format!("sqrt r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("sqrt r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(clean); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Tan(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output(format!("tan r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("tan r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(clean); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Trunc(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output(format!("trunc r{} {}", VariableScope::RETURN_REGISTER, var))?; + self.write_output( + format!("trunc r{} {}", VariableScope::RETURN_REGISTER, var), + Some(span), + )?; cleanup!(clean); - Ok(Some(CompilationResult { + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) @@ -2291,6 +2457,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { body, } = expr.node; + let span = expr.span; + if self.function_locations.contains_key(&name.node) { self.errors .push(Error::DuplicateIdentifier(name.node.clone(), name.span)); @@ -2304,7 +2472,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { ); // Declare the function as a line identifier - self.write_output(format!("{}:", name.node))?; + self.write_output(format!("{}:", name.node), Some(name.span))?; self.function_locations .insert(name.node.clone(), self.current_line); @@ -2330,10 +2498,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match loc { VariableLocation::Persistant(loc) => { - self.write_output(format!( - "pop r{loc} {}", - debug!(self, "#{}", var_name.node) - ))?; + self.write_output( + format!("pop r{loc} {}", debug!(self, "#{}", var_name.node)), + Some(var_name.span), + )?; } VariableLocation::Stack(_) => { return Err(Error::Unknown( @@ -2364,7 +2532,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; } - self.write_output("push ra")?; + self.write_output("push ra", Some(span))?; let return_label = self.next_label_name(); @@ -2417,22 +2585,28 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.current_return_label = prev_return_label; - self.write_output(format!("{}:", return_label))?; + self.write_output(format!("{}:", return_label), Some(span))?; - self.write_output(format!( - "sub r{0} sp {ra_stack_offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get ra db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; + self.write_output( + format!( + "sub r{0} sp {ra_stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ), + Some(span), + )?; + self.write_output( + format!("get ra db r{0}", VariableScope::TEMP_STACK_REGISTER), + Some(span), + )?; if block_scope.stack_offset() > 0 { - self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; + self.write_output( + format!("sub sp sp {}", block_scope.stack_offset()), + Some(span), + )?; } - self.write_output("j ra")?; + self.write_output("j ra", Some(span))?; Ok(()) } } diff --git a/rust_compiler/libs/parser/Cargo.toml b/rust_compiler/libs/parser/Cargo.toml index 1c5d935..37328bd 100644 --- a/rust_compiler/libs/parser/Cargo.toml +++ b/rust_compiler/libs/parser/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" tokenizer = { path = "../tokenizer" } helpers = { path = "../helpers" } lsp-types = { workspace = true } +safer-ffi = { workspace = true } thiserror = { workspace = true } diff --git a/rust_compiler/libs/parser/src/tree_node.rs b/rust_compiler/libs/parser/src/tree_node.rs index 9f427f6..42d624c 100644 --- a/rust_compiler/libs/parser/src/tree_node.rs +++ b/rust_compiler/libs/parser/src/tree_node.rs @@ -1,5 +1,6 @@ use super::sys_call::SysCall; use crate::sys_call; +use safer_ffi::prelude::*; use std::{borrow::Cow, ops::Deref}; use tokenizer::token::Number; diff --git a/rust_compiler/src/ffi/mod.rs b/rust_compiler/src/ffi/mod.rs index 9dc8763..a213572 100644 --- a/rust_compiler/src/ffi/mod.rs +++ b/rust_compiler/src/ffi/mod.rs @@ -1,6 +1,6 @@ -use compiler::Compiler; +use compiler::{CompilationResult, Compiler}; use helpers::Documentation; -use parser::{sys_call::SysCall, Parser}; +use parser::{sys_call::SysCall, tree_node::Span, Parser}; use safer_ffi::prelude::*; use std::io::BufWriter; use tokenizer::{ @@ -8,6 +8,20 @@ use tokenizer::{ Tokenizer, }; +#[derive_ReprC] +#[repr(C)] +pub struct FfiSourceMapEntry { + pub line_number: u32, + pub span: FfiRange, +} + +#[derive_ReprC] +#[repr(C)] +pub struct FfiCompilationResult { + pub output_code: safer_ffi::String, + pub source_map: safer_ffi::Vec, +} + #[derive_ReprC] #[repr(C)] pub struct FfiToken { @@ -34,6 +48,17 @@ pub struct FfiDocumentedItem { docs: safer_ffi::String, } +impl From for FfiRange { + fn from(value: Span) -> Self { + Self { + start_line: value.start_line as u32, + end_line: value.end_line as u32, + start_col: value.start_col as u32, + end_col: value.end_col as u32, + } + } +} + impl From for FfiRange { fn from(value: lsp_types::Range) -> Self { Self { @@ -69,6 +94,11 @@ impl From for FfiDiagnostic { } } +#[ffi_export] +pub fn free_ffi_compilation_result(input: FfiCompilationResult) { + drop(input) +} + #[ffi_export] pub fn free_ffi_token_vec(v: safer_ffi::Vec) { drop(v) @@ -94,7 +124,7 @@ pub fn free_docs_vec(v: safer_ffi::Vec) { /// This should result in the ability to compile many times without triggering frame drops /// from the GC from a `GetBytes()` call on a string in C#. #[ffi_export] -pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::String { +pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> FfiCompilationResult { let res = std::panic::catch_unwind(|| { let input = String::from_utf16_lossy(input.as_slice()); let mut writer = BufWriter::new(Vec::new()); @@ -103,19 +133,45 @@ pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi:: let parser = Parser::new(tokenizer); let compiler = Compiler::new(parser, &mut writer, None); - if !compiler.compile().is_empty() { - return safer_ffi::String::EMPTY; + let res = compiler.compile(); + + if !res.errors.is_empty() { + return (safer_ffi::String::EMPTY, res.source_map); } let Ok(compiled_vec) = writer.into_inner() else { - return safer_ffi::String::EMPTY; + return (safer_ffi::String::EMPTY, res.source_map); }; // Safety: I know the compiler only outputs valid utf8 - safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) }) + ( + safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) }), + res.source_map, + ) }); - res.unwrap_or("".into()) + if let Ok((res_str, source_map)) = res { + FfiCompilationResult { + source_map: source_map + .into_iter() + .flat_map(|(k, v)| { + v.into_iter() + .map(|span| FfiSourceMapEntry { + span: span.into(), + line_number: k as u32, + }) + .collect::>() + }) + .collect::>() + .into(), + output_code: res_str, + } + } else { + FfiCompilationResult { + output_code: "".into(), + source_map: vec![].into(), + } + } } #[ffi_export] @@ -184,7 +240,9 @@ pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec< let tokenizer = Tokenizer::from(input.as_str()); let compiler = Compiler::new(Parser::new(tokenizer), &mut writer, None); - let diagnosis = compiler.compile(); + let CompilationResult { + errors: diagnosis, .. + } = compiler.compile(); let mut result_vec: Vec = Vec::with_capacity(diagnosis.len()); diff --git a/rust_compiler/src/main.rs b/rust_compiler/src/main.rs index 040f07f..cdd7bc0 100644 --- a/rust_compiler/src/main.rs +++ b/rust_compiler/src/main.rs @@ -1,7 +1,7 @@ #![allow(clippy::result_large_err)] use clap::Parser; -use compiler::Compiler; +use compiler::{CompilationResult, Compiler}; use parser::Parser as ASTParser; use std::{ fs::File, @@ -90,7 +90,7 @@ fn run_logic<'a>() -> Result<(), Error<'a>> { let compiler = Compiler::new(parser, &mut writer, None); - let errors = compiler.compile(); + let CompilationResult { errors, .. } = compiler.compile(); if !errors.is_empty() { let mut std_error = stderr();