Browse Source

Add: debug library, but not full

Akeit0 10 months ago
parent
commit
3501dacddd

+ 23 - 3
sandbox/ConsoleApp1/Program.cs

@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
 using Lua.CodeAnalysis.Syntax;
 using Lua.CodeAnalysis.Compilation;
 using Lua.Runtime;
@@ -11,7 +12,7 @@ state.Environment["vec3"] = new LVec3();
 
 try
 {
-    var source = File.ReadAllText("test.lua");
+    var source = File.ReadAllText(GetAbsolutePath("test.lua"));
 
     var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua");
 
@@ -41,6 +42,15 @@ try
 catch (Exception ex)
 {
     Console.WriteLine(ex);
+    if(ex is LuaRuntimeException { InnerException: not null } luaEx)
+    {
+        Console.WriteLine(luaEx.InnerException);
+    }
+}
+
+static string GetAbsolutePath(string relativePath, [CallerFilePath] string callerFilePath = "")
+{
+    return Path.Combine(Path.GetDirectoryName(callerFilePath)!, relativePath);
 }
 
 static void DebugChunk(Chunk chunk, int id)
@@ -56,14 +66,24 @@ static void DebugChunk(Chunk chunk, int id)
         index++;
     }
 
-    Console.WriteLine("Constants " + new string('-', 50)); index = 0;
+    Console.WriteLine("Locals " + new string('-', 50));
+    index = 0;
+    foreach (var local in chunk.Locals.ToArray())
+    {
+        Console.WriteLine($"[{index}]\t{local.Index}\t{local.Name}\t{local.StartPc}\t{local.EndPc}");
+        index++;
+    }
+
+    Console.WriteLine("Constants " + new string('-', 50));
+    index = 0;
     foreach (var constant in chunk.Constants.ToArray())
     {
         Console.WriteLine($"[{index}]\t{constant}");
         index++;
     }
 
-    Console.WriteLine("UpValues " + new string('-', 50)); index = 0;
+    Console.WriteLine("UpValues " + new string('-', 50));
+    index = 0;
     foreach (var upValue in chunk.UpValues.ToArray())
     {
         Console.WriteLine($"[{index}]\t{upValue.Name}\t{(upValue.IsInRegister ? 1 : 0)}\t{upValue.Index}");

+ 1 - 0
src/Lua/CodeAnalysis/Compilation/Descriptions.cs

@@ -6,6 +6,7 @@ namespace Lua.CodeAnalysis.Compilation
     public readonly record struct LocalVariableDescription
     {
         public required byte RegisterIndex { get; init; }
+        public required int StartPc { get; init; }
     }
 
     public readonly record struct FunctionDescription

+ 17 - 1
src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs

@@ -60,6 +60,7 @@ public class FunctionCompilationContext : IDisposable
 
     // upvalues
     FastListCore<UpValueInfo> upvalues;
+    FastListCore<LocalValueInfo> localVariables;
 
     // loop
     FastListCore<BreakDescription> breakQueue;
@@ -301,6 +302,17 @@ public class FunctionCompilationContext : IDisposable
             return false;
         }
     }
+    
+    public void AddLocalVariable(ReadOnlyMemory<char> name, LocalVariableDescription description)
+    {
+        localVariables.Add(new LocalValueInfo()
+        {
+            Name = name,
+            Index = description.RegisterIndex,
+            StartPc = description.StartPc,
+            EndPc = Instructions.Length,
+        });
+    }
 
     public void AddUpValue(UpValueInfo upValue)
     {
@@ -410,7 +422,9 @@ public class FunctionCompilationContext : IDisposable
         // add return
         instructions.Add(Instruction.Return(0, 1));
         instructionPositions.Add(instructionPositions.Length == 0 ? default : instructionPositions[^1]);
-
+        Scope.RegisterLocalsToFunction();
+        var locals = localVariables.AsSpan().ToArray();
+        Array.Sort(locals, (x, y) => x.Index.CompareTo(y.Index));
         var chunk = new Chunk()
         {
             Name = ChunkName ?? "chunk",
@@ -418,6 +432,7 @@ public class FunctionCompilationContext : IDisposable
             SourcePositions = instructionPositions.AsSpan().ToArray(),
             Constants = constants.AsSpan().ToArray(),
             UpValues = upvalues.AsSpan().ToArray(),
+            Locals = locals,
             Functions = functions.AsSpan().ToArray(),
             ParameterCount = ParameterCount,
             MaxStackPosition = MaxStackPosition,
@@ -442,6 +457,7 @@ public class FunctionCompilationContext : IDisposable
         constantIndexMap.Clear();
         constants.Clear();
         upvalues.Clear();
+        localVariables.Clear();
         functionMap.Clear();
         functions.Clear();
         breakQueue.Clear();

+ 45 - 2
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -466,9 +466,11 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
                 context.AddLocalVariable(identifier.Name, new()
                 {
                     RegisterIndex = (byte)(context.StackPosition - 1),
+                    StartPc = context.Function.Instructions.Length,
                 });
             }
         }
+
         return true;
     }
 
@@ -598,6 +600,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         context.AddLocalVariable(node.Name, new()
         {
             RegisterIndex = context.StackPosition,
+            StartPc = context.Function.Instructions.Length,
         });
 
         // compile function
@@ -678,6 +681,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             funcContext.Scope.AddLocalVariable("self".AsMemory(), new()
             {
                 RegisterIndex = 0,
+                StartPc = 0,
             });
 
             funcContext.Scope.StackPosition++;
@@ -690,6 +694,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             funcContext.Scope.AddLocalVariable(parameter.Name, new()
             {
                 RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)),
+                StartPc = 0,
             });
 
             funcContext.Scope.StackPosition++;
@@ -905,10 +910,29 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         context.Function.LoopLevel++;
         using var scopeContext = context.CreateChildScope();
         {
+            scopeContext.AddLocalVariable("(for index)".AsMemory(), new()
+            {
+                RegisterIndex = startPosition,
+                StartPc = context.Function.Instructions.Length,
+            });
+
+            scopeContext.AddLocalVariable("(for limit)".AsMemory(), new()
+            {
+                RegisterIndex = (byte)(startPosition + 1),
+                StartPc = context.Function.Instructions.Length,
+            });
+
+            scopeContext.AddLocalVariable("(for step)".AsMemory(), new()
+            {
+                RegisterIndex = (byte)(startPosition + 2),
+                StartPc = context.Function.Instructions.Length,
+            });
+
             // add local variable
             scopeContext.AddLocalVariable(node.VariableName, new()
             {
-                RegisterIndex = startPosition
+                RegisterIndex = (byte)(startPosition + 3),
+                StartPc = context.Function.Instructions.Length,
             });
 
             foreach (var childNode in node.StatementNodes)
@@ -949,13 +973,32 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         {
             scopeContext.StackPosition = (byte)(startPosition + 3 + node.Names.Length);
 
+            scopeContext.AddLocalVariable("(for generator)".AsMemory(), new()
+            {
+                RegisterIndex = (byte)(startPosition),
+                StartPc = context.Function.Instructions.Length,
+            });
+
+            scopeContext.AddLocalVariable("(for state)".AsMemory(), new()
+            {
+                RegisterIndex = (byte)(startPosition + 1),
+                StartPc = context.Function.Instructions.Length,
+            });
+
+            scopeContext.AddLocalVariable("(for control)".AsMemory(), new()
+            {
+                RegisterIndex = (byte)(startPosition + 2),
+                StartPc = context.Function.Instructions.Length,
+            });
+
             // add local variables
             for (int i = 0; i < node.Names.Length; i++)
             {
                 var name = node.Names[i];
                 scopeContext.AddLocalVariable(name.Name, new()
                 {
-                    RegisterIndex = (byte)(startPosition + 3 + i)
+                    RegisterIndex = (byte)(startPosition + 3 + i),
+                    StartPc = context.Function.Instructions.Length,
                 });
             }
 

+ 9 - 0
src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs

@@ -153,6 +153,14 @@ public class ScopeCompilationContext : IDisposable
 
         return false;
     }
+    
+    public void RegisterLocalsToFunction()
+    {
+        foreach (var localVariable in localVariables)
+        {
+            Function.AddLocalVariable(localVariable.Key, localVariable.Value);
+        }
+    }
 
     /// <summary>
     /// Resets the values ​​held in the context.
@@ -173,6 +181,7 @@ public class ScopeCompilationContext : IDisposable
     /// </summary>
     public void Dispose()
     {
+        RegisterLocalsToFunction();
         Function = null!;
         Pool.Return(this);
     }

+ 5 - 0
src/Lua/Exceptions.cs

@@ -100,6 +100,11 @@ public class LuaRuntimeException : LuaException
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({expected} expected, got {actual})");
     }
 
+    public static void BadArgument(Traceback traceback, int argumentId, string functionName, string message)
+    {
+        throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({message})");
+    }
+
     public static void BadArgumentNumberIsNotInteger(Traceback traceback, int argumentId, string functionName)
     {
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");

+ 44 - 0
src/Lua/LuaFunctionExecutionContext.cs

@@ -37,6 +37,16 @@ public readonly record struct LuaFunctionExecutionContext
         ThrowIfArgumentNotExists(index);
         return Arguments[index];
     }
+    
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal LuaValue GetArgumentOrDefault(int index, LuaValue defaultValue = default)
+    {
+        if (ArgumentCount <= index)
+        {
+            return defaultValue;
+        }
+        return Arguments[index];
+    }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public T GetArgument<T>(int index)
@@ -64,6 +74,40 @@ public readonly record struct LuaFunctionExecutionContext
         return argValue;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal T GetArgumentOrDefault<T>(int index,T defaultValue =default!) 
+    {
+        if (ArgumentCount <= index)
+        {
+            return defaultValue;
+        }
+
+        var arg = Arguments[index];
+        if (!arg.TryRead<T>(out var argValue))
+        {
+            var t = typeof(T);
+            if ((t == typeof(int) || t == typeof(long)) && arg.TryReadNumber(out _))
+            {
+                LuaRuntimeException.BadArgumentNumberIsNotInteger(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+            }
+            else if (LuaValue.TryGetLuaValueType(t, out var type))
+            {
+                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+            }
+            else
+            {
+                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
+            }
+        }
+
+        return argValue;
+    }
+    
+    internal void ThrowBadArgument(int index, string message)
+    {
+        LuaRuntimeException.BadArgument(State.GetTraceback(), index, Thread.GetCurrentFrame().Function.Name, message);
+    }
+
     void ThrowIfArgumentNotExists(int index)
     {
         if (ArgumentCount <= index)

+ 2 - 0
src/Lua/LuaState.cs

@@ -16,6 +16,7 @@ public sealed class LuaState
     FastStackCore<LuaThread> threadStack;
     readonly LuaTable packages = new();
     readonly LuaTable environment;
+    readonly LuaTable registry = new();
     readonly UpValue envUpValue;
     bool isRunning;
 
@@ -24,6 +25,7 @@ public sealed class LuaState
     internal ref FastListCore<UpValue> OpenUpValues => ref openUpValues;
 
     public LuaTable Environment => environment;
+    public LuaTable Registry => registry;
     public LuaTable LoadedModules => packages;
     public LuaMainThread MainThread => mainThread;
     public LuaThread CurrentThread

+ 1 - 0
src/Lua/Runtime/Chunk.cs

@@ -11,6 +11,7 @@ public sealed class Chunk
     public required SourcePosition[] SourcePositions { get; init; }
     public required LuaValue[] Constants { get; init; }
     public required UpValueInfo[] UpValues { get; init; }
+    public required LocalValueInfo[] Locals { get; init; }
     public required Chunk[] Functions { get; init; }
     public required int ParameterCount { get; init; }
     

+ 9 - 0
src/Lua/Runtime/LocalValueInfo.cs

@@ -0,0 +1,9 @@
+namespace Lua.Runtime;
+
+public readonly record struct LocalValueInfo
+{
+    public required ReadOnlyMemory<char> Name { get; init; }
+    public required byte Index { get; init; }
+    public required int StartPc { get; init; }
+    public required int EndPc { get; init; }
+}

+ 16 - 5
src/Lua/Runtime/Tracebacks.cs

@@ -10,7 +10,7 @@ public class Traceback
     public required Closure RootFunc { get; init; }
     public required CallStackFrame[] StackFrames { get; init; }
 
-    internal string RootChunkName => RootFunc.Proto.Name; //StackFrames.Length == 0 ? "" : StackFrames[^1].Function is Closure closure ? closure.Proto.GetRoot().Name : StackFrames[^2].Function.Name;
+    internal string RootChunkName => RootFunc.Proto.Name;
 
     internal SourcePosition LastPosition
     {
@@ -34,15 +34,25 @@ public class Traceback
 
 
     public override string ToString()
+    {
+        return GetTracebackString(RootFunc, StackFrames, LuaValue.Nil);
+    }
+
+    internal static string GetTracebackString(Closure rootFunc, ReadOnlySpan<CallStackFrame> stackFrames, LuaValue message)
     {
         using var list = new PooledList<char>(64);
+        if (message.Type is not LuaValueType.Nil)
+        {
+            list.AddRange(message.ToString());
+            list.AddRange("\n");
+        }
+
         list.AddRange("stack traceback:\n");
-        var stackFrames = StackFrames.AsSpan();
         var intFormatBuffer = (stackalloc char[15]);
+
         for (var index = stackFrames.Length - 1; index >= 0; index--)
         {
-            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
-            var frame = stackFrames[index];
+            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : rootFunc;
             if (lastFunc is not null and not Closure)
             {
                 list.AddRange("\t[C#]: in function '");
@@ -51,12 +61,13 @@ public class Traceback
             }
             else if (lastFunc is Closure closure)
             {
+                var frame = stackFrames[index];
                 var p = closure.Proto;
                 var root = p.GetRoot();
                 list.AddRange("\t");
                 list.AddRange(root.Name);
                 list.AddRange(":");
-                p.SourcePositions[frame.CallerInstructionIndex].Line.TryFormat(intFormatBuffer, out var charsWritten,provider:CultureInfo.InvariantCulture);
+                p.SourcePositions[frame.CallerInstructionIndex].Line.TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture);
                 list.AddRange(intFormatBuffer[..charsWritten]);
                 list.AddRange(root == p ? ": in '" : ": in function '");
                 list.AddRange(p.Name);

+ 286 - 0
src/Lua/Standard/DebugLibrary.cs

@@ -0,0 +1,286 @@
+using System.Runtime.CompilerServices;
+using Lua.Runtime;
+
+namespace Lua.Standard;
+
+public class DebugLibrary
+{
+    public static readonly DebugLibrary Instance = new();
+
+    public DebugLibrary()
+    {
+        Functions =
+        [
+            new("getlocal", GetLocal),
+            new("setlocal", SetLocal),
+            new("getupvalue", GetUpValue),
+            new("setupvalue", SetUpValue),
+            new("getmetatable", GetMetatable),
+            new("setmetatable", SetMetatable),
+            new("traceback", Traceback),
+            new("getregistry", GetRegistry)
+        ];
+    }
+
+    public readonly LuaFunction[] Functions;
+
+
+    LuaThread GetLuaThread(in LuaFunctionExecutionContext context, out int argOffset)
+    {
+        if (context.ArgumentCount < 1)
+        {
+            argOffset = 0;
+            return context.Thread;
+        }
+
+        if (context.GetArgument(0).TryRead<LuaThread>(out var thread))
+        {
+            argOffset = 1;
+            return thread;
+        }
+
+        argOffset = 0;
+        return context.Thread;
+    }
+
+    ref LuaValue FindLocal(LuaThread thread, int level, int index, out string? name)
+    {
+        if (index == 0)
+        {
+            name = null;
+            return ref Unsafe.NullRef<LuaValue>();
+        }
+
+        var callStack = thread.GetCallStackFrames();
+        var frame = callStack[^(level + 1)];
+        if (index < 0)
+        {
+            index = -index - 1;
+            var frameVariableArgumentCount = frame.VariableArgumentCount;
+            if (frameVariableArgumentCount > 0 && index < frameVariableArgumentCount)
+            {
+                name = "(vararg)";
+                return ref thread.Stack.Get(frame.Base - frameVariableArgumentCount + index);
+            }
+
+            name = null;
+            return ref Unsafe.NullRef<LuaValue>();
+        }
+
+        index -= 1;
+
+
+        var frameBase = frame.Base;
+        var nextFrameBase = level != 0 ? callStack[^level].Base : thread.Stack.Count;
+        if (nextFrameBase - frameBase <= index)
+        {
+            name = null;
+            return ref Unsafe.NullRef<LuaValue>();
+        }
+
+        if (frame.Function is Closure closure)
+        {
+            var locals = closure.Proto.Locals;
+            var currentPc = callStack[^level].CallerInstructionIndex;
+            foreach (var local in locals)
+            {
+                if (local.Index == index && currentPc >= local.StartPc && currentPc < local.EndPc)
+                {
+                    name = local.Name.ToString();
+                    return ref thread.Stack.Get(frameBase + local.Index);
+                }
+
+                if (local.Index >= index)
+                {
+                    break;
+                }
+            }
+        }
+
+        name = "(*temporary)";
+        return ref thread.Stack.Get(frameBase + index);
+    }
+
+    public ValueTask<int> GetLocal(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        static LuaValue GetParam(LuaFunction function, int index)
+        {
+            if (function is Closure closure)
+            {
+                var paramCount = closure.Proto.ParameterCount;
+                if (0 <= index && index < paramCount)
+                {
+                    return closure.Proto.Locals[index].Name.ToString();
+                }
+            }
+
+            return LuaValue.Nil;
+        }
+
+        var thread = GetLuaThread(context, out var argOffset);
+
+        var index = context.GetArgument<int>(argOffset + 1);
+        if (context.GetArgument(argOffset).TryReadFunction(out var f))
+        {
+            buffer.Span[0] = GetParam(f, index - 1);
+            return new(1);
+        }
+
+        var level = context.GetArgument<int>(argOffset);
+
+
+        if (level < 0 || level >= thread.GetCallStackFrames().Length)
+        {
+            context.ThrowBadArgument(1, "level out of range");
+        }
+
+        ref var local = ref FindLocal(thread, level, index, out var name);
+        if (name is null)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        buffer.Span[0] = name;
+        buffer.Span[1] = local;
+        return new(2);
+    }
+
+    public ValueTask<int> SetLocal(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var thread = GetLuaThread(context, out var argOffset);
+
+        var value = context.GetArgument(argOffset + 2);
+        var index = context.GetArgument<int>(argOffset + 1);
+        var level = context.GetArgument<int>(argOffset);
+
+
+        if (level < 0 || level >= thread.GetCallStackFrames().Length)
+        {
+            context.ThrowBadArgument(1, "level out of range");
+        }
+
+        ref var local = ref FindLocal(thread, level, index, out var name);
+        if (name is null)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        buffer.Span[0] = name;
+        local = value;
+        return new(1);
+    }
+
+    public ValueTask<int> GetUpValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var func = context.GetArgument<LuaFunction>(0);
+        var index = context.GetArgument<int>(1) - 1;
+        if (func is not Closure closure)
+        {
+            return new(0);
+        }
+
+        var upValues = closure.UpValues;
+        var descriptions = closure.Proto.UpValues;
+        if (index < 0 || index >= descriptions.Length)
+        {
+            return new(0);
+        }
+
+        var description = descriptions[index];
+        buffer.Span[0] = description.Name.ToString();
+        buffer.Span[1] = upValues[index].GetValue();
+        return new(2);
+    }
+
+    public ValueTask<int> SetUpValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var func = context.GetArgument<LuaFunction>(0);
+        var index = context.GetArgument<int>(1) - 1;
+        var value = context.GetArgument(2);
+        if (func is not Closure closure)
+        {
+            return new(0);
+        }
+
+        var upValues = closure.UpValues;
+        var descriptions = closure.Proto.UpValues;
+        if (index < 0 || index >= descriptions.Length)
+        {
+            return new(0);
+        }
+
+        var description = descriptions[index];
+        buffer.Span[0] = description.Name.ToString();
+        upValues[index].SetValue(value);
+        return new(1);
+    }
+
+    public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.GetArgument(0);
+
+        if (context.State.TryGetMetatable(arg0, out var table))
+        {
+            buffer.Span[0] = table;
+        }
+        else
+        {
+            buffer.Span[0] = LuaValue.Nil;
+        }
+
+        return new(1);
+    }
+
+    public ValueTask<int> SetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.GetArgument(0);
+        var arg1 = context.GetArgument(1);
+
+        if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
+        {
+            LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
+        }
+
+        context.State.SetMetatable(arg0, arg1.UnsafeRead<LuaTable>());
+
+        buffer.Span[0] = arg0;
+        return new(1);
+    }
+
+    public ValueTask<int> Traceback(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var thread = (GetLuaThread(context, out var argOffset));
+
+        var message = context.GetArgumentOrDefault(argOffset);
+        var level = context.GetArgumentOrDefault<int>(argOffset + 1, 1);
+
+
+        if (message.Type is not (LuaValueType.Nil or LuaValueType.String or LuaValueType.Number))
+        {
+            buffer.Span[0] = message;
+            return new(1);
+        }
+
+        if (level < 0)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        thread.PushCallStackFrame(thread.GetCurrentFrame());
+        var callStack = thread.GetCallStackFrames();
+        var skipCount = Math.Min(level, callStack.Length - 1);
+        var frames = callStack[1..^skipCount];
+        buffer.Span[0] = Runtime.Traceback.GetTracebackString((Closure)callStack[0].Function, frames, message);
+        thread.PopCallStackFrame();
+        return new(1);
+    }
+
+    public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        buffer.Span[0] = context.State.Registry;
+        return new(1);
+    }
+}

+ 13 - 0
src/Lua/Standard/OpenLibsExtensions.cs

@@ -130,6 +130,18 @@ public static class OpenLibsExtensions
         state.Environment["table"] = table;
         state.LoadedModules["table"] = table;
     }
+    
+    public static void OpenDebugLibrary(this LuaState state)
+    {
+        var debug = new LuaTable(0, DebugLibrary.Instance.Functions.Length);
+        foreach (var func in DebugLibrary.Instance.Functions)
+        {
+            debug[func.Name] = func;
+        }
+
+        state.Environment["debug"] = debug;
+        state.LoadedModules["debug"] = debug;
+    }
 
     public static void OpenStandardLibraries(this LuaState state)
     {
@@ -142,5 +154,6 @@ public static class OpenLibsExtensions
         state.OpenOperatingSystemLibrary();
         state.OpenStringLibrary();
         state.OpenTableLibrary();
+        state.OpenDebugLibrary();
     }
 }

+ 1 - 0
src/Lua/Standard/TableLibrary.cs

@@ -41,6 +41,7 @@ public sealed class TableLibrary
         ],
         ParameterCount = 2,
         UpValues = [],
+        Locals = [new LocalValueInfo(){Name = "a".AsMemory(),StartPc = 0,Index = 0,EndPc = 4}, new LocalValueInfo(){Name = "b".AsMemory(),StartPc = 0,Index = 1,EndPc = 4}],
         MaxStackPosition = 2,
     };
 

+ 6 - 0
tests/Lua.Tests/LuaTests.cs

@@ -54,6 +54,12 @@ public class LuaTests
     {
         await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/coroutine.lua"));
     }
+    
+    [Test]
+    public async Task Test_Debug_Mini()
+    {
+        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/db-mini.lua"));
+    }
 
     [Test]
     public async Task Test_VeryBig()

+ 66 - 0
tests/Lua.Tests/tests-lua/db-mini.lua

@@ -0,0 +1,66 @@
+-- testing debug library
+local a =1
+
+local function multi_assert(expected,...)
+    local arg = {...}
+    for i = 1, #arg do
+        assert(arg[i]==expected[i])
+    end
+end
+local function test_locals(x,...)
+    local b ="local b"
+    assert(debug.getlocal(test_locals,1) == "x")
+    multi_assert({"x",1},debug.getlocal(1,1))
+    multi_assert({"b","local b"},debug.getlocal(1,2))
+    multi_assert({"(vararg)",2},debug.getlocal(1,-1))
+    multi_assert({"(vararg)",3},debug.getlocal(1,-2))
+    multi_assert({"a",1},debug.getlocal(2,1))
+    assert(debug.setlocal(2,1,"new a") == "a")
+
+end
+
+test_locals(1,2,3)
+assert(a == "new a")
+
+local function test_upvalues()
+    local a =3
+    local function f(x)
+        local b = a + x
+        local function g(y)
+            local c = b + y
+            local function h()
+                return a+b+c
+            end
+            multi_assert({"a",3},debug.getupvalue(h,1))
+            multi_assert({"b",4},debug.getupvalue(h,2))
+            multi_assert({"c",6},debug.getupvalue(h,3))
+            multi_assert({"b",4},debug.getupvalue(g,1))
+            multi_assert({"a",3},debug.getupvalue(g,2))
+            multi_assert({"a",3},debug.getupvalue(f,1))
+            debug.setupvalue(h,1,10)
+            debug.setupvalue(h,2,20)
+            debug.setupvalue(h,3,30)
+            assert(h() == 60)
+        end
+        g(2)
+    end
+    f(1)
+end
+test_upvalues()
+local mt = {
+    __metatable = "my own metatable",
+    __index = function (o, k)
+        return o+k
+    end
+}
+debug.setmetatable(10, mt)
+assert(debug.getmetatable(10) == mt)
+a = 10
+assert( a[3] == 13)
+
+assert(debug.traceback(print)==print)
+assert(debug.traceback(print)==print)
+
+
+
+assert(type(debug.getregistry())=="table")