Browse Source

Add: debug.getinfo

Akeit0 10 months ago
parent
commit
8b134a2312

+ 25 - 5
src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs

@@ -91,6 +91,16 @@ public class FunctionCompilationContext : IDisposable
     /// </summary>
     public bool HasVariableArguments { get; set; }
 
+    /// <summary>
+    /// Line number where the function is defined
+    /// </summary>
+    public int LineDefined { get; set; }
+
+    /// <summary>
+    /// Last line number where the function is defined
+    /// </summary>
+    public int LastLineDefined { get; set; }
+
     /// <summary>
     /// Parent scope context
     /// </summary>
@@ -128,6 +138,7 @@ public class FunctionCompilationContext : IDisposable
             instructionPositions.Add(position);
             return;
         }
+
         ref var lastInstruction = ref instructions.AsSpan()[^1];
         var opcode = instruction.OpCode;
         switch (opcode)
@@ -157,6 +168,7 @@ public class FunctionCompilationContext : IDisposable
                             }
                     }
                 }
+
                 break;
             case OpCode.GetTable:
                 {
@@ -170,8 +182,8 @@ public class FunctionCompilationContext : IDisposable
                             incrementStackPosition = false;
                             return;
                         }
-
                     }
+
                     break;
                 }
             case OpCode.SetTable:
@@ -198,6 +210,7 @@ public class FunctionCompilationContext : IDisposable
                                     return;
                                 }
                             }
+
                             lastInstruction = Instruction.SetTable((byte)(lastB), instruction.B, instruction.C);
                             instructionPositions[^1] = position;
                             incrementStackPosition = false;
@@ -218,7 +231,6 @@ public class FunctionCompilationContext : IDisposable
                         var last2OpCode = last2Instruction.OpCode;
                         if (last2OpCode is OpCode.LoadK or OpCode.Move)
                         {
-
                             var last2A = last2Instruction.A;
                             if (last2A != lastLocal && instruction.C == last2A)
                             {
@@ -232,6 +244,7 @@ public class FunctionCompilationContext : IDisposable
                             }
                         }
                     }
+
                     break;
                 }
             case OpCode.Unm:
@@ -239,11 +252,13 @@ public class FunctionCompilationContext : IDisposable
             case OpCode.Len:
                 if (lastInstruction.OpCode == OpCode.Move && lastLocal != lastInstruction.A && lastInstruction.A == instruction.B)
                 {
-                    lastInstruction = instruction with { B = lastInstruction.B }; ;
+                    lastInstruction = instruction with { B = lastInstruction.B };
+                    ;
                     instructionPositions[^1] = position;
                     incrementStackPosition = false;
                     return;
                 }
+
                 break;
             case OpCode.Return:
                 if (lastInstruction.OpCode == OpCode.Move && instruction.B == 2 && lastInstruction.B < 256)
@@ -253,6 +268,7 @@ public class FunctionCompilationContext : IDisposable
                     incrementStackPosition = false;
                     return;
                 }
+
                 break;
         }
 
@@ -302,7 +318,7 @@ public class FunctionCompilationContext : IDisposable
             return false;
         }
     }
-    
+
     public void AddLocalVariable(ReadOnlyMemory<char> name, LocalVariableDescription description)
     {
         localVariables.Add(new LocalValueInfo()
@@ -390,6 +406,7 @@ public class FunctionCompilationContext : IDisposable
             {
                 instruction.A = startPosition;
             }
+
             instruction.SBx = endPosition - description.Index;
         }
 
@@ -421,7 +438,7 @@ public class FunctionCompilationContext : IDisposable
     {
         // add return
         instructions.Add(Instruction.Return(0, 1));
-        instructionPositions.Add(instructionPositions.Length == 0 ? default : instructionPositions[^1]);
+        instructionPositions.Add( new (LastLineDefined, 0));
         Scope.RegisterLocalsToFunction();
         var locals = localVariables.AsSpan().ToArray();
         Array.Sort(locals, (x, y) => x.Index.CompareTo(y.Index));
@@ -435,7 +452,10 @@ public class FunctionCompilationContext : IDisposable
             Locals = locals,
             Functions = functions.AsSpan().ToArray(),
             ParameterCount = ParameterCount,
+            HasVariableArguments = HasVariableArguments,
             MaxStackPosition = MaxStackPosition,
+            LineDefined = LineDefined,
+            LastLineDefined = LastLineDefined,
         };
 
         foreach (var function in functions.AsSpan())

+ 14 - 10
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -20,7 +20,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     public Chunk Compile(LuaSyntaxTree syntaxTree, string? chunkName = null)
     {
         using var context = FunctionCompilationContext.Create(null);
-
+        context.HasVariableArguments = true;
         // set global enviroment upvalue
         context.AddUpValue(new()
         {
@@ -586,7 +586,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     // function declaration
     public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context)
     {
-        var funcIndex = CompileFunctionProto(ReadOnlyMemory<char>.Empty, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false);
+        var funcIndex = CompileFunctionProto(ReadOnlyMemory<char>.Empty, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.LastLineDefined);
 
         // push closure instruction
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
@@ -604,7 +604,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         });
 
         // compile function
-        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false);
+        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.LastLineDefined);
 
         // push closure instruction
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
@@ -614,7 +614,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
     public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context)
     {
-        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false);
+        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.LastLineDefined);
 
         // add closure
         var index = context.Function.GetConstantIndex(node.Name.ToString());
@@ -639,7 +639,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context)
     {
         var funcIdentifier = node.MemberPath[^1];
-        var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length + 1, node.HasVariableArguments, node.HasSelfParameter);
+        var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length + 1, node.HasVariableArguments, node.HasSelfParameter, node.LineDefined, node.LastLineDefined);
 
         // add closure
         var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString());
@@ -669,12 +669,14 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         return true;
     }
 
-    int CompileFunctionProto(ReadOnlyMemory<char> functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, int parameterCount, bool hasVarArg, bool hasSelfParameter)
+    int CompileFunctionProto(ReadOnlyMemory<char> functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, int parameterCount, bool hasVarArg, bool hasSelfParameter, int lineDefined, int lastLineDefined)
     {
         using var funcContext = context.CreateChildFunction();
         funcContext.ChunkName = functionName.ToString();
         funcContext.ParameterCount = parameterCount;
         funcContext.HasVariableArguments = hasVarArg;
+        funcContext.LineDefined = lineDefined;
+        funcContext.LastLineDefined = lastLineDefined;
 
         if (hasSelfParameter)
         {
@@ -900,11 +902,11 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         else
         {
             var index = context.Function.GetConstantIndex(1);
-            context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.Position, true);
+            context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.DoPosition, true);
         }
 
         var prepIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.ForPrep(startPosition, 0), node.Position, true);
+        context.PushInstruction(Instruction.ForPrep(startPosition, 0), node.DoPosition, true);
 
         // compile statements
         context.Function.LoopLevel++;
@@ -965,7 +967,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
         // jump to TFORCALL
         var startJumpIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
+        context.PushInstruction(Instruction.Jmp(0, 0), node.DoPosition);
 
         // compile statements
         context.Function.LoopLevel++;
@@ -1016,7 +1018,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
         // push OP_TFORCALL and OP_TFORLOOP
         context.PushInstruction(Instruction.TForCall(startPosition, (ushort)node.Names.Length), node.Position);
-        context.PushInstruction(Instruction.TForLoop((byte)(startPosition + 2), startJumpIndex - context.Function.Instructions.Length), node.Position);
+        context.PushInstruction(Instruction.TForLoop((byte)(startPosition + 2), startJumpIndex - context.Function.Instructions.Length), node.EndPosition);
 
         context.Function.ResolveAllBreaks((byte)(startPosition + 1), context.Function.Instructions.Length - 1, scopeContext);
         context.StackPosition = startPosition;
@@ -1145,6 +1147,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
                 {
                     value = stringLiteral.Text.ToString();
                 }
+
                 return true;
             case UnaryExpressionNode unaryExpression:
                 if (TryGetConstant(unaryExpression.Node, context, out var unaryNodeValue))
@@ -1212,6 +1215,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
                             break;
                     }
                 }
+
                 break;
         }
 

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 
-public record FunctionDeclarationExpressionNode(IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position) : ExpressionNode(Position)
+public record FunctionDeclarationExpressionNode(IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position, int LineDefined,int LastLineDefined) : ExpressionNode(Position)
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 
-public record FunctionDeclarationStatementNode(ReadOnlyMemory<char> Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position) : StatementNode(Position)
+public record FunctionDeclarationStatementNode(ReadOnlyMemory<char> Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position,int LineDefined,int LastLineDefined) : StatementNode(Position)
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 
-public record GenericForStatementNode(IdentifierNode[] Names, ExpressionNode[] ExpressionNodes, StatementNode[] StatementNodes, SourcePosition Position) : StatementNode(Position)
+public record GenericForStatementNode(IdentifierNode[] Names, ExpressionNode[] ExpressionNodes, StatementNode[] StatementNodes, SourcePosition Position, SourcePosition DoPosition, SourcePosition EndPosition) : StatementNode(Position)
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 
-public record LocalFunctionDeclarationStatementNode(ReadOnlyMemory<char> Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position) : FunctionDeclarationStatementNode(Name, ParameterNodes, Nodes, HasVariableArguments, Position)
+public record LocalFunctionDeclarationStatementNode(ReadOnlyMemory<char> Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position, int LineDefined,int LastLineDefined) : FunctionDeclarationStatementNode(Name, ParameterNodes, Nodes, HasVariableArguments, Position, LineDefined, LastLineDefined)
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 
-public record NumericForStatementNode(ReadOnlyMemory<char> VariableName, ExpressionNode InitNode, ExpressionNode LimitNode, ExpressionNode? StepNode, StatementNode[] StatementNodes, SourcePosition Position) : StatementNode(Position)
+public record NumericForStatementNode(ReadOnlyMemory<char> VariableName, ExpressionNode InitNode, ExpressionNode LimitNode, ExpressionNode? StepNode, StatementNode[] StatementNodes, SourcePosition Position,SourcePosition DoPosition) : StatementNode(Position)
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 
-public record TableMethodDeclarationStatementNode(IdentifierNode[] MemberPath, IdentifierNode[] ParameterNodes, StatementNode[] Nodes, bool HasVariableArguments, bool HasSelfParameter, SourcePosition Position) : StatementNode(Position)
+public record TableMethodDeclarationStatementNode(IdentifierNode[] MemberPath, IdentifierNode[] ParameterNodes, StatementNode[] Nodes, bool HasVariableArguments, bool HasSelfParameter, SourcePosition Position, int LineDefined,int LastLineDefined) : StatementNode(Position)
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {

+ 34 - 26
src/Lua/CodeAnalysis/Syntax/Parser.cs

@@ -64,6 +64,7 @@ public ref struct Parser
 
                                 return ParseAssignmentStatement(firstExpression, ref enumerator);
                             }
+
                             break;
                     }
                 }
@@ -168,7 +169,7 @@ public ref struct Parser
         var doToken = enumerator.Current;
 
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
 
         return new DoStatementNode(statements, doToken.Position);
     }
@@ -208,6 +209,7 @@ public ref struct Parser
             enumerator.MovePrevious();
             return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), [], firstExpression.Position);
         }
+
         MoveNextWithValidation(ref enumerator);
 
         // parse expressions
@@ -227,6 +229,7 @@ public ref struct Parser
             enumerator.MovePrevious();
             return new LocalAssignmentStatementNode(identifiers, [], localToken.Position);
         }
+
         MoveNextWithValidation(ref enumerator);
 
         // parse expressions
@@ -353,7 +356,7 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
 
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
 
         return new WhileStatementNode(condition, statements, whileToken.Position);
     }
@@ -365,7 +368,7 @@ public ref struct Parser
         var repeatToken = enumerator.Current;
 
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until, out _);
 
         // skip 'until keyword'
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
@@ -418,11 +421,12 @@ public ref struct Parser
 
         // skip 'do' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
+        var doToken = enumerator.Current;
 
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
 
-        return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position);
+        return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position, doToken.Position);
     }
 
     GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
@@ -433,33 +437,33 @@ public ref struct Parser
         // skip 'in' keyword
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _);
         enumerator.SkipEoL();
-
+        var iteratorToken = enumerator.Current;
         var expressions = ParseExpressionList(ref enumerator);
         MoveNextWithValidation(ref enumerator);
         enumerator.SkipEoL();
 
         // skip 'do' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
-
+        var doToken = enumerator.Current;
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
 
-        return new GenericForStatementNode(identifiers, expressions, statements, forToken.Position);
+        return new GenericForStatementNode(identifiers, expressions, statements, iteratorToken.Position, doToken.Position, endToken.Position);
     }
 
     FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     {
-        var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false);
-        return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position);
+        var (Name, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, false);
+        return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined);
     }
 
     LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     {
-        var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false);
-        return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position);
+        var (Name, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, false);
+        return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined);
     }
 
-    (ReadOnlyMemory<char> Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
+    (ReadOnlyMemory<char> Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments, int LineDefined, int LastLineDefined) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
     {
         ReadOnlyMemory<char> name;
 
@@ -478,7 +482,7 @@ public ref struct Parser
         }
 
         // skip '('
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _);
+        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var leftParenToken);
         enumerator.SkipEoL();
 
         // parse parameters
@@ -494,16 +498,16 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
 
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
 
-        return (name, identifiers, statements, hasVarArg);
+        return (name, identifiers, statements, hasVarArg, leftParenToken.Position.Line, endToken.Position.Line);
     }
 
     TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     {
         using var names = new PooledList<IdentifierNode>(32);
         var hasSelfParameter = false;
-
+        SyntaxToken leftParenToken;
         while (true)
         {
             CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
@@ -517,6 +521,7 @@ public ref struct Parser
                 {
                     LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
                 }
+
                 hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon;
 
                 MoveNextWithValidation(ref enumerator);
@@ -524,6 +529,7 @@ public ref struct Parser
             }
             else if (enumerator.Current.Type is SyntaxTokenType.LParen)
             {
+                leftParenToken = enumerator.Current;
                 // skip '('
                 MoveNextWithValidation(ref enumerator);
                 enumerator.SkipEoL();
@@ -544,9 +550,9 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
 
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
 
-        return new TableMethodDeclarationStatementNode(names.AsSpan().ToArray(), identifiers, statements, hasVarArg, hasSelfParameter, functionToken.Position);
+        return new TableMethodDeclarationStatementNode(names.AsSpan().ToArray(), identifiers, statements, hasVarArg, hasSelfParameter, functionToken.Position, leftParenToken.Position.Line, endToken.Position.Line);
     }
 
     bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result)
@@ -579,7 +585,7 @@ public ref struct Parser
         // nested table access & function call
     RECURSIVE:
         enumerator.SkipEoL();
-        
+
         var nextType = enumerator.GetNext().Type;
         if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon)
         {
@@ -851,9 +857,9 @@ public ref struct Parser
         // skip 'function' keyword
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
         enumerator.SkipEoL();
-        
-        var (_, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, true);
-        return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, functionToken.Position);
+
+        var (_, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, true);
+        return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined);
     }
 
     ExpressionNode[] ParseCallFunctionArguments(ref SyntaxTokenEnumerator enumerator)
@@ -946,20 +952,22 @@ public ref struct Parser
         return buffer.AsSpan().ToArray();
     }
 
-    StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endToken)
+    StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endTokenType, out SyntaxToken endToken)
     {
         using var statements = new PooledList<StatementNode>(64);
 
         // parse statements
         while (enumerator.MoveNext())
         {
-            if (enumerator.Current.Type == endToken) break;
+            if (enumerator.Current.Type == endTokenType) break;
             if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
 
             var node = ParseStatement(ref enumerator);
             statements.Add(node);
         }
 
+        endToken = enumerator.Current;
+
         return statements.AsSpan().ToArray();
     }
 

+ 6 - 0
src/Lua/LuaFunctionExecutionContext.cs

@@ -83,6 +83,12 @@ public readonly record struct LuaFunctionExecutionContext
         }
 
         var arg = Arguments[index];
+        
+        if(arg.Type is LuaValueType.Nil)
+        {
+            return defaultValue;
+        }
+        
         if (!arg.TryRead<T>(out var argValue))
         {
             var t = typeof(T);

+ 3 - 0
src/Lua/LuaState.cs

@@ -19,10 +19,13 @@ public sealed class LuaState
     readonly LuaTable registry = new();
     readonly UpValue envUpValue;
     bool isRunning;
+    
+    FastStackCore<LuaDebug.LuaDebugBuffer> debugBufferPool;
 
     internal UpValue EnvUpValue => envUpValue;
     internal ref FastStackCore<LuaThread> ThreadStack => ref threadStack;
     internal ref FastListCore<UpValue> OpenUpValues => ref openUpValues;
+    internal ref FastStackCore<LuaDebug.LuaDebugBuffer> DebugBufferPool => ref debugBufferPool;
 
     public LuaTable Environment => environment;
     public LuaTable Registry => registry;

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

@@ -16,4 +16,5 @@ public record struct CallStackFrame
 public enum CallStackFrameFlags
 {
     ReversedLe = 1,
+    TailCall
 }

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

@@ -14,8 +14,10 @@ public sealed class Chunk
     public required LocalValueInfo[] Locals { get; init; }
     public required Chunk[] Functions { get; init; }
     public required int ParameterCount { get; init; }
-    
+    public required bool HasVariableArguments { get; init; }
     public required byte MaxStackPosition { get; init; }
+    public required int LineDefined { get; init; }
+    public required int LastLineDefined { get; init; }
 
     Chunk? rootCache;
 

+ 28 - 4
src/Lua/Runtime/Tracebacks.cs

@@ -49,7 +49,7 @@ public class Traceback
 
         list.AddRange("stack traceback:\n");
         var intFormatBuffer = (stackalloc char[15]);
-
+        var shortSourceBuffer = (stackalloc char[59]);
         for (var index = stackFrames.Length - 1; index >= 0; index--)
         {
             LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : rootFunc;
@@ -65,13 +65,37 @@ public class Traceback
                 var p = closure.Proto;
                 var root = p.GetRoot();
                 list.AddRange("\t");
-                list.AddRange(root.Name);
+                var len = LuaDebug.WriteShortSource(root.Name, shortSourceBuffer);
+                list.AddRange(shortSourceBuffer[..len]);
                 list.AddRange(":");
                 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(": in ");
+                if (root == p)
+                {
+                    list.AddRange("main chunk");
+                    list.AddRange("\n");
+                    continue;
+                }
+
+                var caller = index > 1 ? stackFrames[index - 2].Function : rootFunc;
+                if (index > 0 && caller is Closure callerClosure)
+                {
+                    var t = LuaDebug.GetFuncName(callerClosure.Proto, stackFrames[index - 1].CallerInstructionIndex, out var name);
+                    if (t is not null and not "global")
+                    {
+                        list.AddRange(t);
+                        list.AddRange(" '");
+                        list.AddRange(name);
+                        list.AddRange("'\n");
+                        continue;
+                    }
+                }
+
+                list.AddRange("function '");
                 list.AddRange(p.Name);
-                list.AddRange("'\n");
+                list.AddRange("\n");
             }
         }
 

+ 105 - 4
src/Lua/Standard/DebugLibrary.cs

@@ -1,6 +1,6 @@
 using System.Runtime.CompilerServices;
 using Lua.Runtime;
-
+using Lua.Internal;
 namespace Lua.Standard;
 
 public class DebugLibrary
@@ -19,7 +19,8 @@ public class DebugLibrary
             new("setmetatable", SetMetatable),
             new("traceback", Traceback),
             new("getregistry", GetRegistry),
-            new("upvaluejoin", UpValueJoin)
+            new("upvaluejoin", UpValueJoin),
+            new("getinfo", GetInfo),
         ];
     }
 
@@ -252,10 +253,10 @@ public class DebugLibrary
 
     public ValueTask<int> Traceback(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
-        var thread = (GetLuaThread(context, out var argOffset));
+        var thread = GetLuaThread(context, out var argOffset);
 
         var message = context.GetArgumentOrDefault(argOffset);
-        var level = context.GetArgumentOrDefault<int>(argOffset + 1, 1);
+        var level = context.GetArgumentOrDefault<int>(argOffset + 1, 0);
 
 
         if (message.Type is not (LuaValueType.Nil or LuaValueType.String or LuaValueType.Number))
@@ -313,4 +314,104 @@ public class DebugLibrary
         upValues1[n1 - 1] = upValues2[n2 - 1];
         return new(0);
     }
+
+    public ValueTask<int> GetInfo(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        //return new(0);
+        var thread = GetLuaThread(context, out var argOffset);
+        string what = context.GetArgumentOrDefault<string>(argOffset + 1,"flnStu");
+        CallStackFrame? previousFrame = null;
+        CallStackFrame? currentFrame = null;
+        int pc = 0;
+        var arg1 = context.GetArgument(argOffset);
+
+        if (arg1.TryReadFunction(out var functionToInspect))
+        {
+            //what = ">" + what;
+        }
+        else if (arg1.TryReadNumber(out _))
+        {
+            var level = context.GetArgument<int>(argOffset) + 1;
+            
+            var callStack = thread.GetCallStackFrames();
+            if (level <= 0 || level > callStack.Length)
+            {
+                context.ThrowBadArgument(1, "level out of range");
+            }
+            currentFrame = thread.GetCallStackFrames()[^(level)];
+            functionToInspect = currentFrame.Value.Function;
+            previousFrame = level +1 <= callStack.Length?callStack[^(level + 1)]:null;
+            if(level!=0)
+            {
+                pc = thread.GetCallStackFrames()[^(level - 1)].CallerInstructionIndex;
+            }
+            
+        }
+        else
+        {
+            context.ThrowBadArgument(argOffset, "function or level expected");
+        }
+
+        using var debug = LuaDebug.Create(context.State, previousFrame, currentFrame, functionToInspect, pc, what, out var isValid);
+        if (!isValid)
+        {
+            context.ThrowBadArgument(argOffset + 1, "invalid option");
+        }
+
+        var table = new LuaTable(0, 1);
+        if (what.Contains('S'))
+        {
+            table["source"] = debug.Source ?? LuaValue.Nil;
+            table["short_src"] = debug.ShortSource.ToString();
+            table["linedefined"] = debug.LineDefined;
+            table["lastlinedefined"] = debug.LastLineDefined;
+            table["what"] = debug.What?? LuaValue.Nil;;
+        }
+
+        if (what.Contains('l'))
+        {
+            table["currentline"] = debug.CurrentLine;
+        }
+
+        if (what.Contains('u'))
+        {
+            table["nups"] = debug.UpValueCount;
+            table["nparams"] = debug.ParameterCount;
+            table["isvararg"] = debug.IsVarArg;
+        }
+
+        if (what.Contains('n'))
+        {
+            table["name"] = debug.Name?? LuaValue.Nil;;
+            table["namewhat"] = debug.NameWhat?? LuaValue.Nil;;
+        }
+
+        if (what.Contains('t'))
+        {
+            table["istailcall"] = debug.IsTailCall;
+        }
+
+        if (what.Contains('f'))
+        {
+            table["func"] = functionToInspect;
+        }
+
+        if (what.Contains('L'))
+        {
+            var activeLines = new LuaTable(0, 8);
+            if (functionToInspect is Closure closure)
+            {
+                foreach (var pos in closure.Proto.SourcePositions)
+                {
+                    activeLines[pos.Line] = true;
+                }
+            }
+
+            table["activelines"] = activeLines;
+        }
+
+        buffer.Span[0] = table;
+
+        return new(1);
+    }
 }

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

@@ -43,6 +43,9 @@ public sealed class TableLibrary
         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,
+        HasVariableArguments = false,
+        LineDefined = 0,
+        LastLineDefined = 0,
     };
 
     public ValueTask<int> Concat(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)

+ 3 - 3
tests/Lua.Tests/LuaTests.cs

@@ -12,7 +12,7 @@ public class LuaTests
         state = LuaState.Create();
         state.OpenStandardLibraries();
     }
-    
+
     [Test]
     public async Task Test_Closure()
     {
@@ -54,11 +54,11 @@ 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"));
+        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/db_mini.lua"));
     }
 
     [Test]

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

@@ -1,82 +0,0 @@
--- 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
-}
-
-local a = 1
-local b = 2
-local function f()
-    return a
-end
-local function g()
-    return b
-end
-
-debug.upvaluejoin(f,1,g,1)
-
-assert(f() == 2)
-b=3
-assert(f() == 3)
-
-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")

+ 227 - 0
tests/Lua.Tests/tests-lua/db_mini.lua

@@ -0,0 +1,227 @@
+-- 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")
+
+-- test file and string names truncation
+a = "function f () end"
+local function dostring (s, x)
+    return load(s, x)()
+end
+dostring(a)
+assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a))
+dostring(a .. string.format("; %s\n=1", string.rep('p', 400)))
+assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
+dostring(a .. string.format("; %s=1", string.rep('p', 400)))
+assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
+dostring("\n" .. a)
+assert(debug.getinfo(f).short_src == '[string "..."]')
+dostring(a, "")
+assert(debug.getinfo(f).short_src == '[string ""]')
+dostring(a, "@xuxu")
+assert(debug.getinfo(f).short_src == "xuxu")
+dostring(a, "@" .. string.rep('p', 1000) .. 't')
+assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$"))
+dostring(a, "=xuxu")
+assert(debug.getinfo(f).short_src == "xuxu")
+dostring(a, string.format("=%s", string.rep('x', 500)))
+assert(string.find(debug.getinfo(f).short_src, "^x*$"))
+dostring(a, "=")
+assert(debug.getinfo(f).short_src == "")
+a = nil;
+f = nil;
+
+repeat
+    local g = { x = function()
+        local a = debug.getinfo(2)
+        assert(a.name == 'f' and a.namewhat == 'local')
+        a = debug.getinfo(1)
+        assert(a.name == 'x' and a.namewhat == 'field')
+        return 'xixi'
+    end }
+    local f = function()
+        return 1 + 1 and (not 1 or g.x())
+    end
+    assert(f() == 'xixi')
+    g = debug.getinfo(f)
+    assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
+
+    function f (x, name)
+        -- local!
+        if not name then
+            name = 'f'
+        end
+        local a = debug.getinfo(1)
+        print(a.name, a.namewhat, name)
+        assert(a.name == name and a.namewhat == 'local')
+        return x
+    end
+
+    -- breaks in different conditions
+    if 3 > 4 then
+        break
+    end ;
+    f()
+    if 3 < 4 then
+        a = 1
+    else
+        break
+    end ;
+    f()
+    while 1 do
+        local x = 10;
+        break
+    end ;
+    f()
+    local b = 1
+    if 3 > 4 then
+        return math.sin(1)
+    end ;
+    f()
+    a = 3 < 4;
+    f()
+    a = 3 < 4 or 1;
+    f()
+    repeat local x = 20;
+        if 4 > 3 then
+            f()
+        else
+            break
+        end ;
+        f() until 1
+    g = {}
+    f(g).x = f(2) and f(10) + f(9)
+    assert(g.x == f(19))
+    function g(x)
+        if not x then
+            return 3
+        end
+        return (x('a', 'x'))
+    end
+    assert(g(f) == 'a')
+until 1
+
+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
+}
+
+local a = 1
+local b = 2
+local function f()
+    return a
+end
+local function g()
+    return b
+end
+
+debug.upvaluejoin(f, 1, g, 1)
+
+assert(f() == 2)
+b = 3
+assert(f() == 3)
+
+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")
+
+-- testing nparams, nups e isvararg
+local t = debug.getinfo(print, "u")
+assert(t.isvararg == true and t.nparams == 0 and t.nups == 0)
+
+t = debug.getinfo(function(a, b, c)
+end, "u")
+assert(t.isvararg == false and t.nparams == 3 and t.nups == 0)
+
+t = debug.getinfo(function(a, b, ...)
+    return t[a]
+end, "u")
+assert(t.isvararg == true and t.nparams == 2 and t.nups == 1)
+
+t = debug.getinfo(1)   -- main
+assert(t.isvararg == true and t.nparams == 0 and t.nups == 1 and
+        debug.getupvalue(t.func, 1) == "_ENV")
+
+
+-- testing debugging of coroutines
+
+local function checktraceback (co, p, level)
+    local tb = debug.traceback(co, nil, level)
+    local i = 0
+    for l in string.gmatch(tb, "[^\n]+\n?") do
+        assert(i == 0 or string.find(l, p[i]))
+        i = i + 1
+    end
+    assert(p[i] == nil)
+end
+
+local function f (n)
+    if n > 0 then
+        f(n - 1)
+    else
+        coroutine.yield()
+    end
+end
+
+local co = coroutine.create(f)
+coroutine.resume(co, 3)
+checktraceback(co, { "yield", "db_mini.lua", "db_mini.lua", "db_mini.lua", "db_mini.lua" })
+checktraceback(co, { "db_mini.lua", "db_mini.lua", "db_mini.lua", "db_mini.lua" }, 1)
+checktraceback(co, { "db_mini.lua", "db_mini.lua", "db_mini.lua" }, 2)
+checktraceback(co, { "db_mini.lua" }, 4)
+checktraceback(co, {}, 40)