Browse Source

Merge branch 'main' into fix-conditionals

Akeit0 8 months ago
parent
commit
f9b12b56ff
63 changed files with 3319 additions and 343 deletions
  1. 234 0
      .editorconfig
  2. 23 3
      sandbox/ConsoleApp1/Program.cs
  3. 1 0
      src/Lua/CodeAnalysis/Compilation/Descriptions.cs
  4. 42 2
      src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs
  5. 73 25
      src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs
  6. 9 0
      src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs
  7. 10 4
      src/Lua/CodeAnalysis/Syntax/Lexer.cs
  8. 1 1
      src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs
  9. 1 1
      src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs
  10. 1 1
      src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs
  11. 1 1
      src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs
  12. 1 0
      src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs
  13. 1 1
      src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs
  14. 1 1
      src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs
  15. 1 1
      src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs
  16. 50 28
      src/Lua/CodeAnalysis/Syntax/Parser.cs
  17. 5 0
      src/Lua/Exceptions.cs
  18. 38 0
      src/Lua/Internal/BitFlags.cs
  19. 14 1
      src/Lua/Internal/FastStackCore.cs
  20. 14 19
      src/Lua/Internal/HexConverter.cs
  21. 696 0
      src/Lua/Internal/LuaDebug.cs
  22. 22 1
      src/Lua/Internal/MathEx.cs
  23. 4 3
      src/Lua/Internal/StringHelper.cs
  24. 6 2
      src/Lua/LuaCoroutine.cs
  25. 8 1
      src/Lua/LuaFunction.cs
  26. 66 4
      src/Lua/LuaFunctionExecutionContext.cs
  27. 37 6
      src/Lua/LuaState.cs
  28. 1 1
      src/Lua/LuaStateExtensions.cs
  29. 21 6
      src/Lua/LuaTable.cs
  30. 36 3
      src/Lua/LuaThread.cs
  31. 16 0
      src/Lua/LuaUserData.cs
  32. 25 0
      src/Lua/LuaValue.cs
  33. 6 0
      src/Lua/Runtime/CSharpClosure.cs
  34. 5 1
      src/Lua/Runtime/CallStackFrame.cs
  35. 4 1
      src/Lua/Runtime/Chunk.cs
  36. 9 0
      src/Lua/Runtime/LocalValueInfo.cs
  37. 5 4
      src/Lua/Runtime/LuaClosure.cs
  38. 2 2
      src/Lua/Runtime/LuaValueRuntimeExtensions.cs
  39. 215 0
      src/Lua/Runtime/LuaVirtualMachine.Debug.cs
  40. 249 117
      src/Lua/Runtime/LuaVirtualMachine.cs
  41. 131 17
      src/Lua/Runtime/Tracebacks.cs
  42. 17 6
      src/Lua/Standard/BasicLibrary.cs
  43. 9 2
      src/Lua/Standard/CoroutineLibrary.cs
  44. 652 0
      src/Lua/Standard/DebugLibrary.cs
  45. 4 3
      src/Lua/Standard/FileHandle.cs
  46. 10 3
      src/Lua/Standard/IOLibrary.cs
  47. 39 0
      src/Lua/Standard/Internal/ConsoleHelper.cs
  48. 31 0
      src/Lua/Standard/Internal/LuaPlatformUtility.cs
  49. 18 7
      src/Lua/Standard/MathematicsLibrary.cs
  50. 1 1
      src/Lua/Standard/ModuleLibrary.cs
  51. 19 5
      src/Lua/Standard/OpenLibsExtensions.cs
  52. 7 3
      src/Lua/Standard/StringLibrary.cs
  53. 5 1
      src/Lua/Standard/TableLibrary.cs
  54. 5 1
      tests/Lua.Tests/HexConverterTests.cs
  55. 25 0
      tests/Lua.Tests/LuaObjectTests.cs
  56. 7 1
      tests/Lua.Tests/LuaTests.cs
  57. 67 1
      tests/Lua.Tests/MetatableTests.cs
  58. 2 2
      tests/Lua.Tests/ParserTests.cs
  59. 17 0
      tests/Lua.Tests/StringTests.cs
  60. 21 0
      tests/Lua.Tests/TableTests.cs
  61. 32 32
      tests/Lua.Tests/tests-lua/coroutine.lua
  62. 19 17
      tests/Lua.Tests/tests-lua/db.lua
  63. 227 0
      tests/Lua.Tests/tests-lua/db_mini.lua

+ 234 - 0
.editorconfig

@@ -0,0 +1,234 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_collection_expression = when_types_loosely_match
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:silent
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = false
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = false
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true
+csharp_style_expression_bodied_constructors = false
+csharp_style_expression_bodied_indexers = true
+csharp_style_expression_bodied_lambdas = true
+csharp_style_expression_bodied_local_functions = false
+csharp_style_expression_bodied_methods = false
+csharp_style_expression_bodied_operators = false
+csharp_style_expression_bodied_properties = true
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true
+
+# Modifier preferences
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_style_prefer_readonly_struct = true
+csharp_style_prefer_readonly_struct_member = true
+
+# Code-block preferences
+csharp_prefer_braces = true
+csharp_prefer_simple_using_statement = true
+csharp_style_namespace_declarations = block_scoped
+csharp_style_prefer_method_group_conversion = true
+csharp_style_prefer_primary_constructors = true
+csharp_style_prefer_top_level_statements = true
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true
+csharp_style_prefer_utf8_string_literals = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers = 
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix = 
+dotnet_naming_style.pascal_case.required_suffix = 
+dotnet_naming_style.pascal_case.word_separator = 
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix = 
+dotnet_naming_style.begins_with_i.word_separator = 
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+
+dotnet_diagnostic.IDE0055.severity = warning

+ 23 - 3
sandbox/ConsoleApp1/Program.cs

@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
 using Lua.CodeAnalysis.Syntax;
 using Lua.CodeAnalysis.Syntax;
 using Lua.CodeAnalysis.Compilation;
 using Lua.CodeAnalysis.Compilation;
 using Lua.Runtime;
 using Lua.Runtime;
@@ -11,7 +12,7 @@ state.Environment["vec3"] = new LVec3();
 
 
 try
 try
 {
 {
-    var source = File.ReadAllText("test.lua");
+    var source = File.ReadAllText(GetAbsolutePath("test.lua"));
 
 
     var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua");
     var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua");
 
 
@@ -41,6 +42,15 @@ try
 catch (Exception ex)
 catch (Exception ex)
 {
 {
     Console.WriteLine(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)
 static void DebugChunk(Chunk chunk, int id)
@@ -56,14 +66,24 @@ static void DebugChunk(Chunk chunk, int id)
         index++;
         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())
     foreach (var constant in chunk.Constants.ToArray())
     {
     {
         Console.WriteLine($"[{index}]\t{constant}");
         Console.WriteLine($"[{index}]\t{constant}");
         index++;
         index++;
     }
     }
 
 
-    Console.WriteLine("UpValues " + new string('-', 50)); index = 0;
+    Console.WriteLine("UpValues " + new string('-', 50));
+    index = 0;
     foreach (var upValue in chunk.UpValues.ToArray())
     foreach (var upValue in chunk.UpValues.ToArray())
     {
     {
         Console.WriteLine($"[{index}]\t{upValue.Name}\t{(upValue.IsInRegister ? 1 : 0)}\t{upValue.Index}");
         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 readonly record struct LocalVariableDescription
     {
     {
         public required byte RegisterIndex { get; init; }
         public required byte RegisterIndex { get; init; }
+        public required int StartPc { get; init; }
     }
     }
 
 
     public readonly record struct FunctionDescription
     public readonly record struct FunctionDescription

+ 42 - 2
src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs

@@ -60,6 +60,7 @@ public class FunctionCompilationContext : IDisposable
 
 
     // upvalues
     // upvalues
     FastListCore<UpValueInfo> upvalues;
     FastListCore<UpValueInfo> upvalues;
+    FastListCore<LocalValueInfo> localVariables;
 
 
     // loop
     // loop
     FastListCore<BreakDescription> breakQueue;
     FastListCore<BreakDescription> breakQueue;
@@ -90,6 +91,16 @@ public class FunctionCompilationContext : IDisposable
     /// </summary>
     /// </summary>
     public bool HasVariableArguments { get; set; }
     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>
     /// <summary>
     /// Parent scope context
     /// Parent scope context
     /// </summary>
     /// </summary>
@@ -260,6 +271,17 @@ public class FunctionCompilationContext : IDisposable
                     return;
                     return;
                 }
                 }
 
 
+                break;
+            case OpCode.Return:
+                if (lastInstruction.OpCode == OpCode.Move && instruction.B == 2 && lastInstruction.B < 256)
+                {
+                    lastInstruction = instruction with { A = (byte)lastInstruction.B };
+
+                    instructionPositions[^1] = position;
+                    incrementStackPosition = false;
+                    return;
+                }
+
                 break;
                 break;
         }
         }
 
 
@@ -310,6 +332,17 @@ public class FunctionCompilationContext : IDisposable
         }
         }
     }
     }
 
 
+    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)
     public void AddUpValue(UpValueInfo upValue)
     {
     {
         upvalues.Add(upValue);
         upvalues.Add(upValue);
@@ -418,8 +451,10 @@ public class FunctionCompilationContext : IDisposable
     {
     {
         // add return
         // add return
         instructions.Add(Instruction.Return(0, 1));
         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));
         var chunk = new Chunk()
         var chunk = new Chunk()
         {
         {
             Name = ChunkName ?? "chunk",
             Name = ChunkName ?? "chunk",
@@ -427,9 +462,13 @@ public class FunctionCompilationContext : IDisposable
             SourcePositions = instructionPositions.AsSpan().ToArray(),
             SourcePositions = instructionPositions.AsSpan().ToArray(),
             Constants = constants.AsSpan().ToArray(),
             Constants = constants.AsSpan().ToArray(),
             UpValues = upvalues.AsSpan().ToArray(),
             UpValues = upvalues.AsSpan().ToArray(),
+            Locals = locals,
             Functions = functions.AsSpan().ToArray(),
             Functions = functions.AsSpan().ToArray(),
             ParameterCount = ParameterCount,
             ParameterCount = ParameterCount,
+            HasVariableArguments = HasVariableArguments,
             MaxStackPosition = MaxStackPosition,
             MaxStackPosition = MaxStackPosition,
+            LineDefined = LineDefined,
+            LastLineDefined = LastLineDefined,
         };
         };
 
 
         foreach (var function in functions.AsSpan())
         foreach (var function in functions.AsSpan())
@@ -451,6 +490,7 @@ public class FunctionCompilationContext : IDisposable
         constantIndexMap.Clear();
         constantIndexMap.Clear();
         constants.Clear();
         constants.Clear();
         upvalues.Clear();
         upvalues.Clear();
+        localVariables.Clear();
         functionMap.Clear();
         functionMap.Clear();
         functions.Clear();
         functions.Clear();
         breakQueue.Clear();
         breakQueue.Clear();

+ 73 - 25
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -20,7 +20,9 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     public Chunk Compile(LuaSyntaxTree syntaxTree, string? chunkName = null)
     public Chunk Compile(LuaSyntaxTree syntaxTree, string? chunkName = null)
     {
     {
         using var context = FunctionCompilationContext.Create(null);
         using var context = FunctionCompilationContext.Create(null);
-
+        context.HasVariableArguments = true;
+        context.LineDefined = syntaxTree.Position.Line;
+        context.LastLineDefined = syntaxTree.Position.Line;
         // set global enviroment upvalue
         // set global enviroment upvalue
         context.AddUpValue(new()
         context.AddUpValue(new()
         {
         {
@@ -472,6 +474,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
                 context.AddLocalVariable(identifier.Name, new()
                 context.AddLocalVariable(identifier.Name, new()
                 {
                 {
                     RegisterIndex = (byte)(context.StackPosition - 1),
                     RegisterIndex = (byte)(context.StackPosition - 1),
+                    StartPc = context.Function.Instructions.Length,
                 });
                 });
             }
             }
         }
         }
@@ -591,10 +594,10 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     // function declaration
     // function declaration
     public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context)
     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.EndPosition.Line);
 
 
         // push closure instruction
         // push closure instruction
-        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
+        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
 
 
         return true;
         return true;
     }
     }
@@ -605,26 +608,27 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         context.AddLocalVariable(node.Name, new()
         context.AddLocalVariable(node.Name, new()
         {
         {
             RegisterIndex = context.StackPosition,
             RegisterIndex = context.StackPosition,
+            StartPc = context.Function.Instructions.Length,
         });
         });
 
 
         // compile function
         // 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.EndPosition.Line);
 
 
         // push closure instruction
         // push closure instruction
-        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
+        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
 
 
         return true;
         return true;
     }
     }
 
 
     public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context)
     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.EndPosition.Line);
 
 
         // add closure
         // add closure
         var index = context.Function.GetConstantIndex(node.Name.ToString());
         var index = context.Function.GetConstantIndex(node.Name.ToString());
 
 
         // push closure instruction
         // push closure instruction
-        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
+        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
 
 
         if (context.TryGetLocalVariableInThisScope(node.Name, out var variable))
         if (context.TryGetLocalVariableInThisScope(node.Name, out var variable))
         {
         {
@@ -643,7 +647,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context)
     public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context)
     {
     {
         var funcIdentifier = node.MemberPath[^1];
         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.EndPosition.Line);
 
 
         // add closure
         // add closure
         var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString());
         var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString());
@@ -664,7 +668,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
 
         // push closure instruction
         // push closure instruction
         var closureIndex = context.StackPosition;
         var closureIndex = context.StackPosition;
-        context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.Position, true);
+        context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.EndPosition, true);
 
 
         // set table
         // set table
         context.PushInstruction(Instruction.SetTable(tableIndex, (ushort)(index + 256), closureIndex), funcIdentifier.Position);
         context.PushInstruction(Instruction.SetTable(tableIndex, (ushort)(index + 256), closureIndex), funcIdentifier.Position);
@@ -673,18 +677,21 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         return true;
         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();
         using var funcContext = context.CreateChildFunction();
         funcContext.ChunkName = functionName.ToString();
         funcContext.ChunkName = functionName.ToString();
         funcContext.ParameterCount = parameterCount;
         funcContext.ParameterCount = parameterCount;
         funcContext.HasVariableArguments = hasVarArg;
         funcContext.HasVariableArguments = hasVarArg;
+        funcContext.LineDefined = lineDefined;
+        funcContext.LastLineDefined = lastLineDefined;
 
 
         if (hasSelfParameter)
         if (hasSelfParameter)
         {
         {
             funcContext.Scope.AddLocalVariable("self".AsMemory(), new()
             funcContext.Scope.AddLocalVariable("self".AsMemory(), new()
             {
             {
                 RegisterIndex = 0,
                 RegisterIndex = 0,
+                StartPc = 0,
             });
             });
 
 
             funcContext.Scope.StackPosition++;
             funcContext.Scope.StackPosition++;
@@ -697,6 +704,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             funcContext.Scope.AddLocalVariable(parameter.Name, new()
             funcContext.Scope.AddLocalVariable(parameter.Name, new()
             {
             {
                 RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)),
                 RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)),
+                StartPc = 0,
             });
             });
 
 
             funcContext.Scope.StackPosition++;
             funcContext.Scope.StackPosition++;
@@ -758,10 +766,10 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         // if
         // if
         using (var scopeContext = context.CreateChildScope())
         using (var scopeContext = context.CreateChildScope())
         {
         {
-            CompileConditionNode(node.IfNode.ConditionNode, scopeContext, true);
+            CompileConditionNode(node.IfNode.ConditionNode, scopeContext, true, node.IfNode.Position);
 
 
             var ifPosition = scopeContext.Function.Instructions.Length;
             var ifPosition = scopeContext.Function.Instructions.Length;
-            scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.Position);
+            scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.IfNode.Position);
 
 
             foreach (var childNode in node.IfNode.ThenNodes)
             foreach (var childNode in node.IfNode.ThenNodes)
             {
             {
@@ -772,7 +780,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             if (hasElse)
             if (hasElse)
             {
             {
                 endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
                 endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
-                scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.Position, true);
+                scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.IfNode.ThenNodes[^1].Position, true);
             }
             }
             else
             else
             {
             {
@@ -790,7 +798,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             CompileConditionNode(elseIf.ConditionNode, scopeContext, true);
             CompileConditionNode(elseIf.ConditionNode, scopeContext, true);
 
 
             var elseifPosition = scopeContext.Function.Instructions.Length;
             var elseifPosition = scopeContext.Function.Instructions.Length;
-            scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.Position);
+            scopeContext.PushInstruction(Instruction.Jmp(0, 0), elseIf.Position);
 
 
             foreach (var childNode in elseIf.ThenNodes)
             foreach (var childNode in elseIf.ThenNodes)
             {
             {
@@ -802,11 +810,11 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             if (hasElse)
             if (hasElse)
             {
             {
                 endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
                 endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
-                scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.Position);
+                scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), elseIf.Position);
             }
             }
             else
             else
             {
             {
-                scopeContext.TryPushCloseUpValue(stackPositionToClose, node.Position);
+                scopeContext.TryPushCloseUpValue(stackPositionToClose, elseIf.Position);
             }
             }
 
 
             scopeContext.Function.Instructions[elseifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - elseifPosition;
             scopeContext.Function.Instructions[elseifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - elseifPosition;
@@ -847,8 +855,9 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
 
         CompileConditionNode(node.ConditionNode, scopeContext, true);
         CompileConditionNode(node.ConditionNode, scopeContext, true);
         var a = scopeContext.HasCapturedLocalVariables ? (byte)(stackPosition + 1) : (byte)0;
         var a = scopeContext.HasCapturedLocalVariables ? (byte)(stackPosition + 1) : (byte)0;
-        scopeContext.PushInstruction(Instruction.Jmp(a, startIndex - scopeContext.Function.Instructions.Length - 1), node.Position);
-        scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
+        var untilPosition = node.ConditionNode.Position;
+        scopeContext.PushInstruction(Instruction.Jmp(a, startIndex - scopeContext.Function.Instructions.Length - 1), untilPosition);
+        scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, untilPosition);
 
 
         context.Function.LoopLevel--;
         context.Function.LoopLevel--;
 
 
@@ -902,20 +911,39 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         else
         else
         {
         {
             var index = context.Function.GetConstantIndex(1);
             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;
         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
         // compile statements
         context.Function.LoopLevel++;
         context.Function.LoopLevel++;
         using var scopeContext = context.CreateChildScope();
         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
             // add local variable
             scopeContext.AddLocalVariable(node.VariableName, new()
             scopeContext.AddLocalVariable(node.VariableName, new()
             {
             {
-                RegisterIndex = startPosition
+                RegisterIndex = (byte)(startPosition + 3),
+                StartPc = context.Function.Instructions.Length,
             });
             });
 
 
             foreach (var childNode in node.StatementNodes)
             foreach (var childNode in node.StatementNodes)
@@ -948,7 +976,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
 
         // jump to TFORCALL
         // jump to TFORCALL
         var startJumpIndex = context.Function.Instructions.Length;
         var startJumpIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
+        context.PushInstruction(Instruction.Jmp(0, 0), node.DoPosition);
 
 
         // compile statements
         // compile statements
         context.Function.LoopLevel++;
         context.Function.LoopLevel++;
@@ -956,13 +984,32 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         {
         {
             scopeContext.StackPosition = (byte)(startPosition + 3 + node.Names.Length);
             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
             // add local variables
             for (int i = 0; i < node.Names.Length; i++)
             for (int i = 0; i < node.Names.Length; i++)
             {
             {
                 var name = node.Names[i];
                 var name = node.Names[i];
                 scopeContext.AddLocalVariable(name.Name, new()
                 scopeContext.AddLocalVariable(name.Name, new()
                 {
                 {
-                    RegisterIndex = (byte)(startPosition + 3 + i)
+                    RegisterIndex = (byte)(startPosition + 3 + i),
+                    StartPc = context.Function.Instructions.Length,
                 });
                 });
             }
             }
 
 
@@ -1199,7 +1246,8 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     /// <param name="node">Condition node</param>
     /// <param name="node">Condition node</param>
     /// <param name="context">Context</param>
     /// <param name="context">Context</param>
     /// <param name="falseIsSkip">If true, generates an instruction sequence that skips the next instruction if the condition is false.</param>
     /// <param name="falseIsSkip">If true, generates an instruction sequence that skips the next instruction if the condition is false.</param>
-    void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, bool falseIsSkip)
+    /// <param name="testPosition">Position of the test instruction</param>
+    void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, bool falseIsSkip, SourcePosition? testPosition = null)
     {
     {
         if (node is BinaryExpressionNode binaryExpression)
         if (node is BinaryExpressionNode binaryExpression)
         {
         {
@@ -1251,7 +1299,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         }
         }
 
 
         node.Accept(this, context);
         node.Accept(this, context);
-        context.PushInstruction(Instruction.Test((byte)(context.StackPosition - 1), falseIsSkip ? (byte)0 : (byte)1), node.Position);
+        context.PushInstruction(Instruction.Test((byte)(context.StackPosition - 1), falseIsSkip ? (byte)0 : (byte)1), testPosition ?? node.Position);
     }
     }
 
 
     void CompileExpressionList(SyntaxNode rootNode, ExpressionNode[] expressions, int minimumCount, ScopeCompilationContext context)
     void CompileExpressionList(SyntaxNode rootNode, ExpressionNode[] expressions, int minimumCount, ScopeCompilationContext context)

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

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

+ 10 - 4
src/Lua/CodeAnalysis/Syntax/Lexer.cs

@@ -324,7 +324,13 @@ public ref struct Lexer
                 if (c is '\\')
                 if (c is '\\')
                 {
                 {
                     Advance(1);
                     Advance(1);
+
                     if (span.Length <= offset) break;
                     if (span.Length <= offset) break;
+                    if (span[offset] == '\r')
+                    {
+                        if (span.Length<=offset +1) continue;
+                        if (span[offset+1] == '\n')Advance(1);
+                    }
                 }
                 }
                 else if (c == quote)
                 else if (c == quote)
                 {
                 {
@@ -530,8 +536,8 @@ public ref struct Lexer
     static bool IsIdentifier(char c)
     static bool IsIdentifier(char c)
     {
     {
         return c == '_' ||
         return c == '_' ||
-            ('A' <= c && c <= 'Z') ||
-            ('a' <= c && c <= 'z') ||
-            StringHelper.IsNumber(c);
+               ('A' <= c && c <= 'Z') ||
+               ('a' <= c && c <= 'z') ||
+               StringHelper.IsNumber(c);
     }
     }
-}
+}

+ 1 - 1
src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax;
 namespace Lua.CodeAnalysis.Syntax;
 
 
-public record LuaSyntaxTree(SyntaxNode[] Nodes) : SyntaxNode(new SourcePosition(0, 0))
+public record LuaSyntaxTree(SyntaxNode[] Nodes,SourcePosition Position) : SyntaxNode(Position)
 {
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {
     {

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

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 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,SourcePosition EndPosition) : ExpressionNode(Position)
 {
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     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;
 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,SourcePosition EndPosition) : StatementNode(Position)
 {
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     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;
 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)
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {
     {

+ 1 - 0
src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs

@@ -4,6 +4,7 @@ public record IfStatementNode(IfStatementNode.ConditionAndThenNodes IfNode, IfSt
 {
 {
     public record ConditionAndThenNodes
     public record ConditionAndThenNodes
     {
     {
+        public SourcePosition Position;
         public required ExpressionNode ConditionNode;
         public required ExpressionNode ConditionNode;
         public required StatementNode[] ThenNodes;
         public required StatementNode[] ThenNodes;
     }
     }

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

@@ -1,6 +1,6 @@
 namespace Lua.CodeAnalysis.Syntax.Nodes;
 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,SourcePosition EndPosition) : FunctionDeclarationStatementNode(Name, ParameterNodes, Nodes, HasVariableArguments, Position, LineDefined, EndPosition)
 {
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     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;
 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)
     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;
 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,SourcePosition EndPosition) : StatementNode(Position)
 {
 {
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
     {
     {

+ 50 - 28
src/Lua/CodeAnalysis/Syntax/Parser.cs

@@ -33,8 +33,19 @@ public ref struct Parser
             var node = ParseStatement(ref enumerator);
             var node = ParseStatement(ref enumerator);
             root.Add(node);
             root.Add(node);
         }
         }
+        var tokensSpan = tokens.AsSpan();
+        var lastToken = tokensSpan[0];
+        for (int i = tokensSpan.Length-1; 0<i;i--)
+        {
+            var t = tokensSpan[i];
+            if (t.Type is not SyntaxTokenType.EndOfLine)
+            {
+                lastToken = t;
+                break;
+            }
+        }
 
 
-        var tree = new LuaSyntaxTree(root.AsSpan().ToArray());
+        var tree = new LuaSyntaxTree(root.AsSpan().ToArray(),lastToken.Position);
         Dispose();
         Dispose();
 
 
         return tree;
         return tree;
@@ -64,6 +75,7 @@ public ref struct Parser
 
 
                                 return ParseAssignmentStatement(firstExpression, ref enumerator);
                                 return ParseAssignmentStatement(firstExpression, ref enumerator);
                             }
                             }
+
                             break;
                             break;
                     }
                     }
                 }
                 }
@@ -168,7 +180,7 @@ public ref struct Parser
         var doToken = enumerator.Current;
         var doToken = enumerator.Current;
 
 
         // parse statements
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
 
 
         return new DoStatementNode(statements, doToken.Position);
         return new DoStatementNode(statements, doToken.Position);
     }
     }
@@ -208,6 +220,7 @@ public ref struct Parser
             enumerator.MovePrevious();
             enumerator.MovePrevious();
             return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), [], firstExpression.Position);
             return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), [], firstExpression.Position);
         }
         }
+
         MoveNextWithValidation(ref enumerator);
         MoveNextWithValidation(ref enumerator);
 
 
         // parse expressions
         // parse expressions
@@ -227,6 +240,7 @@ public ref struct Parser
             enumerator.MovePrevious();
             enumerator.MovePrevious();
             return new LocalAssignmentStatementNode(identifiers, [], localToken.Position);
             return new LocalAssignmentStatementNode(identifiers, [], localToken.Position);
         }
         }
+
         MoveNextWithValidation(ref enumerator);
         MoveNextWithValidation(ref enumerator);
 
 
         // parse expressions
         // parse expressions
@@ -248,6 +262,7 @@ public ref struct Parser
 
 
         // skip 'then' keyword
         // skip 'then' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Then);
         CheckCurrent(ref enumerator, SyntaxTokenType.Then);
+        var thenToken = enumerator.Current;
 
 
         using var builder = new PooledList<StatementNode>(64);
         using var builder = new PooledList<StatementNode>(64);
         using var elseIfBuilder = new PooledList<IfStatementNode.ConditionAndThenNodes>(64);
         using var elseIfBuilder = new PooledList<IfStatementNode.ConditionAndThenNodes>(64);
@@ -280,6 +295,7 @@ public ref struct Parser
                     case 0:
                     case 0:
                         ifNodes = new()
                         ifNodes = new()
                         {
                         {
+                            Position = thenToken.Position,
                             ConditionNode = condition,
                             ConditionNode = condition,
                             ThenNodes = builder.AsSpan().ToArray(),
                             ThenNodes = builder.AsSpan().ToArray(),
                         };
                         };
@@ -288,6 +304,7 @@ public ref struct Parser
                     case 1:
                     case 1:
                         elseIfBuilder.Add(new()
                         elseIfBuilder.Add(new()
                         {
                         {
+                            Position = thenToken.Position,
                             ConditionNode = condition,
                             ConditionNode = condition,
                             ThenNodes = builder.AsSpan().ToArray(),
                             ThenNodes = builder.AsSpan().ToArray(),
                         });
                         });
@@ -311,7 +328,7 @@ public ref struct Parser
 
 
                     // check 'then' keyword
                     // check 'then' keyword
                     CheckCurrent(ref enumerator, SyntaxTokenType.Then);
                     CheckCurrent(ref enumerator, SyntaxTokenType.Then);
-
+                    thenToken = enumerator.Current;
                     // set elseif state
                     // set elseif state
                     state = 1;
                     state = 1;
 
 
@@ -353,7 +370,7 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
 
 
         // parse statements
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
 
 
         return new WhileStatementNode(condition, statements, whileToken.Position);
         return new WhileStatementNode(condition, statements, whileToken.Position);
     }
     }
@@ -365,7 +382,7 @@ public ref struct Parser
         var repeatToken = enumerator.Current;
         var repeatToken = enumerator.Current;
 
 
         // parse statements
         // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until);
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until, out _);
 
 
         // skip 'until keyword'
         // skip 'until keyword'
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
@@ -418,11 +435,12 @@ public ref struct Parser
 
 
         // skip 'do' keyword
         // skip 'do' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
+        var doToken = enumerator.Current;
 
 
         // parse statements
         // 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)
     GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
@@ -433,33 +451,33 @@ public ref struct Parser
         // skip 'in' keyword
         // skip 'in' keyword
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _);
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _);
         enumerator.SkipEoL();
         enumerator.SkipEoL();
-
+        var iteratorToken = enumerator.Current;
         var expressions = ParseExpressionList(ref enumerator);
         var expressions = ParseExpressionList(ref enumerator);
         MoveNextWithValidation(ref enumerator);
         MoveNextWithValidation(ref enumerator);
         enumerator.SkipEoL();
         enumerator.SkipEoL();
 
 
         // skip 'do' keyword
         // skip 'do' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
-
+        var doToken = enumerator.Current;
         // parse statements
         // 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)
     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, EndPosition) = ParseFunctionDeclarationCore(ref enumerator, false);
+        return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, EndPosition);
     }
     }
 
 
     LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     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, EndPosition) = ParseFunctionDeclarationCore(ref enumerator, false);
+        return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, EndPosition);
     }
     }
 
 
-    (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, SourcePosition EndPosition) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
     {
     {
         ReadOnlyMemory<char> name;
         ReadOnlyMemory<char> name;
 
 
@@ -478,7 +496,7 @@ public ref struct Parser
         }
         }
 
 
         // skip '('
         // skip '('
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _);
+        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var leftParenToken);
         enumerator.SkipEoL();
         enumerator.SkipEoL();
 
 
         // parse parameters
         // parse parameters
@@ -494,16 +512,16 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
 
 
         // parse statements
         // 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);
     }
     }
 
 
     TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     {
     {
         using var names = new PooledList<IdentifierNode>(32);
         using var names = new PooledList<IdentifierNode>(32);
         var hasSelfParameter = false;
         var hasSelfParameter = false;
-
+        SyntaxToken leftParenToken;
         while (true)
         while (true)
         {
         {
             CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
             CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
@@ -517,6 +535,7 @@ public ref struct Parser
                 {
                 {
                     LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
                     LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
                 }
                 }
+
                 hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon;
                 hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon;
 
 
                 MoveNextWithValidation(ref enumerator);
                 MoveNextWithValidation(ref enumerator);
@@ -524,6 +543,7 @@ public ref struct Parser
             }
             }
             else if (enumerator.Current.Type is SyntaxTokenType.LParen)
             else if (enumerator.Current.Type is SyntaxTokenType.LParen)
             {
             {
+                leftParenToken = enumerator.Current;
                 // skip '('
                 // skip '('
                 MoveNextWithValidation(ref enumerator);
                 MoveNextWithValidation(ref enumerator);
                 enumerator.SkipEoL();
                 enumerator.SkipEoL();
@@ -544,9 +564,9 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
 
 
         // parse statements
         // 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);
     }
     }
 
 
     bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result)
     bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result)
@@ -579,7 +599,7 @@ public ref struct Parser
         // nested table access & function call
         // nested table access & function call
     RECURSIVE:
     RECURSIVE:
         enumerator.SkipEoL();
         enumerator.SkipEoL();
-        
+
         var nextType = enumerator.GetNext().Type;
         var nextType = enumerator.GetNext().Type;
         if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon)
         if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon)
         {
         {
@@ -851,9 +871,9 @@ public ref struct Parser
         // skip 'function' keyword
         // skip 'function' keyword
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
         enumerator.SkipEoL();
         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)
     ExpressionNode[] ParseCallFunctionArguments(ref SyntaxTokenEnumerator enumerator)
@@ -946,20 +966,22 @@ public ref struct Parser
         return buffer.AsSpan().ToArray();
         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);
         using var statements = new PooledList<StatementNode>(64);
 
 
         // parse statements
         // parse statements
         while (enumerator.MoveNext())
         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;
             if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
 
 
             var node = ParseStatement(ref enumerator);
             var node = ParseStatement(ref enumerator);
             statements.Add(node);
             statements.Add(node);
         }
         }
 
 
+        endToken = enumerator.Current;
+
         return statements.AsSpan().ToArray();
         return statements.AsSpan().ToArray();
     }
     }
 
 

+ 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})");
         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)
     public static void BadArgumentNumberIsNotInteger(Traceback traceback, int argumentId, string functionName)
     {
     {
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");

+ 38 - 0
src/Lua/Internal/BitFlags.cs

@@ -0,0 +1,38 @@
+namespace Lua.Internal;
+
+internal struct BitFlags2
+{
+    public byte Value;
+
+    public bool Flag0
+    {
+        get => (Value & 1) == 1;
+        set
+        {
+            if (value)
+            {
+                Value |= 1;
+            }
+            else
+            {
+                Value = (byte)(Value & ~1);
+            }
+        }
+    }
+    
+    public bool Flag1
+    {
+        get => (Value & 2) == 2;
+        set
+        {
+            if (value)
+            {
+                Value |= 2;
+            }
+            else
+            {
+                Value = (byte)(Value & ~2);
+            }
+        }
+    }
+}

+ 14 - 1
src/Lua/Internal/FastStackCore.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 
 
@@ -100,6 +101,17 @@ public struct FastStackCore<T>
         return result;
         return result;
     }
     }
 
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal ref T PeekRef()
+    {
+        if (tail == 0)
+        {
+            ThrowForEmptyStack();
+        }
+
+        return ref array[tail - 1]!;
+    }
+
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public void EnsureCapacity(int capacity)
     public void EnsureCapacity(int capacity)
@@ -130,7 +142,8 @@ public struct FastStackCore<T>
         array.AsSpan(0, tail).Clear();
         array.AsSpan(0, tail).Clear();
         tail = 0;
         tail = 0;
     }
     }
-    
+
+    [DoesNotReturn]
     void ThrowForEmptyStack()
     void ThrowForEmptyStack()
     {
     {
         throw new InvalidOperationException("Empty stack");
         throw new InvalidOperationException("Empty stack");

+ 14 - 19
src/Lua/Internal/HexConverter.cs

@@ -86,25 +86,20 @@ public static class HexConverter
 
 
     static int ToInt(char c)
     static int ToInt(char c)
     {
     {
-        return c switch
+        unchecked
         {
         {
-            '0' => 0,
-            '1' => 1,
-            '2' => 2,
-            '3' => 3,
-            '4' => 4,
-            '5' => 5,
-            '6' => 6,
-            '7' => 7,
-            '8' => 8,
-            '9' => 9,
-            'A' or 'a' => 10,
-            'B' or 'b' => 11,
-            'C' or 'd' => 12,
-            'D' or 'e' => 13,
-            'E' or 'e' => 14,
-            'F' or 'f' => 15,
-            _ => 0
-        };
+            switch (c)
+            {
+                case < '0':
+                    return 0;
+                case <= '9':
+                    return (c - '0');
+                case >= 'A' and <= 'F':
+                    return (c - 'A' + 10);
+                case >= 'a' and <= 'f':
+                    return (c - 'a' + 10);
+            }
+        }
+        return 0;
     }
     }
 }
 }

+ 696 - 0
src/Lua/Internal/LuaDebug.cs

@@ -0,0 +1,696 @@
+using Lua.Runtime;
+using static Lua.Internal.OpMode;
+using static Lua.Internal.OpArgMask;
+
+namespace Lua.Internal;
+
+internal readonly struct LuaDebug : IDisposable
+{
+    readonly LuaDebugBuffer buffer;
+    readonly uint version;
+
+    LuaDebug(LuaDebugBuffer buffer, uint version)
+    {
+        this.buffer = buffer;
+        this.version = version;
+    }
+
+    public string? Name
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.Name;
+        }
+    }
+
+    public string? NameWhat
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.NameWhat;
+        }
+    }
+
+    public string? What
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.What;
+        }
+    }
+
+    public string? Source
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.Source;
+        }
+    }
+
+    public int CurrentLine
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.CurrentLine;
+        }
+    }
+
+    public int LineDefined
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.LineDefined;
+        }
+    }
+
+    public int LastLineDefined
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.LastLineDefined;
+        }
+    }
+
+    public int UpValueCount
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.UpValueCount;
+        }
+    }
+
+    public int ParameterCount
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.ParameterCount;
+        }
+    }
+
+    public bool IsVarArg
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.IsVarArg;
+        }
+    }
+
+    public bool IsTailCall
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.IsTailCall;
+        }
+    }
+
+    public ReadOnlySpan<char> ShortSource
+    {
+        get
+        {
+            CheckVersion();
+            return buffer.ShortSource.AsSpan(0, buffer.ShortSourceLength);
+        }
+    }
+
+
+    public static LuaDebug Create(LuaState state, CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan<char> what, out bool isValid)
+    {
+        if (!state.DebugBufferPool.TryPop(out var buffer))
+        {
+            buffer = new(state);
+        }
+
+        isValid = buffer.GetInfo(prevFrame, frame, function, pc, what);
+
+        return new(buffer, buffer.version);
+    }
+
+    public void CheckVersion()
+    {
+        if (buffer.version != version) ThrowObjectDisposedException();
+    }
+
+
+    public void Dispose()
+    {
+        if (buffer.version != version) ThrowObjectDisposedException();
+        buffer.Return(version);
+    }
+
+    void ThrowObjectDisposedException()
+    {
+        throw new ObjectDisposedException("This has been disposed");
+    }
+
+
+    internal class LuaDebugBuffer(LuaState state)
+    {
+        internal uint version;
+        LuaState state = state;
+        public string? Name;
+        public string? NameWhat;
+        public string? What;
+        public string? Source;
+        public int CurrentLine;
+        public int LineDefined;
+        public int LastLineDefined;
+        public int UpValueCount;
+        public int ParameterCount;
+        public bool IsVarArg;
+        public bool IsTailCall;
+        public readonly char[] ShortSource = new char[59];
+        public int ShortSourceLength;
+
+        internal void Return(uint version)
+        {
+            if (this.version != version) throw new ObjectDisposedException("Buffer has been modified");
+
+            Name = null;
+            NameWhat = null;
+            What = null;
+            Source = null;
+            CurrentLine = 0;
+            LineDefined = 0;
+            LastLineDefined = 0;
+            UpValueCount = 0;
+            ParameterCount = 0;
+            IsVarArg = false;
+            IsTailCall = false;
+
+            if (version < uint.MaxValue)
+            {
+                this.version++;
+                state.DebugBufferPool.Push(this);
+            }
+        }
+
+
+        internal bool GetInfo(CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan<char> what)
+        {
+            LuaClosure? closure = function as LuaClosure;
+            int status = 1;
+            foreach (var c in what)
+            {
+                switch (c)
+                {
+                    case 'S':
+                        {
+                            GetFuncInfo(function);
+                            break;
+                        }
+                    case 'l':
+                        {
+                            CurrentLine = (pc >= 0 && closure is not null) ? closure.Proto.SourcePositions[pc].Line : -1;
+                            break;
+                        }
+                    case 'u':
+                        {
+                            UpValueCount = (closure is null) ? 0 : closure.UpValues.Length;
+                            if (closure is null)
+                            {
+                                IsVarArg = true;
+                                ParameterCount = 0;
+                            }
+                            else
+                            {
+                                IsVarArg = closure.Proto.HasVariableArguments;
+                                ParameterCount = closure.Proto.ParameterCount;
+                            }
+
+                            break;
+                        }
+                    case 't':
+                        {
+                            IsTailCall = frame.HasValue && (frame.Value.Flags | CallStackFrameFlags.TailCall) == frame.Value.Flags;
+                            break;
+                        }
+                    case 'n':
+                        {
+                            /* calling function is a known Lua function? */
+                            if (prevFrame is { Function: LuaClosure prevFrameClosure })
+                                NameWhat = GetFuncName(prevFrameClosure.Proto, frame?.CallerInstructionIndex ?? 0, out Name);
+                            else
+                                NameWhat = null;
+                            if (NameWhat is null)
+                            {
+                                NameWhat = ""; /* not found */
+                                Name = null;
+                            }
+                            else if (NameWhat != null && Name is "?")
+                            {
+                                Name = function.Name;
+                            }
+
+                            break;
+                        }
+                    case 'L':
+                    case 'f': /* handled by lua_getinfo */
+                        break;
+                    default:
+                        status = 0; /* invalid option */
+                        break;
+                }
+            }
+
+            return status == 1;
+        }
+
+        void GetFuncInfo(LuaFunction f)
+        {
+            if (f is not LuaClosure cl)
+            {
+                Source = "=[C#]";
+                LineDefined = -1;
+                LastLineDefined = -1;
+                What = "C#";
+            }
+            else
+            {
+                var p = cl.Proto;
+                Source = p.GetRoot().Name;
+                LineDefined = p.LineDefined;
+                LastLineDefined = p.LastLineDefined;
+                What = (p.GetRoot() == p) ? "main" : "Lua";
+            }
+
+            ShortSourceLength = WriteShortSource(Source, ShortSource);
+        }
+    }
+
+
+    internal static string? GetLocalName(Chunk chunk, int register, int pc)
+    {
+        var locals = chunk.Locals;
+        foreach (var local in locals)
+        {
+            if (local.Index == register && pc >= local.StartPc && pc < local.EndPc)
+            {
+                return local.Name.ToString();
+            }
+
+            if (local.Index > register)
+            {
+                break;
+            }
+        }
+
+        return null;
+    }
+
+    static int FilterPc(int pc, int jmpTarget)
+    {
+        if (pc < jmpTarget) /* is code conditional (inside a jump)? */
+            return -1; /* cannot know who sets that register */
+        else return pc; /* current position sets that register */
+    }
+
+    internal static int FindSetRegister(Chunk chunk, int lastPc, int reg)
+    {
+        int pc;
+        int setReg = -1; /* keep last instruction that changed 'reg' */
+        int jmpTarget = 0; /* any code before this address is conditional */
+        var instructions = chunk.Instructions;
+        for (pc = 0; pc < lastPc; pc++)
+        {
+            Instruction i = instructions[pc];
+            OpCode op = i.OpCode;
+            int a = i.A;
+            switch (op)
+            {
+                case OpCode.LoadNil:
+                    {
+                        int b = i.B;
+                        if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */
+                            setReg = FilterPc(pc, jmpTarget);
+                        break;
+                    }
+                case OpCode.TForCall:
+                    {
+                        if (reg >= a + 2) /* affect all regs above its base */
+                            setReg = FilterPc(pc, jmpTarget);
+                        break;
+                    }
+                case OpCode.Call:
+                case OpCode.TailCall:
+                    {
+                        if (reg >= a) /* affect all registers above base */
+                            setReg = FilterPc(pc, jmpTarget);
+                        break;
+                    }
+                case OpCode.Jmp:
+                    {
+                        int b = i.SBx;
+                        int dest = pc + 1 + b;
+                        /* jump is forward and do not skip `lastpc'? */
+                        if (pc < dest && dest <= lastPc)
+                        {
+                            if (dest > jmpTarget)
+                                jmpTarget = dest; /* update 'jmptarget' */
+                        }
+
+                        break;
+                    }
+                case OpCode.Test:
+                    {
+                        if (reg == a) /* jumped code can change 'a' */
+                            setReg = FilterPc(pc, jmpTarget);
+                        break;
+                    }
+                default:
+                    if (TestAMode(op) && reg == a) /* any instruction that set A */
+                        setReg = FilterPc(pc, jmpTarget);
+                    break;
+            }
+        }
+
+        return setReg;
+    }
+
+    static void GetConstantName(Chunk p, int pc, int c, out string name)
+    {
+        if (c >= 256)
+        {
+            /* is 'c' a constant? */
+            ref var kvalue = ref p.Constants[c - 256];
+            if (kvalue.TryReadString(out name))
+            {
+                /* literal constant? */
+                /* it is its own name */
+                return;
+            }
+            /* else no reasonable name found */
+        }
+        else
+        {
+            /* 'c' is a register */
+            var what = GetName(p, pc, c, out name!); /* search for 'c' */
+            if (what != null && what[0] == 'c')
+            {
+                /* found a constant name? */
+                return; /* 'name' already filled */
+            }
+            /* else no reasonable name found */
+        }
+
+        name = "?"; /* no reasonable name found */
+    }
+
+
+    internal static string? GetName(Chunk chunk, int lastPc, int reg, out string? name)
+    {
+        name = GetLocalName(chunk, reg, lastPc);
+        if (name != null)
+        {
+            return "local";
+        }
+
+        var pc = FindSetRegister(chunk, lastPc, reg);
+        if (pc != -1)
+        {
+            /* could find instruction? */
+            Instruction i = chunk.Instructions[pc];
+            OpCode op = i.OpCode;
+            switch (op)
+            {
+                case OpCode.Move:
+                    {
+                        int b = i.B; /* move from 'b' to 'a' */
+                        if (b < i.A)
+                            return GetName(chunk, pc, b, out name); /* get name for 'b' */
+                        break;
+                    }
+                case OpCode.GetTabUp:
+                case OpCode.GetTable:
+                    {
+                        int k = i.C; /* key index */
+                        int t = i.B; /* table index */
+
+                        var vn = (op == OpCode.GetTable) /* name of indexed variable */
+                            ? GetLocalName(chunk, t + 1, pc)
+                            : chunk.UpValues[t].Name.ToString();
+                        GetConstantName(chunk, pc, k, out name);
+                        return vn is "_ENV" ? "global" : "field";
+                    }
+                case OpCode.GetUpVal:
+                    {
+                        name = chunk.UpValues[i.B].Name.ToString();
+                        return "upvalue";
+                    }
+                case OpCode.LoadK:
+                case OpCode.LoadKX:
+                    {
+                        uint b = (op == OpCode.LoadKX)
+                            ? i.Bx
+                            : (chunk.Instructions[pc + 1].Ax);
+                        if (chunk.Constants[b].TryReadString(out name))
+                        {
+                            return "constant";
+                        }
+
+                        break;
+                    }
+                case OpCode.Self:
+                    {
+                        int k = i.C; /* key index */
+                        GetConstantName(chunk, pc, k, out name);
+                        return "method";
+                    }
+                default: break; /* go through to return NULL */
+            }
+        }
+
+        return null; /* could not find reasonable name */
+    }
+
+    internal static string? GetFuncName(Chunk chunk, int pc, out string? name)
+    {
+        Instruction i = chunk.Instructions[pc]; /* calling instruction */
+        switch (i.OpCode)
+        {
+            case OpCode.Call:
+            case OpCode.TailCall: /* get function name */
+                return GetName(chunk, pc, i.A, out name);
+            case OpCode.TForCall:
+                {
+                    /* for iterator */
+                    name = "for iterator";
+                    return "for iterator";
+                }
+            case OpCode.Self:
+            case OpCode.GetTabUp:
+            case OpCode.GetTable:
+                name = "index";
+                break;
+            case OpCode.SetTabUp:
+            case OpCode.SetTable:
+                name = "newindex";
+                break;
+            case OpCode.Add:
+                name = "add";
+                break;
+            case OpCode.Sub:
+                name = "sub";
+                break;
+            case OpCode.Mul:
+                name = "mul";
+                break;
+            case OpCode.Div:
+                name = "div";
+                break;
+            case OpCode.Mod:
+                name = "mod";
+                break;
+            case OpCode.Pow:
+                name = "pow";
+                break;
+            case OpCode.Unm:
+                name = "unm";
+                break;
+            case OpCode.Len:
+                name = "len";
+                break;
+            case OpCode.Concat:
+                name = "concat";
+                break;
+            case OpCode.Eq:
+                name = "eq";
+                break;
+            case OpCode.Lt:
+                name = "lt";
+                break;
+            case OpCode.Le:
+                name = "le";
+                break;
+            default:
+                name = null;
+                return null;
+        }
+
+        return "metamethod";
+    }
+
+    internal static int WriteShortSource(ReadOnlySpan<char> source, Span<char> dest)
+    {
+        const string PRE = "[string \"";
+        const int PRE_LEN = 9;
+        const string POS = "\"]";
+        const int POS_LEN = 2;
+        const string RETS = "...";
+        const int RETS_LEN = 3;
+        const string PREPOS = "[string \"\"]";
+
+        const int BUFFER_LEN = 59;
+        if (dest.Length != BUFFER_LEN) throw new ArgumentException("dest must be 60 chars long");
+
+        if (source.Length == 0)
+        {
+            PREPOS.AsSpan().CopyTo(dest);
+            return PREPOS.Length;
+        }
+
+        if (source[0] == '=')
+        {
+            source = source[1..]; /* skip the '=' */
+            /* 'literal' source */
+            if (source.Length < BUFFER_LEN) /* small enough? */
+            {
+                source.CopyTo(dest);
+                return source.Length;
+            }
+            else
+            {
+                /* truncate it */
+                source[..BUFFER_LEN].CopyTo(dest);
+                return BUFFER_LEN;
+            }
+        }
+        else if (source[0] == '@')
+        {
+            /* file name */
+            source = source[1..]; /* skip the '@' */
+            if (source.Length <= BUFFER_LEN) /* small enough? */
+            {
+                source.CopyTo(dest);
+                return source.Length;
+            }
+            else
+            {
+                /* add '...' before rest of name */
+                RETS.AsSpan().CopyTo(dest);
+                source[^(BUFFER_LEN - RETS_LEN)..].CopyTo(dest[RETS_LEN..]);
+
+                return BUFFER_LEN;
+            }
+        }
+        else
+        {
+            /* string; format as [string "source"] */
+
+
+            PRE.AsSpan().CopyTo(dest);
+            int newLine = source.IndexOf('\n');
+            if (newLine == -1 && source.Length < BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN))
+            {
+                source.CopyTo(dest[PRE_LEN..]);
+                POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]);
+                return PRE_LEN + source.Length + POS_LEN;
+            }
+
+            if (newLine != -1)
+            {
+                source = source[..newLine]; /* stop at first newline */
+            }
+
+            if (BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN) < source.Length)
+            {
+                source = source[..(BUFFER_LEN - PRE_LEN - RETS_LEN - POS_LEN)];
+            }
+
+            /* add '...' before rest of name */
+            source.CopyTo(dest[PRE_LEN..]);
+            RETS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]);
+            POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length + RETS_LEN)..]);
+            return PRE_LEN + source.Length + RETS_LEN + POS_LEN;
+        }
+    }
+
+    static int GetOpMode(byte t, byte a, OpArgMask b, OpArgMask c, OpMode m) => (((t) << 7) | ((a) << 6) | (((byte)b) << 4) | (((byte)c) << 2) | ((byte)m));
+
+
+    static readonly int[] OpModes =
+    [
+        GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_MOVE */
+        GetOpMode(0, 1, OpArgK, OpArgN, iABx), /* OP_LOADK */
+        GetOpMode(0, 1, OpArgN, OpArgN, iABx), /* OP_LOADKX */
+        GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_LOADBOOL */
+        GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_LOADNIL */
+        GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_GETUPVAL */
+        GetOpMode(0, 1, OpArgU, OpArgK, iABC), /* OP_GETTABUP */
+        GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_GETTABLE */
+        GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABUP */
+        GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_SETUPVAL */
+        GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABLE */
+        GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_NEWTABLE */
+        GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_SELF */
+        GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_ADD */
+        GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_SUB */
+        GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MUL */
+        GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_DIV */
+        GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MOD */
+        GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_POW */
+        GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_UNM */
+        GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_NOT */
+        GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_LEN */
+        GetOpMode(0, 1, OpArgR, OpArgR, iABC), /* OP_CONCAT */
+        GetOpMode(0, 0, OpArgR, OpArgN, iAsBx), /* OP_JMP */
+        GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_EQ */
+        GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LT */
+        GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LE */
+        GetOpMode(1, 0, OpArgN, OpArgU, iABC), /* OP_TEST */
+        GetOpMode(1, 1, OpArgR, OpArgU, iABC), /* OP_TESTSET */
+        GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_CALL */
+        GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_TAILCALL */
+        GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_RETURN */
+        GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORLOOP */
+        GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORPREP */
+        GetOpMode(0, 0, OpArgN, OpArgU, iABC), /* OP_TFORCALL */
+        GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_TFORLOOP */
+        GetOpMode(0, 0, OpArgU, OpArgU, iABC), /* OP_SETLIST */
+        GetOpMode(0, 1, OpArgU, OpArgN, iABx), /* OP_CLOSURE */
+        GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_VARARG */
+        GetOpMode(0, 0, OpArgU, OpArgU, iAx), /* OP_EXTRAARG */
+    ];
+
+    internal static OpMode GetOpMode(OpCode m) => (OpMode)(OpModes[(int)m] & 3);
+    internal static OpArgMask GetBMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 4) & 3);
+    internal static OpArgMask GetCMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 2) & 3);
+    internal static bool TestAMode(OpCode m) => (OpModes[(int)m] & (1 << 6)) != 0;
+    internal static bool TestTMode(OpCode m) => (OpModes[(int)m] & (1 << 7)) != 0;
+}
+
+internal enum OpMode : byte
+{
+    iABC,
+    iABx,
+    iAsBx,
+    iAx
+}
+
+internal enum OpArgMask : byte
+{
+    OpArgN, /* argument is not used */
+    OpArgU, /* argument is used */
+    OpArgR, /* argument is a register or a jump offset */
+    OpArgK /* argument is a constant or register/constant */
+}

+ 22 - 1
src/Lua/Internal/MathEx.cs

@@ -1,5 +1,7 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
-
+#if NET6_0_OR_GREATER
+using System.Numerics;
+#endif
 namespace Lua;
 namespace Lua;
 
 
 internal static class MathEx
 internal static class MathEx
@@ -86,4 +88,23 @@ internal static class MathEx
     {
     {
         return ((int)Math.Truncate(d), d % 1.0);
         return ((int)Math.Truncate(d), d % 1.0);
     }
     }
+    
+    /// <summary>Returns the smallest power of two greater than or equal to the input.</summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static int NextPowerOfTwo(int x)
+    {
+#if NET6_0_OR_GREATER
+        if (x <= 0) return 0;
+        return (int)BitOperations.RoundUpToPowerOf2((uint)x);
+#else
+        if (x <= 0) return 0;
+        x -= 1;
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        x |= x >> 16;
+        return x + 1;
+#endif
+    }
 }
 }

+ 4 - 3
src/Lua/Internal/StringHelper.cs

@@ -37,7 +37,7 @@ internal static class StringHelper
                         builder.Append('\n');
                         builder.Append('\n');
                         break;
                         break;
                     case '\r':
                     case '\r':
-                        builder.Append('\r');
+                        builder.Append('\n');
                         // check CRLF
                         // check CRLF
                         if (i + 1 < literal.Length && literal[i + 1] is '\n')
                         if (i + 1 < literal.Length && literal[i + 1] is '\n')
                         {
                         {
@@ -306,6 +306,7 @@ internal static class StringHelper
                             builder.Append(c);
                             builder.Append(c);
                             break;
                             break;
                     }
                     }
+
                     isEscapeSequence = false;
                     isEscapeSequence = false;
                 }
                 }
             }
             }
@@ -358,7 +359,7 @@ internal static class StringHelper
     public static bool IsDigit(char c)
     public static bool IsDigit(char c)
     {
     {
         return IsNumber(c) ||
         return IsNumber(c) ||
-            ('a' <= c && c <= 'f') ||
-            ('A' <= c && c <= 'F');
+               ('a' <= c && c <= 'f') ||
+               ('A' <= c && c <= 'F');
     }
     }
 }
 }

+ 6 - 2
src/Lua/LuaCoroutine.cs

@@ -24,6 +24,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
 
 
     ManualResetValueTaskSourceCore<ResumeContext> resume;
     ManualResetValueTaskSourceCore<ResumeContext> resume;
     ManualResetValueTaskSourceCore<YieldContext> yield;
     ManualResetValueTaskSourceCore<YieldContext> yield;
+    Traceback? traceback;
 
 
     public LuaCoroutine(LuaFunction function, bool isProtectedMode)
     public LuaCoroutine(LuaFunction function, bool isProtectedMode)
     {
     {
@@ -43,6 +44,9 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
 
 
     public bool IsProtectedMode { get; }
     public bool IsProtectedMode { get; }
     public LuaFunction Function { get; }
     public LuaFunction Function { get; }
+    
+    
+    internal Traceback? LuaTraceback => traceback;
 
 
     public override async ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
     public override async ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
     {
     {
@@ -179,7 +183,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                 if (IsProtectedMode)
                 if (IsProtectedMode)
                 {
                 {
                     ArrayPool<LuaValue>.Shared.Return(this.buffer);
                     ArrayPool<LuaValue>.Shared.Return(this.buffer);
-
+                    traceback = (ex as LuaRuntimeException)?.LuaTraceback;
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
                     buffer.Span[0] = false;
                     buffer.Span[0] = false;
                     buffer.Span[1] = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message;
                     buffer.Span[1] = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message;
@@ -210,7 +214,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
             throw new LuaRuntimeException(context.State.GetTraceback(), "cannot call yield on a coroutine that is not currently running");
             throw new LuaRuntimeException(context.State.GetTraceback(), "cannot call yield on a coroutine that is not currently running");
         }
         }
 
 
-        if (context.Thread.GetCallStackFrames()[^2].Function is not Closure)
+        if (context.Thread.GetCallStackFrames()[^2].Function is not LuaClosure)
         {
         {
             throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield across a C#-call boundary");
             throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield across a C#-call boundary");
         }
         }

+ 8 - 1
src/Lua/LuaFunction.cs

@@ -17,13 +17,20 @@ public class LuaFunction(string name, Func<LuaFunctionExecutionContext, Memory<L
         var frame = new CallStackFrame
         var frame = new CallStackFrame
         {
         {
             Base = context.FrameBase,
             Base = context.FrameBase,
-            VariableArgumentCount = this is Closure closure ? Math.Max(context.ArgumentCount - closure.Proto.ParameterCount, 0) : 0,
+            VariableArgumentCount = this is LuaClosure closure ? Math.Max(context.ArgumentCount - closure.Proto.ParameterCount, 0) : 0,
             Function = this,
             Function = this,
         };
         };
 
 
         context.Thread.PushCallStackFrame(frame);
         context.Thread.PushCallStackFrame(frame);
+
+
         try
         try
         {
         {
+            if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            {
+                return await LuaVirtualMachine.ExecuteCallHook(context, buffer, cancellationToken);
+            }
+
             return await Func(context, buffer, cancellationToken);
             return await Func(context, buffer, cancellationToken);
         }
         }
         finally
         finally

+ 66 - 4
src/Lua/LuaFunctionExecutionContext.cs

@@ -1,6 +1,7 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using Lua.CodeAnalysis;
 using Lua.CodeAnalysis;
+using Lua.Runtime;
 
 
 namespace Lua;
 namespace Lua;
 
 
@@ -19,10 +20,7 @@ public readonly record struct LuaFunctionExecutionContext
 
 
     public ReadOnlySpan<LuaValue> Arguments
     public ReadOnlySpan<LuaValue> Arguments
     {
     {
-        get
-        {
-            return Thread.GetStackValues().Slice(FrameBase, ArgumentCount);
-        }
+        get { return Thread.GetStackValues().Slice(FrameBase, ArgumentCount); }
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -38,6 +36,17 @@ public readonly record struct LuaFunctionExecutionContext
         return Arguments[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)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public T GetArgument<T>(int index)
     public T GetArgument<T>(int index)
     {
     {
@@ -55,6 +64,49 @@ public readonly record struct LuaFunctionExecutionContext
             {
             {
                 LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
                 LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             }
+            else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData)
+            {
+                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
+            }
+            else
+            {
+                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
+            }
+        }
+
+        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.Type is LuaValueType.Nil)
+        {
+            return defaultValue;
+        }
+
+        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 if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData)
+            {
+                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
+            }
             else
             else
             {
             {
                 LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
                 LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
@@ -64,6 +116,16 @@ public readonly record struct LuaFunctionExecutionContext
         return argValue;
         return argValue;
     }
     }
 
 
+    public CSharpClosure? GetCsClosure()
+    {
+        return Thread.GetCurrentFrame().Function as CSharpClosure;
+    }
+
+    internal void ThrowBadArgument(int index, string message)
+    {
+        LuaRuntimeException.BadArgument(State.GetTraceback(), index, Thread.GetCurrentFrame().Function.Name, message);
+    }
+
     void ThrowIfArgumentNotExists(int index)
     void ThrowIfArgumentNotExists(int index)
     {
     {
         if (ArgumentCount <= index)
         if (ArgumentCount <= index)

+ 37 - 6
src/Lua/LuaState.cs

@@ -16,14 +16,19 @@ public sealed class LuaState
     FastStackCore<LuaThread> threadStack;
     FastStackCore<LuaThread> threadStack;
     readonly LuaTable packages = new();
     readonly LuaTable packages = new();
     readonly LuaTable environment;
     readonly LuaTable environment;
+    readonly LuaTable registry = new();
     readonly UpValue envUpValue;
     readonly UpValue envUpValue;
     bool isRunning;
     bool isRunning;
 
 
+    FastStackCore<LuaDebug.LuaDebugBuffer> debugBufferPool;
+
     internal UpValue EnvUpValue => envUpValue;
     internal UpValue EnvUpValue => envUpValue;
     internal ref FastStackCore<LuaThread> ThreadStack => ref threadStack;
     internal ref FastStackCore<LuaThread> ThreadStack => ref threadStack;
     internal ref FastListCore<UpValue> OpenUpValues => ref openUpValues;
     internal ref FastListCore<UpValue> OpenUpValues => ref openUpValues;
+    internal ref FastStackCore<LuaDebug.LuaDebugBuffer> DebugBufferPool => ref debugBufferPool;
 
 
     public LuaTable Environment => environment;
     public LuaTable Environment => environment;
+    public LuaTable Registry => registry;
     public LuaTable LoadedModules => packages;
     public LuaTable LoadedModules => packages;
     public LuaMainThread MainThread => mainThread;
     public LuaMainThread MainThread => mainThread;
     public LuaThread CurrentThread
     public LuaThread CurrentThread
@@ -63,7 +68,7 @@ public sealed class LuaState
         Volatile.Write(ref isRunning, true);
         Volatile.Write(ref isRunning, true);
         try
         try
         {
         {
-            var closure = new Closure(this, chunk);
+            var closure = new LuaClosure(this, chunk);
             return await closure.InvokeAsync(new()
             return await closure.InvokeAsync(new()
             {
             {
                 State = this,
                 State = this,
@@ -90,9 +95,9 @@ public sealed class LuaState
     {
     {
         if (threadStack.Count == 0)
         if (threadStack.Count == 0)
         {
         {
-            return new()
+            return new(this)
             {
             {
-                RootFunc = (Closure)MainThread.GetCallStackFrames()[0].Function,
+                RootFunc = (LuaClosure)MainThread.GetCallStackFrames()[0].Function,
                 StackFrames = MainThread.GetCallStackFrames()[1..]
                 StackFrames = MainThread.GetCallStackFrames()[1..]
                     .ToArray()
                     .ToArray()
             };
             };
@@ -103,6 +108,7 @@ public sealed class LuaState
         {
         {
             list.Add(frame);
             list.Add(frame);
         }
         }
+
         foreach (var thread in threadStack.AsSpan())
         foreach (var thread in threadStack.AsSpan())
         {
         {
             if (thread.CallStack.Count == 0) continue;
             if (thread.CallStack.Count == 0) continue;
@@ -111,9 +117,34 @@ public sealed class LuaState
                 list.Add(frame);
                 list.Add(frame);
             }
             }
         }
         }
-        return new()
+
+        return new(this)
+        {
+            RootFunc = (LuaClosure)MainThread.GetCallStackFrames()[0].Function,
+            StackFrames = list.AsSpan().ToArray()
+        };
+    }
+
+    internal Traceback GetTraceback(LuaThread thread)
+    {
+        using var list = new PooledList<CallStackFrame>(8);
+        foreach (var frame in thread.GetCallStackFrames()[1..])
+        {
+            list.Add(frame);
+        }
+        LuaClosure rootFunc;
+        if (thread.GetCallStackFrames()[0].Function is LuaClosure closure)
+        {
+            rootFunc = closure;
+        }
+        else
+        {
+            rootFunc = (LuaClosure)MainThread.GetCallStackFrames()[0].Function;
+        }
+
+        return new(this)
         {
         {
-            RootFunc = (Closure)MainThread.GetCallStackFrames()[0].Function,
+            RootFunc = rootFunc,
             StackFrames = list.AsSpan().ToArray()
             StackFrames = list.AsSpan().ToArray()
         };
         };
     }
     }
@@ -207,4 +238,4 @@ public sealed class LuaState
             throw new InvalidOperationException("the lua state is currently running");
             throw new InvalidOperationException("the lua state is currently running");
         }
         }
     }
     }
-}
+}

+ 1 - 1
src/Lua/LuaStateExtensions.cs

@@ -30,7 +30,7 @@ public static class LuaStateExtensions
     public static async ValueTask<int> DoFileAsync(this LuaState state, string path, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
     public static async ValueTask<int> DoFileAsync(this LuaState state, string path, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
     {
     {
         var text = await File.ReadAllTextAsync(path, cancellationToken);
         var text = await File.ReadAllTextAsync(path, cancellationToken);
-        var fileName = Path.GetFileName(path);
+        var fileName = "@"+Path.GetFileName(path);
         var syntaxTree = LuaSyntaxTree.Parse(text, fileName);
         var syntaxTree = LuaSyntaxTree.Parse(text, fileName);
         var chunk = LuaCompiler.Default.Compile(syntaxTree, fileName);
         var chunk = LuaCompiler.Default.Compile(syntaxTree, fileName);
         return await state.RunAsync(chunk, buffer, cancellationToken);
         return await state.RunAsync(chunk, buffer, cancellationToken);

+ 21 - 6
src/Lua/LuaTable.cs

@@ -19,6 +19,11 @@ public sealed class LuaTable
     readonly LuaValueDictionary dictionary;
     readonly LuaValueDictionary dictionary;
     LuaTable? metatable;
     LuaTable? metatable;
 
 
+    internal LuaValueDictionary Dictionary => dictionary;
+    private const int MaxArraySize = 1 << 24;
+    private const int MaxDistance = 1 << 12;
+
+
     public LuaValue this[LuaValue key]
     public LuaValue this[LuaValue key]
     {
     {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -51,7 +56,15 @@ public sealed class LuaTable
                 if (MathEx.IsInteger(d))
                 if (MathEx.IsInteger(d))
                 {
                 {
                     var index = (int)d;
                     var index = (int)d;
-                    if (0 < index && index <= Math.Max(array.Length * 2, 8))
+                    
+                    var distance = index - array.Length;
+                    if (distance > MaxDistance)
+                    {
+                        dictionary[key] = value;
+                        return;
+                    }
+                    
+                    if (0 < index && index < MaxArraySize && index <= Math.Max(array.Length * 2, 8))
                     {
                     {
                         if (array.Length < index)
                         if (array.Length < index)
                             EnsureArrayCapacity(index);
                             EnsureArrayCapacity(index);
@@ -168,6 +181,12 @@ public sealed class LuaTable
         }
         }
 
 
         var arrayIndex = index - 1;
         var arrayIndex = index - 1;
+        var distance = index - array.Length;
+        if (distance > MaxDistance)
+        {
+            dictionary[index] = value;
+            return;
+        }
 
 
         if (index > array.Length || array[^1].Type != LuaValueType.Nil)
         if (index > array.Length || array[^1].Type != LuaValueType.Nil)
         {
         {
@@ -249,11 +268,7 @@ public sealed class LuaTable
         var prevLength = array.Length;
         var prevLength = array.Length;
         var newLength = array.Length;
         var newLength = array.Length;
         if (newLength == 0) newLength = 8;
         if (newLength == 0) newLength = 8;
-
-        while (newLength < newCapacity)
-        {
-            newLength *= 2;
-        }
+        newLength = newCapacity <= 8 ? 8 : MathEx.NextPowerOfTwo(newCapacity);
 
 
         Array.Resize(ref array, newLength);
         Array.Resize(ref array, newLength);
 
 

+ 36 - 3
src/Lua/LuaThread.cs

@@ -17,9 +17,42 @@ public abstract class LuaThread
     internal LuaStack Stack => stack;
     internal LuaStack Stack => stack;
     internal ref FastStackCore<CallStackFrame> CallStack => ref callStack;
     internal ref FastStackCore<CallStackFrame> CallStack => ref callStack;
 
 
-    public CallStackFrame GetCurrentFrame()
+    internal bool IsLineHookEnabled
     {
     {
-        return callStack.Peek();
+        get => LineAndCountHookMask.Flag0;
+        set => LineAndCountHookMask.Flag0 = value;
+    }
+
+    internal bool IsCountHookEnabled
+    {
+        get => LineAndCountHookMask.Flag1;
+        set => LineAndCountHookMask.Flag1 = value;
+    }
+
+    internal BitFlags2 LineAndCountHookMask;
+
+    internal bool IsCallHookEnabled
+    {
+        get => CallOrReturnHookMask.Flag0;
+        set => CallOrReturnHookMask.Flag0 = value;
+    }
+
+    internal bool IsReturnHookEnabled
+    {
+        get => CallOrReturnHookMask.Flag1;
+        set => CallOrReturnHookMask.Flag1 = value;
+    }
+
+    internal BitFlags2 CallOrReturnHookMask;
+    internal bool IsInHook;
+    internal int HookCount;
+    internal int BaseHookCount;
+    internal int LastPc;
+    internal LuaFunction? Hook { get; set; }
+
+    public ref readonly CallStackFrame GetCurrentFrame()
+    {
+        return ref callStack.PeekRef();
     }
     }
 
 
     public ReadOnlySpan<LuaValue> GetStackValues()
     public ReadOnlySpan<LuaValue> GetStackValues()
@@ -81,6 +114,6 @@ public abstract class LuaThread
             Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
             Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
         }
         }
     }
     }
-    
+
     static void ThrowForEmptyStack() => throw new InvalidOperationException("Empty stack");
     static void ThrowForEmptyStack() => throw new InvalidOperationException("Empty stack");
 }
 }

+ 16 - 0
src/Lua/LuaUserData.cs

@@ -1,6 +1,22 @@
 namespace Lua;
 namespace Lua;
 
 
+internal sealed class LuaUserData : ILuaUserData
+{
+    public LuaTable? Metatable { get; set; }
+    readonly LuaValue[] userValues = new LuaValue[1];
+    public Span<LuaValue> UserValues => userValues;
+
+    public LuaUserData(LuaValue value, LuaTable? metatable)
+    {
+        userValues[0] = value;
+        Metatable = metatable;
+    }
+}
+
 public interface ILuaUserData
 public interface ILuaUserData
 {
 {
     LuaTable? Metatable { get; set; }
     LuaTable? Metatable { get; set; }
+
+    //We use span for compatibility with lua5.4.
+    Span<LuaValue> UserValues => default;
 }
 }

+ 25 - 0
src/Lua/LuaValue.cs

@@ -14,6 +14,7 @@ public enum LuaValueType : byte
     Number,
     Number,
     Function,
     Function,
     Thread,
     Thread,
+    LightUserData,
     UserData,
     UserData,
     Table,
     Table,
 }
 }
@@ -136,6 +137,16 @@ public readonly struct LuaValue : IEquatable<LuaValue>
                 }
                 }
                 else
                 else
                 {
                 {
+                    break;
+                }
+            case LuaValueType.LightUserData:
+                {
+                    if (referenceValue is T tValue)
+                    {
+                        result = tValue;
+                        return true;
+                    }
+
                     break;
                     break;
                 }
                 }
             case LuaValueType.UserData:
             case LuaValueType.UserData:
@@ -360,6 +371,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
             case LuaValueType.Thread:
             case LuaValueType.Thread:
             case LuaValueType.Function:
             case LuaValueType.Function:
             case LuaValueType.Table:
             case LuaValueType.Table:
+            case LuaValueType.LightUserData:
             case LuaValueType.UserData:
             case LuaValueType.UserData:
                 {
                 {
                     var v = referenceValue!;
                     var v = referenceValue!;
@@ -378,6 +390,13 @@ public readonly struct LuaValue : IEquatable<LuaValue>
         return true;
         return true;
     }
     }
 
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public LuaValue(object obj)
+    {
+        Type = LuaValueType.LightUserData;
+        referenceValue = obj;
+    }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(bool value)
     public LuaValue(bool value)
     {
     {
@@ -517,6 +536,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
             LuaValueType.Function => $"function: {referenceValue!.GetHashCode()}",
             LuaValueType.Function => $"function: {referenceValue!.GetHashCode()}",
             LuaValueType.Thread => $"thread: {referenceValue!.GetHashCode()}",
             LuaValueType.Thread => $"thread: {referenceValue!.GetHashCode()}",
             LuaValueType.Table => $"table: {referenceValue!.GetHashCode()}",
             LuaValueType.Table => $"table: {referenceValue!.GetHashCode()}",
+            LuaValueType.LightUserData => $"userdata: {referenceValue!.GetHashCode()}",
             LuaValueType.UserData => $"userdata: {referenceValue!.GetHashCode()}",
             LuaValueType.UserData => $"userdata: {referenceValue!.GetHashCode()}",
             _ => "",
             _ => "",
         };
         };
@@ -554,6 +574,11 @@ public readonly struct LuaValue : IEquatable<LuaValue>
             result = LuaValueType.Thread;
             result = LuaValueType.Thread;
             return true;
             return true;
         }
         }
+        else if (type == typeof(ILuaUserData) || type.IsAssignableFrom(typeof(ILuaUserData)))
+        {
+            result = LuaValueType.UserData;
+            return true;
+        }
 
 
         result = default;
         result = default;
         return false;
         return false;

+ 6 - 0
src/Lua/Runtime/CSharpClosure.cs

@@ -0,0 +1,6 @@
+namespace Lua.Runtime;
+
+public sealed class CSharpClosure(string name,LuaValue[] upValues,Func<LuaFunctionExecutionContext, Memory<LuaValue>, CancellationToken, ValueTask<int>> func) : LuaFunction(name, func)
+{
+   public readonly LuaValue[] UpValues = upValues;
+}

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

@@ -10,10 +10,14 @@ public record struct CallStackFrame
     public required int VariableArgumentCount;
     public required int VariableArgumentCount;
     public int CallerInstructionIndex;
     public int CallerInstructionIndex;
     internal CallStackFrameFlags Flags;
     internal CallStackFrameFlags Flags;
+    internal bool IsTailCall => (Flags & CallStackFrameFlags.TailCall) ==CallStackFrameFlags.TailCall;
 }
 }
 
 
 [Flags]
 [Flags]
 public enum CallStackFrameFlags
 public enum CallStackFrameFlags
 {
 {
-    ReversedLe = 1,
+    //None = 0,
+    ReversedLe  = 1,
+    TailCall = 2,
+    InHook = 4,
 }
 }

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

@@ -11,10 +11,13 @@ public sealed class Chunk
     public required SourcePosition[] SourcePositions { get; init; }
     public required SourcePosition[] SourcePositions { get; init; }
     public required LuaValue[] Constants { get; init; }
     public required LuaValue[] Constants { get; init; }
     public required UpValueInfo[] UpValues { get; init; }
     public required UpValueInfo[] UpValues { get; init; }
+    public required LocalValueInfo[] Locals { get; init; }
     public required Chunk[] Functions { get; init; }
     public required Chunk[] Functions { get; init; }
     public required int ParameterCount { get; init; }
     public required int ParameterCount { get; init; }
-    
+    public required bool HasVariableArguments { get; init; }
     public required byte MaxStackPosition { get; init; }
     public required byte MaxStackPosition { get; init; }
+    public required int LineDefined { get; init; }
+    public required int LastLineDefined { get; init; }
 
 
     Chunk? rootCache;
     Chunk? rootCache;
 
 

+ 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; }
+}

+ 5 - 4
src/Lua/Runtime/Closure.cs → src/Lua/Runtime/LuaClosure.cs

@@ -3,12 +3,12 @@ using Lua.Internal;
 
 
 namespace Lua.Runtime;
 namespace Lua.Runtime;
 
 
-public sealed class Closure : LuaFunction
+public sealed class LuaClosure : LuaFunction
 {
 {
     Chunk proto;
     Chunk proto;
     FastListCore<UpValue> upValues;
     FastListCore<UpValue> upValues;
 
 
-    public Closure(LuaState state, Chunk proto, LuaTable? environment = null)
+    public LuaClosure(LuaState state, Chunk proto, LuaTable? environment = null)
         : base(proto.Name, (context, buffer, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, buffer, ct))
         : base(proto.Name, (context, buffer, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, buffer, ct))
     {
     {
         this.proto = proto;
         this.proto = proto;
@@ -24,6 +24,7 @@ public sealed class Closure : LuaFunction
 
 
     public Chunk Proto => proto;
     public Chunk Proto => proto;
     public ReadOnlySpan<UpValue> UpValues => upValues.AsSpan();
     public ReadOnlySpan<UpValue> UpValues => upValues.AsSpan();
+    internal Span<UpValue> GetUpValuesSpan() => upValues.AsSpan();
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal LuaValue GetUpValue(int index)
     internal LuaValue GetUpValue(int index)
@@ -47,7 +48,7 @@ public sealed class Closure : LuaFunction
     {
     {
         if (description.IsInRegister)
         if (description.IsInRegister)
         {
         {
-            return state.GetOrAddUpValue(thread, thread.GetCallStackFrames()[^1].Base + description.Index);
+            return state.GetOrAddUpValue(thread, thread.GetCurrentFrame().Base + description.Index);
         }
         }
 
 
         if (description.Index == -1) // -1 is global environment
         if (description.Index == -1) // -1 is global environment
@@ -55,7 +56,7 @@ public sealed class Closure : LuaFunction
             return envUpValue;
             return envUpValue;
         }
         }
 
 
-        if (thread.GetCallStackFrames()[^1].Function is Closure parentClosure)
+        if (thread.GetCurrentFrame().Function is LuaClosure parentClosure)
         {
         {
             return parentClosure.UpValues[description.Index];
             return parentClosure.UpValues[description.Index];
         }
         }

+ 2 - 2
src/Lua/Runtime/LuaValueRuntimeExtensions.cs

@@ -15,8 +15,8 @@ internal static class LuaRuntimeExtensions
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static int GetVariableArgumentCount(this LuaFunction function, int argumentCount)
     public static int GetVariableArgumentCount(this LuaFunction function, int argumentCount)
     {
     {
-        return function is Closure luaClosure
-            ? argumentCount - luaClosure.Proto.ParameterCount
+        return function is LuaClosure { Proto.HasVariableArguments: true } luaClosure
+            ?argumentCount - luaClosure.Proto.ParameterCount
             : 0;
             : 0;
     }
     }
 }
 }

+ 215 - 0
src/Lua/Runtime/LuaVirtualMachine.Debug.cs

@@ -0,0 +1,215 @@
+using System.Runtime.CompilerServices;
+
+namespace Lua.Runtime;
+
+public static partial class LuaVirtualMachine
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool ExecutePerInstructionHook(ref VirtualMachineExecutionContext context)
+    {
+        var r = Impl(context);
+        if (r.IsCompleted)
+        {
+            if (r.Result == 0)
+            {
+                context.Thread.PopCallStackFrame();
+            }
+
+            return false;
+        }
+
+        context.Task = r;
+        context.Pc--;
+        return true;
+
+        static async ValueTask<int> Impl(VirtualMachineExecutionContext context)
+        {
+            bool countHookIsDone = false;
+            if (context.Thread.IsCountHookEnabled && --context.Thread.HookCount == 0)
+            {
+                context.Thread.HookCount = context.Thread.BaseHookCount;
+
+                var hook = context.Thread.Hook!;
+                var stack = context.Thread.Stack;
+                stack.Push("count");
+                stack.Push(LuaValue.Nil);
+                var funcContext = new LuaFunctionExecutionContext
+                {
+                    State = context.State,
+                    Thread = context.Thread,
+                    ArgumentCount = 2,
+                    FrameBase = context.Thread.Stack.Count - 2,
+                };
+                var frame = new CallStackFrame
+                {
+                    Base = funcContext.FrameBase,
+                    VariableArgumentCount = hook is LuaClosure closure ? Math.Max(funcContext.ArgumentCount - closure.Proto.ParameterCount, 0) : 0,
+                    Function = hook,
+                    CallerInstructionIndex = context.Pc,
+                };
+                frame.Flags |= CallStackFrameFlags.InHook;
+                context.Thread.IsInHook = true;
+                context.Thread.PushCallStackFrame(frame);
+                await hook.Func(funcContext, Memory<LuaValue>.Empty, context.CancellationToken);
+                context.Thread.IsInHook = false;
+
+
+                countHookIsDone = true;
+            }
+
+
+            if (context.Thread.IsLineHookEnabled)
+            {
+                var pc = context.Pc;
+                var sourcePositions = context.Chunk.SourcePositions;
+                var line = sourcePositions[pc].Line;
+
+                if (countHookIsDone || pc == 0 || context.Thread.LastPc < 0 || pc <= context.Thread.LastPc || sourcePositions[context.Thread.LastPc].Line != line)
+                {
+                    if (countHookIsDone)
+                    {
+                        context.Thread.PopCallStackFrame();
+                    }
+
+
+                    var hook = context.Thread.Hook!;
+                    var stack = context.Thread.Stack;
+                    stack.Push("line");
+                    stack.Push(line);
+                    var funcContext = new LuaFunctionExecutionContext
+                    {
+                        State = context.State,
+                        Thread = context.Thread,
+                        ArgumentCount = 2,
+                        FrameBase = context.Thread.Stack.Count - 2,
+                    };
+                    var frame = new CallStackFrame
+                    {
+                        Base = funcContext.FrameBase,
+                        VariableArgumentCount = hook is LuaClosure closure ? Math.Max(funcContext.ArgumentCount - closure.Proto.ParameterCount, 0) : 0,
+                        Function = hook,
+                        CallerInstructionIndex = pc,
+                    };
+                    frame.Flags |= CallStackFrameFlags.InHook;
+                    context.Thread.IsInHook = true;
+                    context.Thread.PushCallStackFrame(frame);
+                    await hook.Func(funcContext, Memory<LuaValue>.Empty, context.CancellationToken);
+                    context.Thread.IsInHook = false;
+                    context.Pc--;
+                    context.Thread.LastPc = pc;
+                    return 0;
+                }
+
+                context.Thread.LastPc = pc;
+            }
+
+            if (countHookIsDone)
+            {
+                context.Pc--;
+                return 0;
+            }
+
+            return -1;
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static ValueTask<int> ExecuteCallHook(ref VirtualMachineExecutionContext context, in CallStackFrame frame, int arguments, bool isTailCall = false)
+    {
+        return ExecuteCallHook(new()
+        {
+            State = context.State,
+            Thread = context.Thread,
+            ArgumentCount = arguments,
+            FrameBase = frame.Base,
+            CallerInstructionIndex = frame.CallerInstructionIndex,
+        }, context.ResultsBuffer, context.CancellationToken, isTailCall);
+    }
+
+    internal static async ValueTask<int> ExecuteCallHook(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken, bool isTailCall = false)
+    {
+        var argCount = context.ArgumentCount;
+        var hook = context.Thread.Hook!;
+        var stack = context.Thread.Stack;
+        if (context.Thread.IsCallHookEnabled)
+        {
+            stack.Push((isTailCall ? "tail call" : "call"));
+
+            stack.Push(LuaValue.Nil);
+            var funcContext = new LuaFunctionExecutionContext
+            {
+                State = context.State,
+                Thread = context.Thread,
+                ArgumentCount = 2,
+                FrameBase = context.Thread.Stack.Count - 2,
+            };
+            CallStackFrame frame = new()
+            {
+                Base = funcContext.FrameBase,
+                VariableArgumentCount = hook.GetVariableArgumentCount(2),
+                Function = hook,
+                CallerInstructionIndex = 0,
+                Flags = CallStackFrameFlags.InHook
+            };
+
+            context.Thread.PushCallStackFrame(frame);
+            try
+            {
+                context.Thread.IsInHook = true;
+                await hook.Func(funcContext, Memory<LuaValue>.Empty, cancellationToken);
+            }
+            finally
+            {
+                context.Thread.IsInHook = false;
+                context.Thread.PopCallStackFrame();
+            }
+        }
+
+        {
+            var frame = context.Thread.GetCurrentFrame();
+            var task = frame.Function.Func(new()
+            {
+                State = context.State,
+                Thread = context.Thread,
+                ArgumentCount = argCount,
+                FrameBase = frame.Base,
+            }, buffer, cancellationToken);
+            if (isTailCall || !context.Thread.IsReturnHookEnabled)
+            {
+                return await task;
+            }
+            var result = await task;
+            stack.Push("return");
+            stack.Push(LuaValue.Nil);
+            var funcContext = new LuaFunctionExecutionContext
+            {
+                State = context.State,
+                Thread = context.Thread,
+                ArgumentCount = 2,
+                FrameBase = context.Thread.Stack.Count - 2,
+            };
+           
+
+            context.Thread.PushCallStackFrame( new()
+            {
+                Base = funcContext.FrameBase,
+                VariableArgumentCount = hook.GetVariableArgumentCount(2),
+                Function = hook,
+                CallerInstructionIndex = 0,
+                Flags = CallStackFrameFlags.InHook
+            });
+            try
+            {
+                context.Thread.IsInHook = true;
+                await hook.Func(funcContext, Memory<LuaValue>.Empty, cancellationToken);
+            }
+            finally
+            {
+                context.Thread.IsInHook = false;
+            }
+
+            context.Thread.PopCallStackFrame();
+            return result;
+        }
+    }
+}

+ 249 - 117
src/Lua/Runtime/LuaVirtualMachine.cs

@@ -22,11 +22,11 @@ public static partial class LuaVirtualMachine
     {
     {
         public readonly LuaState State = state;
         public readonly LuaState State = state;
         public readonly LuaStack Stack = stack;
         public readonly LuaStack Stack = stack;
-        public Closure Closure = (Closure)frame.Function;
+        public LuaClosure LuaClosure = (LuaClosure)frame.Function;
         public readonly LuaValue[] ResultsBuffer = resultsBuffer;
         public readonly LuaValue[] ResultsBuffer = resultsBuffer;
         public readonly Memory<LuaValue> Buffer = buffer;
         public readonly Memory<LuaValue> Buffer = buffer;
         public readonly LuaThread Thread = thread;
         public readonly LuaThread Thread = thread;
-        public Chunk Chunk => Closure.Proto;
+        public Chunk Chunk => LuaClosure.Proto;
         public int FrameBase = frame.Base;
         public int FrameBase = frame.Base;
         public int VariableArgumentCount = frame.VariableArgumentCount;
         public int VariableArgumentCount = frame.VariableArgumentCount;
         public readonly CancellationToken CancellationToken = cancellationToken;
         public readonly CancellationToken CancellationToken = cancellationToken;
@@ -35,10 +35,13 @@ public static partial class LuaVirtualMachine
         public int ResultCount;
         public int ResultCount;
         public int TaskResult;
         public int TaskResult;
         public ValueTask<int> Task;
         public ValueTask<int> Task;
+        public int LastHookPc = -1;
         public bool IsTopLevel => BaseCallStackCount == Thread.CallStack.Count;
         public bool IsTopLevel => BaseCallStackCount == Thread.CallStack.Count;
 
 
         readonly int BaseCallStackCount = thread.CallStack.Count;
         readonly int BaseCallStackCount = thread.CallStack.Count;
 
 
+        public PostOperationType PostOperation;
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool Pop(Instruction instruction, int frameBase)
         public bool Pop(Instruction instruction, int frameBase)
         {
         {
@@ -58,8 +61,9 @@ public static partial class LuaVirtualMachine
             if (frames.Length == BaseCallStackCount) return false;
             if (frames.Length == BaseCallStackCount) return false;
             ref readonly var frame = ref frames[^1];
             ref readonly var frame = ref frames[^1];
             Pc = frame.CallerInstructionIndex;
             Pc = frame.CallerInstructionIndex;
+            Thread.LastPc = Pc;
             ref readonly var lastFrame = ref frames[^2];
             ref readonly var lastFrame = ref frames[^2];
-            Closure = Unsafe.As<Closure>(lastFrame.Function);
+            LuaClosure = Unsafe.As<LuaClosure>(lastFrame.Function);
             var callInstruction = Chunk.Instructions[Pc];
             var callInstruction = Chunk.Instructions[Pc];
             FrameBase = lastFrame.Base;
             FrameBase = lastFrame.Base;
             VariableArgumentCount = lastFrame.VariableArgumentCount;
             VariableArgumentCount = lastFrame.VariableArgumentCount;
@@ -92,15 +96,15 @@ public static partial class LuaVirtualMachine
             switch (opCode)
             switch (opCode)
             {
             {
                 case OpCode.Call:
                 case OpCode.Call:
+                {
+                    var c = callInstruction.C;
+                    if (c != 0)
                     {
                     {
-                        var c = callInstruction.C;
-                        if (c != 0)
-                        {
-                            targetCount = c - 1;
-                        }
-
-                        break;
+                        targetCount = c - 1;
                     }
                     }
+
+                    break;
+                }
                 case OpCode.TForCall:
                 case OpCode.TForCall:
                     target += 3;
                     target += 3;
                     targetCount = callInstruction.C;
                     targetCount = callInstruction.C;
@@ -142,7 +146,7 @@ public static partial class LuaVirtualMachine
         public void Push(in CallStackFrame frame)
         public void Push(in CallStackFrame frame)
         {
         {
             Pc = -1;
             Pc = -1;
-            Closure = (frame.Function as Closure)!;
+            LuaClosure = (frame.Function as LuaClosure)!;
             FrameBase = frame.Base;
             FrameBase = frame.Base;
             VariableArgumentCount = frame.VariableArgumentCount;
             VariableArgumentCount = frame.VariableArgumentCount;
         }
         }
@@ -186,49 +190,60 @@ public static partial class LuaVirtualMachine
             ResultsBuffer.AsSpan(0, count).Clear();
             ResultsBuffer.AsSpan(0, count).Clear();
         }
         }
 
 
+        public int? ExecutePostOperation(PostOperationType postOperation)
+        {
+            switch (postOperation)
+            {
+                case PostOperationType.Nop: break;
+                case PostOperationType.SetResult:
+                    var RA = Instruction.A + FrameBase;
+                    Stack.Get(RA) = TaskResult == 0 ? LuaValue.Nil : ResultsBuffer[0];
+                    Stack.NotifyTop(RA + 1);
+                    ClearResultsBuffer();
+                    break;
+                case PostOperationType.TForCall:
+                    TForCallPostOperation(ref this);
+                    break;
+                case PostOperationType.Call:
+                    CallPostOperation(ref this);
+                    break;
+                case PostOperationType.TailCall:
+                    var resultsSpan = ResultsBuffer.AsSpan(0, TaskResult);
+                    if (!PopFromBuffer(resultsSpan))
+                    {
+                        ResultCount = TaskResult;
+                        resultsSpan.CopyTo(Buffer.Span);
+                        resultsSpan.Clear();
+                        LuaValueArrayPool.Return1024(ResultsBuffer);
+                        return TaskResult;
+                    }
+
+                    resultsSpan.Clear();
+                    break;
+                case PostOperationType.Self:
+                    SelfPostOperation(ref this);
+                    break;
+                case PostOperationType.Compare:
+                    ComparePostOperation(ref this);
+                    break;
+            }
+
+            return null;
+        }
+
         public async ValueTask<int> ExecuteClosureAsyncImpl()
         public async ValueTask<int> ExecuteClosureAsyncImpl()
         {
         {
-            while (MoveNext(ref this, out var postOperation))
+            while (MoveNext(ref this))
             {
             {
                 TaskResult = await Task;
                 TaskResult = await Task;
                 Task = default;
                 Task = default;
-
-                Thread.PopCallStackFrame();
-                switch (postOperation)
+                if (PostOperation != PostOperationType.TailCall)
                 {
                 {
-                    case PostOperationType.Nop: break;
-                    case PostOperationType.SetResult:
-                        var RA = Instruction.A + FrameBase;
-                        Stack.Get(RA) = TaskResult == 0 ? LuaValue.Nil : ResultsBuffer[0];
-                        Stack.NotifyTop(RA + 1);
-                        ClearResultsBuffer();
-                        break;
-                    case PostOperationType.TForCall:
-                        TForCallPostOperation(ref this);
-                        break;
-                    case PostOperationType.Call:
-                        CallPostOperation(ref this);
-                        break;
-                    case PostOperationType.TailCall:
-                        var resultsSpan = ResultsBuffer.AsSpan(0, TaskResult);
-                        if (!PopFromBuffer(resultsSpan))
-                        {
-                            ResultCount = TaskResult;
-                            resultsSpan.CopyTo(Buffer.Span);
-                            resultsSpan.Clear();
-                            LuaValueArrayPool.Return1024(ResultsBuffer);
-                            return TaskResult;
-                        }
-
-                        resultsSpan.Clear();
-                        break;
-                    case PostOperationType.Self:
-                        SelfPostOperation(ref this);
-                        break;
-                    case PostOperationType.Compare:
-                        ComparePostOperation(ref this);
-                        break;
+                    Thread.PopCallStackFrame();
                 }
                 }
+
+                var r = ExecutePostOperation(PostOperation);
+                if (r.HasValue) return r.Value;
             }
             }
 
 
             return ResultCount;
             return ResultCount;
@@ -250,7 +265,7 @@ public static partial class LuaVirtualMachine
     internal static ValueTask<int> ExecuteClosureAsync(LuaState luaState, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     internal static ValueTask<int> ExecuteClosureAsync(LuaState luaState, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
         var thread = luaState.CurrentThread;
         var thread = luaState.CurrentThread;
-        ref readonly var frame = ref thread.GetCallStackFrames()[^1];
+        ref readonly var frame = ref thread.GetCurrentFrame();
         var resultBuffer = LuaValueArrayPool.Rent1024();
         var resultBuffer = LuaValueArrayPool.Rent1024();
 
 
         var context = new VirtualMachineExecutionContext(luaState, thread.Stack, resultBuffer, buffer, thread, in frame,
         var context = new VirtualMachineExecutionContext(luaState, thread.Stack, resultBuffer, buffer, thread, in frame,
@@ -259,24 +274,46 @@ public static partial class LuaVirtualMachine
         return context.ExecuteClosureAsyncImpl();
         return context.ExecuteClosureAsyncImpl();
     }
     }
 
 
-    static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperationType postOperation)
+    static bool MoveNext(ref VirtualMachineExecutionContext context)
     {
     {
-        postOperation = PostOperationType.None;
-
         try
         try
         {
         {
-        // This is a label to restart the execution when new function is called or restarted
+            // This is a label to restart the execution when new function is called or restarted
         Restart:
         Restart:
             ref var instructionsHead = ref context.Chunk.Instructions[0];
             ref var instructionsHead = ref context.Chunk.Instructions[0];
             var frameBase = context.FrameBase;
             var frameBase = context.FrameBase;
             var stack = context.Stack;
             var stack = context.Stack;
             stack.EnsureCapacity(frameBase + context.Chunk.MaxStackPosition);
             stack.EnsureCapacity(frameBase + context.Chunk.MaxStackPosition);
             ref var constHead = ref MemoryMarshalEx.UnsafeElementAt(context.Chunk.Constants, 0);
             ref var constHead = ref MemoryMarshalEx.UnsafeElementAt(context.Chunk.Constants, 0);
+            ref var lineAndCountHookMask = ref context.Thread.LineAndCountHookMask;
+            goto Loop;
+        LineHook:
+
+            {
+                context.LastHookPc = context.Pc;
+                if (!context.Thread.IsInHook && ExecutePerInstructionHook(ref context))
+                {
+                    {
+                        context.PostOperation = PostOperationType.Nop;
+                        return true;
+                    }
+                }
 
 
+                --context.Pc;
+            }
+
+
+        Loop:
             while (true)
             while (true)
             {
             {
                 var instructionRef = Unsafe.Add(ref instructionsHead, ++context.Pc);
                 var instructionRef = Unsafe.Add(ref instructionsHead, ++context.Pc);
                 context.Instruction = instructionRef;
                 context.Instruction = instructionRef;
+                if (lineAndCountHookMask.Value != 0 && (context.Pc != context.LastHookPc))
+                {
+                    goto LineHook;
+                }
+
+                context.LastHookPc = -1;
                 switch (instructionRef.OpCode)
                 switch (instructionRef.OpCode)
                 {
                 {
                     case OpCode.Move:
                     case OpCode.Move:
@@ -304,14 +341,14 @@ public static partial class LuaVirtualMachine
                         continue;
                         continue;
                     case OpCode.GetUpVal:
                     case OpCode.GetUpVal:
                         instruction = instructionRef;
                         instruction = instructionRef;
-                        stack.GetWithNotifyTop(instruction.A + frameBase) = context.Closure.GetUpValue(instruction.B);
+                        stack.GetWithNotifyTop(instruction.A + frameBase) = context.LuaClosure.GetUpValue(instruction.B);
                         continue;
                         continue;
                     case OpCode.GetTabUp:
                     case OpCode.GetTabUp:
                     case OpCode.GetTable:
                     case OpCode.GetTable:
                         instruction = instructionRef;
                         instruction = instructionRef;
                         stackHead = ref stack.FastGet(frameBase);
                         stackHead = ref stack.FastGet(frameBase);
                         ref readonly var vc = ref RKC(ref stackHead, ref constHead, instruction);
                         ref readonly var vc = ref RKC(ref stackHead, ref constHead, instruction);
-                        ref readonly var vb = ref (instruction.OpCode == OpCode.GetTable ? ref Unsafe.Add(ref stackHead, instruction.UIntB) : ref context.Closure.GetUpValueRef(instruction.B));
+                        ref readonly var vb = ref (instruction.OpCode == OpCode.GetTable ? ref Unsafe.Add(ref stackHead, instruction.UIntB) : ref context.LuaClosure.GetUpValueRef(instruction.B));
                         var doRestart = false;
                         var doRestart = false;
                         if (vb.TryReadTable(out var luaTable) && luaTable.TryGetValue(vc, out var resultValue) || GetTableValueSlowPath(vb, vc, ref context, out resultValue, out doRestart))
                         if (vb.TryReadTable(out var luaTable) && luaTable.TryGetValue(vc, out var resultValue) || GetTableValueSlowPath(vb, vc, ref context, out resultValue, out doRestart))
                         {
                         {
@@ -320,7 +357,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.SetTabUp:
                     case OpCode.SetTabUp:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -335,7 +371,7 @@ public static partial class LuaVirtualMachine
                             }
                             }
                         }
                         }
 
 
-                        var table = context.Closure.GetUpValue(instruction.A);
+                        var table = context.LuaClosure.GetUpValue(instruction.A);
 
 
                         if (table.TryReadTable(out luaTable))
                         if (table.TryReadTable(out luaTable))
                         {
                         {
@@ -354,12 +390,11 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Nop;
                         return true;
                         return true;
 
 
                     case OpCode.SetUpVal:
                     case OpCode.SetUpVal:
                         instruction = instructionRef;
                         instruction = instructionRef;
-                        context.Closure.SetUpValue(instruction.B, stack.FastGet(instruction.A + frameBase));
+                        context.LuaClosure.SetUpValue(instruction.B, stack.FastGet(instruction.A + frameBase));
                         continue;
                         continue;
                     case OpCode.SetTable:
                     case OpCode.SetTable:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -394,7 +429,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Nop;
                         return true;
                         return true;
                     case OpCode.NewTable:
                     case OpCode.NewTable:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -417,7 +451,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Self;
                         return true;
                         return true;
                     case OpCode.Add:
                     case OpCode.Add:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -445,7 +478,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Sub:
                     case OpCode.Sub:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -476,7 +508,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
 
 
                     case OpCode.Mul:
                     case OpCode.Mul:
@@ -508,7 +539,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
 
 
                     case OpCode.Div:
                     case OpCode.Div:
@@ -540,7 +570,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Mod:
                     case OpCode.Mod:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -566,7 +595,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Pow:
                     case OpCode.Pow:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -588,7 +616,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Unm:
                     case OpCode.Unm:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -610,7 +637,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Not:
                     case OpCode.Not:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -642,7 +668,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Concat:
                     case OpCode.Concat:
                         if (Concat(ref context, out doRestart))
                         if (Concat(ref context, out doRestart))
@@ -651,7 +676,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.SetResult;
                         return true;
                         return true;
                     case OpCode.Jmp:
                     case OpCode.Jmp:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -685,7 +709,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Compare;
                         return true;
                         return true;
                     case OpCode.Lt:
                     case OpCode.Lt:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -723,7 +746,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Compare;
                         return true;
                         return true;
                     case OpCode.Le:
                     case OpCode.Le:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -760,7 +782,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Compare;
                         return true;
                         return true;
                     case OpCode.Test:
                     case OpCode.Test:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -791,7 +812,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.Call;
                         return true;
                         return true;
                     case OpCode.TailCall:
                     case OpCode.TailCall:
                         if (TailCall(ref context, out doRestart))
                         if (TailCall(ref context, out doRestart))
@@ -801,7 +821,6 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.TailCall;
                         return true;
                         return true;
                     case OpCode.Return:
                     case OpCode.Return:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -872,7 +891,7 @@ public static partial class LuaVirtualMachine
                             continue;
                             continue;
                         }
                         }
 
 
-                        postOperation = PostOperationType.TForCall;
+
                         return true;
                         return true;
                     case OpCode.TForLoop:
                     case OpCode.TForLoop:
                         instruction = instructionRef;
                         instruction = instructionRef;
@@ -895,7 +914,7 @@ public static partial class LuaVirtualMachine
                         iA = instruction.A;
                         iA = instruction.A;
                         ra1 = iA + frameBase + 1;
                         ra1 = iA + frameBase + 1;
                         stack.EnsureCapacity(ra1);
                         stack.EnsureCapacity(ra1);
-                        stack.Get(ra1 - 1) = new Closure(context.State, context.Chunk.Functions[instruction.SBx]);
+                        stack.Get(ra1 - 1) = new LuaClosure(context.State, context.Chunk.Functions[instruction.SBx]);
                         stack.NotifyTop(ra1);
                         stack.NotifyTop(ra1);
                         continue;
                         continue;
                     case OpCode.VarArg:
                     case OpCode.VarArg:
@@ -926,22 +945,23 @@ public static partial class LuaVirtualMachine
             }
             }
 
 
         End:
         End:
-            postOperation = PostOperationType.None;
+            context.PostOperation = PostOperationType.None;
             LuaValueArrayPool.Return1024(context.ResultsBuffer);
             LuaValueArrayPool.Return1024(context.ResultsBuffer);
             return false;
             return false;
         }
         }
         catch (Exception e)
         catch (Exception e)
         {
         {
-            context.PopOnTopCallStackFrames();
             context.State.CloseUpValues(context.Thread, context.FrameBase);
             context.State.CloseUpValues(context.Thread, context.FrameBase);
             LuaValueArrayPool.Return1024(context.ResultsBuffer, true);
             LuaValueArrayPool.Return1024(context.ResultsBuffer, true);
             if (e is not LuaRuntimeException)
             if (e is not LuaRuntimeException)
             {
             {
-                var newException = new LuaRuntimeException(GetTracebacks(ref context), e);
+                var newException = new LuaRuntimeException(context.State.GetTraceback(), e);
+                context.PopOnTopCallStackFrames();
                 context = default;
                 context = default;
                 throw newException;
                 throw newException;
             }
             }
 
 
+            context.PopOnTopCallStackFrames();
             throw;
             throw;
         }
         }
     }
     }
@@ -1014,11 +1034,15 @@ public static partial class LuaVirtualMachine
         var instruction = context.Instruction;
         var instruction = context.Instruction;
         var RA = instruction.A + context.FrameBase;
         var RA = instruction.A + context.FrameBase;
         var va = context.Stack.Get(RA);
         var va = context.Stack.Get(RA);
+        var newBase = RA + 1;
+        bool isMetamethod = false;
         if (!va.TryReadFunction(out var func))
         if (!va.TryReadFunction(out var func))
         {
         {
             if (va.TryGetMetamethod(context.State, Metamethods.Call, out var metamethod) &&
             if (va.TryGetMetamethod(context.State, Metamethods.Call, out var metamethod) &&
                 metamethod.TryReadFunction(out func))
                 metamethod.TryReadFunction(out func))
             {
             {
+                newBase -= 1;
+                isMetamethod = true;
             }
             }
             else
             else
             {
             {
@@ -1027,12 +1051,20 @@ public static partial class LuaVirtualMachine
         }
         }
 
 
         var thread = context.Thread;
         var thread = context.Thread;
-        var (newBase, argumentCount, variableArgumentCount) = PrepareForFunctionCall(thread, func, instruction, RA);
-
+        var (argumentCount, variableArgumentCount) = PrepareForFunctionCall(thread, func, instruction, newBase, isMetamethod);
+        newBase += variableArgumentCount;
         var newFrame = func.CreateNewFrame(ref context, newBase, variableArgumentCount);
         var newFrame = func.CreateNewFrame(ref context, newBase, variableArgumentCount);
 
 
         thread.PushCallStackFrame(newFrame);
         thread.PushCallStackFrame(newFrame);
-        if (func is Closure)
+        if (thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+        {
+            context.PostOperation = PostOperationType.Call;
+            context.Task=ExecuteCallHook(ref context, newFrame,argumentCount);
+            doRestart = false;
+            return false;
+        }
+
+        if (func is LuaClosure)
         {
         {
             context.Push(newFrame);
             context.Push(newFrame);
             doRestart = true;
             doRestart = true;
@@ -1048,6 +1080,7 @@ public static partial class LuaVirtualMachine
 
 
             if (!task.IsCompleted)
             if (!task.IsCompleted)
             {
             {
+                context.PostOperation = PostOperationType.Call;
                 context.Task = task;
                 context.Task = task;
                 return false;
                 return false;
             }
             }
@@ -1131,6 +1164,8 @@ public static partial class LuaVirtualMachine
         var instruction = context.Instruction;
         var instruction = context.Instruction;
         var stack = context.Stack;
         var stack = context.Stack;
         var RA = instruction.A + context.FrameBase;
         var RA = instruction.A + context.FrameBase;
+        var newBase = RA + 1;
+        bool isMetamethod = false;
         var state = context.State;
         var state = context.State;
         var thread = context.Thread;
         var thread = context.Thread;
 
 
@@ -1139,20 +1174,39 @@ public static partial class LuaVirtualMachine
         var va = stack.Get(RA);
         var va = stack.Get(RA);
         if (!va.TryReadFunction(out var func))
         if (!va.TryReadFunction(out var func))
         {
         {
-            if (!va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) &&
-                !metamethod.TryReadFunction(out func))
+            if (va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) &&
+                metamethod.TryReadFunction(out func))
+            {
+                isMetamethod = true;
+                newBase -= 1;
+            }
+            else
             {
             {
                 LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(ref context), "call", metamethod);
                 LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(ref context), "call", metamethod);
             }
             }
         }
         }
 
 
-        var (newBase, argumentCount, variableArgumentCount) = PrepareForFunctionTailCall(thread, func, instruction, RA);
+        var (argumentCount, variableArgumentCount) = PrepareForFunctionTailCall(thread, func, instruction, newBase, isMetamethod);
+        newBase = context.FrameBase + variableArgumentCount;
 
 
+        var lastPc = thread.CallStack.AsSpan()[^1].CallerInstructionIndex;
+        context.Thread.PopCallStackFrameUnsafe();
         var newFrame = func.CreateNewFrame(ref context, newBase, variableArgumentCount);
         var newFrame = func.CreateNewFrame(ref context, newBase, variableArgumentCount);
+
+        newFrame.Flags |= CallStackFrameFlags.TailCall;
+        newFrame.CallerInstructionIndex = lastPc;
         thread.PushCallStackFrame(newFrame);
         thread.PushCallStackFrame(newFrame);
 
 
+        if (thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+        {
+            context.PostOperation = PostOperationType.TailCall;
+           context.Task=ExecuteCallHook(ref context, newFrame,argumentCount,true);
+            doRestart = false;
+            return false;
+        }
+
         context.Push(newFrame);
         context.Push(newFrame);
-        if (func is Closure)
+        if (func is LuaClosure)
         {
         {
             doRestart = true;
             doRestart = true;
             return true;
             return true;
@@ -1163,12 +1217,11 @@ public static partial class LuaVirtualMachine
 
 
         if (!task.IsCompleted)
         if (!task.IsCompleted)
         {
         {
+            context.PostOperation = PostOperationType.TailCall;
             context.Task = task;
             context.Task = task;
             return false;
             return false;
         }
         }
 
 
-        context.Thread.PopCallStackFrame();
-
         doRestart = true;
         doRestart = true;
         var awaiter = task.GetAwaiter();
         var awaiter = task.GetAwaiter();
         var resultCount = awaiter.GetResult();
         var resultCount = awaiter.GetResult();
@@ -1190,20 +1243,48 @@ public static partial class LuaVirtualMachine
         var instruction = context.Instruction;
         var instruction = context.Instruction;
         var stack = context.Stack;
         var stack = context.Stack;
         var RA = instruction.A + context.FrameBase;
         var RA = instruction.A + context.FrameBase;
-
+        bool isMetamethod = false;
         var iteratorRaw = stack.Get(RA);
         var iteratorRaw = stack.Get(RA);
         if (!iteratorRaw.TryReadFunction(out var iterator))
         if (!iteratorRaw.TryReadFunction(out var iterator))
         {
         {
-            LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(ref context), "call", iteratorRaw);
+            if (iteratorRaw.TryGetMetamethod(context.State, Metamethods.Call, out var metamethod) &&
+                metamethod.TryReadFunction(out iterator))
+            {
+                isMetamethod = true;
+            }
+            else
+            {
+                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(ref context), "call", metamethod);
+            }
         }
         }
 
 
         var newBase = RA + 3 + instruction.C;
         var newBase = RA + 3 + instruction.C;
-        stack.Get(newBase) = stack.Get(RA + 1);
-        stack.Get(newBase + 1) = stack.Get(RA + 2);
-        stack.NotifyTop(newBase + 2);
-        var newFrame = iterator.CreateNewFrame(ref context, newBase);
+
+        if (isMetamethod)
+        {
+            stack.Get(newBase) = iteratorRaw;
+            stack.Get(newBase + 1) = stack.Get(RA + 1);
+            stack.Get(newBase + 2) = stack.Get(RA + 2);
+            stack.NotifyTop(newBase + 3);
+        }
+        else
+        {
+            stack.Get(newBase) = stack.Get(RA + 1);
+            stack.Get(newBase + 1) = stack.Get(RA + 2);
+            stack.NotifyTop(newBase + 2);
+        }
+
+        var argumentCount = isMetamethod ? 3 : 2;
+        var variableArgumentCount = iterator.GetVariableArgumentCount(argumentCount);
+        if (variableArgumentCount != 0)
+        {
+            PrepareVariableArgument(stack, newBase, argumentCount, variableArgumentCount);
+            newBase += variableArgumentCount;
+        }
+
+        var newFrame = iterator.CreateNewFrame(ref context, newBase, variableArgumentCount);
         context.Thread.PushCallStackFrame(newFrame);
         context.Thread.PushCallStackFrame(newFrame);
-        if (iterator is Closure)
+        if (iterator is LuaClosure)
         {
         {
             context.Push(newFrame);
             context.Push(newFrame);
             doRestart = true;
             doRestart = true;
@@ -1214,6 +1295,7 @@ public static partial class LuaVirtualMachine
 
 
         if (!task.IsCompleted)
         if (!task.IsCompleted)
         {
         {
+            context.PostOperation = PostOperationType.TForCall;
             context.Task = task;
             context.Task = task;
 
 
             return false;
             return false;
@@ -1264,6 +1346,7 @@ public static partial class LuaVirtualMachine
         table.EnsureArrayCapacity((instruction.C - 1) * 50 + count);
         table.EnsureArrayCapacity((instruction.C - 1) * 50 + count);
         stack.GetBuffer().Slice(RA + 1, count)
         stack.GetBuffer().Slice(RA + 1, count)
             .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]);
             .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]);
+        stack.PopUntil(RA + 1);
     }
     }
 
 
     static void ComparePostOperation(ref VirtualMachineExecutionContext context)
     static void ComparePostOperation(ref VirtualMachineExecutionContext context)
@@ -1325,7 +1408,7 @@ public static partial class LuaVirtualMachine
             }
             }
 
 
             table = metatableValue;
             table = metatableValue;
-        Function:
+            Function:
             if (table.TryReadFunction(out var function))
             if (table.TryReadFunction(out var function))
             {
             {
                 return CallGetTableFunc(targetTable, function, key, ref context, out value, out doRestart);
                 return CallGetTableFunc(targetTable, function, key, ref context, out value, out doRestart);
@@ -1345,8 +1428,16 @@ public static partial class LuaVirtualMachine
         var newFrame = indexTable.CreateNewFrame(ref context, stack.Count - 2);
         var newFrame = indexTable.CreateNewFrame(ref context, stack.Count - 2);
 
 
         context.Thread.PushCallStackFrame(newFrame);
         context.Thread.PushCallStackFrame(newFrame);
+        if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+        {
+            context.PostOperation = context.Instruction.OpCode == OpCode.GetTable ? PostOperationType.SetResult : PostOperationType.Self;
+           context.Task=ExecuteCallHook(ref context, newFrame,2);
+            doRestart = false;
+            result = default;
+            return false;
+        }
 
 
-        if (indexTable is Closure)
+        if (indexTable is LuaClosure)
         {
         {
             context.Push(newFrame);
             context.Push(newFrame);
             doRestart = true;
             doRestart = true;
@@ -1358,6 +1449,7 @@ public static partial class LuaVirtualMachine
 
 
         if (!task.IsCompleted)
         if (!task.IsCompleted)
         {
         {
+            context.PostOperation = context.Instruction.OpCode == OpCode.GetTable ? PostOperationType.SetResult : PostOperationType.Self;
             context.Task = task;
             context.Task = task;
             result = default;
             result = default;
             return false;
             return false;
@@ -1414,9 +1506,10 @@ public static partial class LuaVirtualMachine
 
 
             table = metatableValue;
             table = metatableValue;
 
 
-        Function:
+            Function:
             if (table.TryReadFunction(out var function))
             if (table.TryReadFunction(out var function))
             {
             {
+                context.PostOperation = PostOperationType.Nop;
                 return CallSetTableFunc(targetTable, function, key, value, ref context, out doRestart);
                 return CallSetTableFunc(targetTable, function, key, value, ref context, out doRestart);
             }
             }
         }
         }
@@ -1436,8 +1529,15 @@ public static partial class LuaVirtualMachine
         var newFrame = newIndexFunction.CreateNewFrame(ref context, stack.Count - 3);
         var newFrame = newIndexFunction.CreateNewFrame(ref context, stack.Count - 3);
 
 
         context.Thread.PushCallStackFrame(newFrame);
         context.Thread.PushCallStackFrame(newFrame);
+        if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+        {
+            context.PostOperation = PostOperationType.Nop;
+             context.Task=ExecuteCallHook(ref context, newFrame,3);
+            doRestart = false;
+            return false;
+        }
 
 
-        if (newIndexFunction is Closure)
+        if (newIndexFunction is LuaClosure)
         {
         {
             context.Push(newFrame);
             context.Push(newFrame);
             doRestart = true;
             doRestart = true;
@@ -1447,6 +1547,7 @@ public static partial class LuaVirtualMachine
         var task = newIndexFunction.Invoke(ref context, newFrame, 3);
         var task = newIndexFunction.Invoke(ref context, newFrame, 3);
         if (!task.IsCompleted)
         if (!task.IsCompleted)
         {
         {
+            context.PostOperation = PostOperationType.Nop;
             context.Task = task;
             context.Task = task;
             return false;
             return false;
         }
         }
@@ -1482,8 +1583,15 @@ public static partial class LuaVirtualMachine
             var newFrame = func.CreateNewFrame(ref context, stack.Count - 2);
             var newFrame = func.CreateNewFrame(ref context, stack.Count - 2);
 
 
             context.Thread.PushCallStackFrame(newFrame);
             context.Thread.PushCallStackFrame(newFrame);
+            if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            {
+                context.PostOperation = PostOperationType.SetResult;
+                context.Task=ExecuteCallHook(ref context, newFrame,2);
+                doRestart = false;
+                return false;
+            }
 
 
-            if (func is Closure)
+            if (func is LuaClosure)
             {
             {
                 context.Push(newFrame);
                 context.Push(newFrame);
                 doRestart = true;
                 doRestart = true;
@@ -1495,6 +1603,7 @@ public static partial class LuaVirtualMachine
 
 
             if (!task.IsCompleted)
             if (!task.IsCompleted)
             {
             {
+                context.PostOperation = PostOperationType.SetResult;
                 context.Task = task;
                 context.Task = task;
                 return false;
                 return false;
             }
             }
@@ -1528,8 +1637,15 @@ public static partial class LuaVirtualMachine
             var newFrame = func.CreateNewFrame(ref context, stack.Count - 1);
             var newFrame = func.CreateNewFrame(ref context, stack.Count - 1);
 
 
             context.Thread.PushCallStackFrame(newFrame);
             context.Thread.PushCallStackFrame(newFrame);
+            if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            {
+                context.PostOperation = PostOperationType.SetResult;
+              context.Task=ExecuteCallHook(ref context, newFrame,1);
+                doRestart = false;
+                return false;
+            }
 
 
-            if (func is Closure)
+            if (func is LuaClosure)
             {
             {
                 context.Push(newFrame);
                 context.Push(newFrame);
                 doRestart = true;
                 doRestart = true;
@@ -1541,6 +1657,7 @@ public static partial class LuaVirtualMachine
 
 
             if (!task.IsCompleted)
             if (!task.IsCompleted)
             {
             {
+                context.PostOperation = PostOperationType.SetResult;
                 context.Task = task;
                 context.Task = task;
                 return false;
                 return false;
             }
             }
@@ -1585,8 +1702,14 @@ public static partial class LuaVirtualMachine
             var newFrame = func.CreateNewFrame(ref context, stack.Count - 2);
             var newFrame = func.CreateNewFrame(ref context, stack.Count - 2);
             if (reverseLe) newFrame.Flags |= CallStackFrameFlags.ReversedLe;
             if (reverseLe) newFrame.Flags |= CallStackFrameFlags.ReversedLe;
             context.Thread.PushCallStackFrame(newFrame);
             context.Thread.PushCallStackFrame(newFrame);
-
-            if (func is Closure)
+            if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            {
+                context.PostOperation = PostOperationType.Compare;
+                context.Task=ExecuteCallHook(ref context, newFrame,2);
+                doRestart = false;
+                return false;
+            }
+            if (func is LuaClosure)
             {
             {
                 context.Push(newFrame);
                 context.Push(newFrame);
                 doRestart = true;
                 doRestart = true;
@@ -1597,6 +1720,7 @@ public static partial class LuaVirtualMachine
 
 
             if (!task.IsCompleted)
             if (!task.IsCompleted)
             {
             {
+                context.PostOperation = PostOperationType.Compare;
                 context.Task = task;
                 context.Task = task;
                 return false;
                 return false;
             }
             }
@@ -1646,7 +1770,7 @@ public static partial class LuaVirtualMachine
     // If there are variable arguments, the base of the stack is moved by that number and the values of the variable arguments are placed in front of it.
     // If there are variable arguments, the base of the stack is moved by that number and the values of the variable arguments are placed in front of it.
     // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
     // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
     [MethodImpl(MethodImplOptions.NoInlining)]
     [MethodImpl(MethodImplOptions.NoInlining)]
-    static (int FrameBase, int ArgumentCount, int VariableArgumentCount) PrepareVariableArgument(LuaStack stack, int newBase, int argumentCount,
+    static ( int ArgumentCount, int VariableArgumentCount) PrepareVariableArgument(LuaStack stack, int newBase, int argumentCount,
         int variableArgumentCount)
         int variableArgumentCount)
     {
     {
         var temp = newBase;
         var temp = newBase;
@@ -1658,51 +1782,59 @@ public static partial class LuaVirtualMachine
         var stackBuffer = stack.GetBuffer()[temp..];
         var stackBuffer = stack.GetBuffer()[temp..];
         stackBuffer[..argumentCount].CopyTo(stackBuffer[variableArgumentCount..]);
         stackBuffer[..argumentCount].CopyTo(stackBuffer[variableArgumentCount..]);
         stackBuffer.Slice(argumentCount, variableArgumentCount).CopyTo(stackBuffer);
         stackBuffer.Slice(argumentCount, variableArgumentCount).CopyTo(stackBuffer);
-        return (newBase, argumentCount, variableArgumentCount);
+        return (argumentCount, variableArgumentCount);
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    static (int FrameBase, int ArgumentCount, int VariableArgumentCount) PrepareForFunctionCall(LuaThread thread, LuaFunction function,
-        Instruction instruction, int RA)
+    static (int ArgumentCount, int VariableArgumentCount) PrepareForFunctionCall(LuaThread thread, LuaFunction function,
+        Instruction instruction, int newBase, bool isMetaMethod)
     {
     {
         var argumentCount = instruction.B - 1;
         var argumentCount = instruction.B - 1;
         if (argumentCount == -1)
         if (argumentCount == -1)
         {
         {
-            argumentCount = (ushort)(thread.Stack.Count - (RA + 1));
+            argumentCount = (ushort)(thread.Stack.Count - newBase);
         }
         }
         else
         else
         {
         {
-            thread.Stack.NotifyTop(RA + 1 + argumentCount);
+            if (isMetaMethod)
+            {
+                argumentCount += 1;
+            }
+
+            thread.Stack.NotifyTop(newBase + argumentCount);
         }
         }
 
 
-        var newBase = RA + 1;
         var variableArgumentCount = function.GetVariableArgumentCount(argumentCount);
         var variableArgumentCount = function.GetVariableArgumentCount(argumentCount);
 
 
         if (variableArgumentCount <= 0)
         if (variableArgumentCount <= 0)
         {
         {
-            return (newBase, argumentCount, 0);
+            return (argumentCount, 0);
         }
         }
 
 
         return PrepareVariableArgument(thread.Stack, newBase, argumentCount, variableArgumentCount);
         return PrepareVariableArgument(thread.Stack, newBase, argumentCount, variableArgumentCount);
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    static (int FrameBase, int ArgumentCount, int VariableArgumentCount) PrepareForFunctionTailCall(LuaThread thread, LuaFunction function,
-        Instruction instruction, int RA)
+    static (int ArgumentCount, int VariableArgumentCount) PrepareForFunctionTailCall(LuaThread thread, LuaFunction function,
+        Instruction instruction, int newBase, bool isMetaMethod)
     {
     {
         var stack = thread.Stack;
         var stack = thread.Stack;
 
 
         var argumentCount = instruction.B - 1;
         var argumentCount = instruction.B - 1;
         if (instruction.B == 0)
         if (instruction.B == 0)
         {
         {
-            argumentCount = (ushort)(stack.Count - (RA + 1));
+            argumentCount = (ushort)(stack.Count - newBase);
         }
         }
         else
         else
         {
         {
-            thread.Stack.NotifyTop(RA + 1 + argumentCount);
+            if (isMetaMethod)
+            {
+                argumentCount += 1;
+            }
+
+            thread.Stack.NotifyTop(newBase + argumentCount);
         }
         }
 
 
-        var newBase = RA + 1;
 
 
         // In the case of tailcall, the local variables of the caller are immediately discarded, so there is no need to retain them.
         // In the case of tailcall, the local variables of the caller are immediately discarded, so there is no need to retain them.
         // Therefore, a call can be made without allocating new registers.
         // Therefore, a call can be made without allocating new registers.
@@ -1718,7 +1850,7 @@ public static partial class LuaVirtualMachine
 
 
         if (variableArgumentCount <= 0)
         if (variableArgumentCount <= 0)
         {
         {
-            return (newBase, argumentCount, 0);
+            return (argumentCount, 0);
         }
         }
 
 
         return PrepareVariableArgument(thread.Stack, newBase, argumentCount, variableArgumentCount);
         return PrepareVariableArgument(thread.Stack, newBase, argumentCount, variableArgumentCount);

+ 131 - 17
src/Lua/Runtime/Tracebacks.cs

@@ -5,12 +5,13 @@ using Lua.Internal;
 
 
 namespace Lua.Runtime;
 namespace Lua.Runtime;
 
 
-public class Traceback
+public class Traceback(LuaState state)
 {
 {
-    public required Closure RootFunc { get; init; }
+    public LuaState State => state;
+    public required LuaClosure RootFunc { get; init; }
     public required CallStackFrame[] StackFrames { 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
     internal SourcePosition LastPosition
     {
     {
@@ -21,49 +22,162 @@ public class Traceback
             {
             {
                 LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
                 LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
                 var frame = stackFrames[index];
                 var frame = stackFrames[index];
-                if (lastFunc is Closure closure)
+                if (!frame.IsTailCall && lastFunc is LuaClosure closure)
                 {
                 {
                     var p = closure.Proto;
                     var p = closure.Proto;
+                    if (frame.CallerInstructionIndex < 0 || p.SourcePositions.Length <= frame.CallerInstructionIndex)
+                    {
+                        Console.WriteLine($"Trace back error");
+                        return default;
+                    }
+
                     return p.SourcePositions[frame.CallerInstructionIndex];
                     return p.SourcePositions[frame.CallerInstructionIndex];
                 }
                 }
             }
             }
 
 
+
             return default;
             return default;
         }
         }
     }
     }
 
 
-
     public override string ToString()
     public override string ToString()
+    {
+        return GetTracebackString(State, RootFunc, StackFrames, LuaValue.Nil);
+    }
+    
+    public string ToString(int skipFrames)
+    {
+        if(skipFrames < 0 || skipFrames >= StackFrames.Length)
+        {
+            return "stack traceback:\n";
+        }
+        return GetTracebackString(State, RootFunc, StackFrames[..^skipFrames], LuaValue.Nil);
+    }
+
+    internal static string GetTracebackString(LuaState state, LuaClosure rootFunc, ReadOnlySpan<CallStackFrame> stackFrames, LuaValue message, bool skipFirstCsharpCall = false)
     {
     {
         using var list = new PooledList<char>(64);
         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");
         list.AddRange("stack traceback:\n");
-        var stackFrames = StackFrames.AsSpan();
         var intFormatBuffer = (stackalloc char[15]);
         var intFormatBuffer = (stackalloc char[15]);
+        var shortSourceBuffer = (stackalloc char[59]);
+        {
+            if (0 < stackFrames.Length && !skipFirstCsharpCall && stackFrames[^1].Function is { } f and not LuaClosure)
+            {
+                list.AddRange("\t[C#]: in function '");
+                list.AddRange(f.Name);
+                list.AddRange("'\n");
+            }
+        }
+
         for (var index = stackFrames.Length - 1; index >= 0; index--)
         for (var index = stackFrames.Length - 1; index >= 0; index--)
         {
         {
-            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
-            var frame = stackFrames[index];
-            if (lastFunc is not null and not Closure)
+            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : rootFunc;
+            if (lastFunc is not null and not LuaClosure)
             {
             {
                 list.AddRange("\t[C#]: in function '");
                 list.AddRange("\t[C#]: in function '");
                 list.AddRange(lastFunc.Name);
                 list.AddRange(lastFunc.Name);
                 list.AddRange("'\n");
                 list.AddRange("'\n");
             }
             }
-            else if (lastFunc is Closure closure)
+            else if (lastFunc is LuaClosure closure)
             {
             {
+                var frame = stackFrames[index];
+
+                if (frame.IsTailCall)
+                {
+                    list.AddRange("\t(...tail calls...)\n");
+                }
+
                 var p = closure.Proto;
                 var p = closure.Proto;
                 var root = p.GetRoot();
                 var root = p.GetRoot();
                 list.AddRange("\t");
                 list.AddRange("\t");
-                list.AddRange(root.Name);
+                var len = LuaDebug.WriteShortSource(root.Name, shortSourceBuffer);
+                list.AddRange(shortSourceBuffer[..len]);
                 list.AddRange(":");
                 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(p.Name);
-                list.AddRange("'\n");
+                if (p.SourcePositions.Length <= frame.CallerInstructionIndex)
+                {
+                    list.AddRange("Trace back error");
+                }
+                else
+                {
+                    p.SourcePositions[frame.CallerInstructionIndex].Line.TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture);
+                    list.AddRange(intFormatBuffer[..charsWritten]);
+                }
+
+
+                list.AddRange(": in ");
+                if (root == p)
+                {
+                    list.AddRange("main chunk");
+                    list.AddRange("\n");
+                    goto Next;
+                }
+
+                if (0 < index && stackFrames[index - 1].Flags.HasFlag(CallStackFrameFlags.InHook))
+                {
+                    list.AddRange("hook");
+                    list.AddRange(" '");
+                    list.AddRange("?");
+                    list.AddRange("'\n");
+                    goto Next;
+                }
+
+                foreach (var pair in state.Environment.Dictionary)
+                {
+                    if (pair.Key.TryReadString(out var name)
+                        && pair.Value.TryReadFunction(out var result) &&
+                        result == closure)
+                    {
+                        list.AddRange("function '");
+                        list.AddRange(name);
+                        list.AddRange("'\n");
+                        goto Next;
+                    }
+                }
+
+                var caller = index > 1 ? stackFrames[index - 2].Function : rootFunc;
+                if (index > 0 && caller is LuaClosure callerClosure)
+                {
+                    var t = LuaDebug.GetFuncName(callerClosure.Proto, stackFrames[index - 1].CallerInstructionIndex, out var name);
+                    if (t is not null)
+                    {
+                        if (t is "global")
+                        {
+                            list.AddRange("function '");
+                            list.AddRange(name);
+                            list.AddRange("'\n");
+                        }
+                        else
+                        {
+                            list.AddRange(t);
+                            list.AddRange(" '");
+                            list.AddRange(name);
+                            list.AddRange("'\n");
+                        }
+
+                        goto Next;
+                    }
+                }
+
+
+                list.AddRange("function <");
+                list.AddRange(shortSourceBuffer[..len]);
+                list.AddRange(":");
+                {
+                    p.LineDefined.TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture);
+                    list.AddRange(intFormatBuffer[..charsWritten]);
+                    list.AddRange(">\n");
+                }
+
+            Next: ;
             }
             }
         }
         }
 
 
-        return list.AsSpan().ToString();
+        return list.AsSpan()[..^1].ToString();
     }
     }
 }
 }

+ 17 - 6
src/Lua/Standard/BasicLibrary.cs

@@ -95,9 +95,9 @@ public sealed class BasicLibrary
         // do not use LuaState.DoFileAsync as it uses the newExecutionContext
         // do not use LuaState.DoFileAsync as it uses the newExecutionContext
         var text = await File.ReadAllTextAsync(arg0, cancellationToken);
         var text = await File.ReadAllTextAsync(arg0, cancellationToken);
         var fileName = Path.GetFileName(arg0);
         var fileName = Path.GetFileName(arg0);
-        var chunk = LuaCompiler.Default.Compile(text, fileName);
+        var chunk = LuaCompiler.Default.Compile(text, "@"+fileName);
 
 
-        return await new Closure(context.State, chunk).InvokeAsync(context, buffer, cancellationToken);
+        return await new LuaClosure(context.State, chunk).InvokeAsync(context, buffer, cancellationToken);
     }
     }
 
 
     public ValueTask<int> Error(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     public ValueTask<int> Error(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
@@ -106,7 +106,18 @@ public sealed class BasicLibrary
             ? "(error object is a nil value)"
             ? "(error object is a nil value)"
             : context.Arguments[0];
             : context.Arguments[0];
 
 
-        throw new LuaRuntimeException(context.State.GetTraceback(), value);
+        Traceback t;
+        try
+        {
+           t = context.State.GetTraceback(context.Thread);
+            
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine(e);
+            throw;
+        }
+        throw new LuaRuntimeException(t, value);
     }
     }
 
 
     public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
@@ -171,7 +182,7 @@ public sealed class BasicLibrary
             var text = await File.ReadAllTextAsync(arg0, cancellationToken);
             var text = await File.ReadAllTextAsync(arg0, cancellationToken);
             var fileName = Path.GetFileName(arg0);
             var fileName = Path.GetFileName(arg0);
             var chunk = LuaCompiler.Default.Compile(text, fileName);
             var chunk = LuaCompiler.Default.Compile(text, fileName);
-            buffer.Span[0] = new Closure(context.State, chunk, arg2);
+            buffer.Span[0] = new LuaClosure(context.State, chunk, arg2);
             return 1;
             return 1;
         }
         }
         catch (Exception ex)
         catch (Exception ex)
@@ -200,8 +211,8 @@ public sealed class BasicLibrary
         {
         {
             if (arg0.TryRead<string>(out var str))
             if (arg0.TryRead<string>(out var str))
             {
             {
-                var chunk = LuaCompiler.Default.Compile(str, arg1 ?? "chunk");
-                buffer.Span[0] = new Closure(context.State, chunk, arg3);
+                var chunk = LuaCompiler.Default.Compile(str, arg1 ?? str);
+                buffer.Span[0] = new LuaClosure(context.State, chunk, arg3);
                 return new(1);
                 return new(1);
             }
             }
             else if (arg0.TryRead<LuaFunction>(out var function))
             else if (arg0.TryRead<LuaFunction>(out var function))

+ 9 - 2
src/Lua/Standard/CoroutineLibrary.cs

@@ -1,3 +1,5 @@
+using Lua.Runtime;
+
 namespace Lua.Standard;
 namespace Lua.Standard;
 
 
 public sealed class CoroutineLibrary
 public sealed class CoroutineLibrary
@@ -57,8 +59,13 @@ public sealed class CoroutineLibrary
         var arg0 = context.GetArgument<LuaFunction>(0);
         var arg0 = context.GetArgument<LuaFunction>(0);
         var thread = new LuaCoroutine(arg0, false);
         var thread = new LuaCoroutine(arg0, false);
 
 
-        buffer.Span[0] = new LuaFunction("wrap", async (context, buffer, cancellationToken) =>
+        buffer.Span[0] = new CSharpClosure("wrap", [thread],static async (context, buffer, cancellationToken) =>
         {
         {
+            var thread = context.GetCsClosure()!.UpValues[0].Read<LuaThread>();
+            if (thread is not LuaCoroutine coroutine)
+            {
+                return await thread.ResumeAsync(context, buffer, cancellationToken);
+            }
             var stack = context.Thread.Stack;
             var stack = context.Thread.Stack;
             var frameBase = stack.Count;
             var frameBase = stack.Count;
 
 
@@ -68,7 +75,7 @@ public sealed class CoroutineLibrary
             {
             {
                 Base = frameBase,
                 Base = frameBase,
                 VariableArgumentCount = 0,
                 VariableArgumentCount = 0,
-                Function = arg0,
+                Function = coroutine.Function,
             });
             });
             try
             try
             {
             {

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

@@ -0,0 +1,652 @@
+using System.Runtime.CompilerServices;
+using Lua.Runtime;
+using Lua.Internal;
+
+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("getuservalue", GetUserValue),
+            new("setuservalue", SetUserValue),
+            new("traceback", Traceback),
+            new("getregistry", GetRegistry),
+            new("upvalueid", UpValueId),
+            new("upvaluejoin", UpValueJoin),
+            new("gethook", GetHook),
+            new("sethook", SetHook),
+            new("getinfo", GetInfo),
+        ];
+    }
+
+    public readonly LuaFunction[] Functions;
+
+
+    static 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;
+    }
+
+
+    static 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;
+
+
+        if (frame.Function is LuaClosure closure)
+        {
+            var locals = closure.Proto.Locals;
+            var nextFrame = callStack[^level];
+            var currentPc = nextFrame.CallerInstructionIndex;
+            {
+                int nextFrameBase = (closure.Proto.Instructions[currentPc].OpCode is OpCode.Call or OpCode.TailCall) ? nextFrame.Base - 1 : nextFrame.Base;
+                if (nextFrameBase - 1 < frameBase + index)
+                {
+                    name = null;
+                    return ref Unsafe.NullRef<LuaValue>();
+                }
+            }
+            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;
+                }
+            }
+        }
+        else
+        {
+            int nextFrameBase = level != 0 ? callStack[^level].Base : thread.Stack.Count;
+
+            if (nextFrameBase - 1 < frameBase + index)
+            {
+                name = null;
+                return ref Unsafe.NullRef<LuaValue>();
+            }
+        }
+
+        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 LuaClosure 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 LuaClosure closure)
+        {
+            if (func is CSharpClosure csClosure)
+            {
+                var upValues = csClosure.UpValues;
+                if (index < 0 || index >= upValues.Length)
+                {
+                    return new(0);
+                }
+
+                buffer.Span[0] = "";
+                buffer.Span[1] = upValues[index];
+                return new(1);
+            }
+
+            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 LuaClosure closure)
+        {
+            if (func is CSharpClosure csClosure)
+            {
+                var upValues = csClosure.UpValues;
+                if (index >= 0 && index < upValues.Length)
+                {
+                    buffer.Span[0] = "";
+                    upValues[index] = value;
+                    return new(0);
+                }
+
+                return new(0);
+            }
+
+            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> GetUserValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        if (!context.GetArgumentOrDefault(0).TryRead<ILuaUserData>(out var iUserData))
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        var index = 1; // context.GetArgument<int>(1); //for lua 5.4
+        var userValues = iUserData.UserValues;
+        if (index > userValues.Length
+            //index < 1 ||  // for lua 5.4
+           )
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        buffer.Span[0] = userValues[index - 1];
+        return new(1);
+    }
+
+    public ValueTask<int> SetUserValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var iUserData = context.GetArgument<ILuaUserData>(0);
+        var value = context.GetArgument(1);
+        var index = 1; // context.GetArgument<int>(2);// for lua 5.4
+        var userValues = iUserData.UserValues;
+        if (index > userValues.Length
+            //|| index < 1 // for lua 5.4
+           )
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        userValues[index - 1] = value;
+        buffer.Span[0] = new LuaValue(iUserData);
+        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, argOffset == 0 ? 1 : 0);
+
+        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);
+        }
+
+        if (thread is LuaCoroutine coroutine)
+        {
+            if (coroutine.LuaTraceback is not null)
+            {
+                buffer.Span[0] = coroutine.LuaTraceback.ToString(level);
+                return new(1);
+            }
+        }
+
+        var callStack = thread.GetCallStackFrames();
+        if (callStack.Length == 0)
+        {
+            buffer.Span[0] = "stack traceback:";
+            return new(1);
+        }
+
+        var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1);
+        var frames = callStack[1..^skipCount];
+        buffer.Span[0] = Runtime.Traceback.GetTracebackString(context.State, (LuaClosure)callStack[0].Function, frames, message, level == 1);
+        return new(1);
+    }
+
+    public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        buffer.Span[0] = context.State.Registry;
+        return new(1);
+    }
+
+    public ValueTask<int> UpValueId(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var n1 = context.GetArgument<int>(1);
+        var f1 = context.GetArgument<LuaFunction>(0);
+
+        if (f1 is not LuaClosure closure)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        var upValues = closure.GetUpValuesSpan();
+        if (n1 <= 0 || n1 > upValues.Length)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        buffer.Span[0] = new LuaValue(upValues[n1 - 1]);
+        return new(1);
+    }
+
+    public ValueTask<int> UpValueJoin(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var n2 = context.GetArgument<int>(3);
+        var f2 = context.GetArgument<LuaFunction>(2);
+        var n1 = context.GetArgument<int>(1);
+        var f1 = context.GetArgument<LuaFunction>(0);
+
+        if (f1 is not LuaClosure closure1 || f2 is not LuaClosure closure2)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            return new(1);
+        }
+
+        var upValues1 = closure1.GetUpValuesSpan();
+        var upValues2 = closure2.GetUpValuesSpan();
+        if (n1 <= 0 || n1 > upValues1.Length)
+        {
+            context.ThrowBadArgument(1, "invalid upvalue index");
+        }
+
+        if (n2 < 0 || n2 > upValues2.Length)
+        {
+            context.ThrowBadArgument(3, "invalid upvalue index");
+        }
+
+        upValues1[n1 - 1] = upValues2[n2 - 1];
+        return new(0);
+    }
+
+    public async ValueTask<int> SetHook(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var thread = GetLuaThread(context, out var argOffset);
+        LuaFunction? hook = context.GetArgumentOrDefault<LuaFunction?>(argOffset);
+        if (hook is null)
+        {
+            thread.HookCount = -1;
+            thread.BaseHookCount = 0;
+            thread.IsCountHookEnabled = false;
+            thread.Hook = null;
+            thread.IsLineHookEnabled = false;
+            thread.IsCallHookEnabled = false;
+            thread.IsReturnHookEnabled = false;
+            return 0;
+        }
+
+        var mask = context.GetArgument<string>(argOffset + 1);
+        if (context.HasArgument(argOffset + 2))
+        {
+            var count = context.GetArgument<int>(argOffset + 2);
+            thread.BaseHookCount = count;
+            thread.HookCount = count;
+            if (count > 0)
+            {
+                thread.IsCountHookEnabled = true;
+            }
+        }
+        else
+        {
+            thread.HookCount = 0;
+            thread.BaseHookCount = 0;
+            thread.IsCountHookEnabled = false;
+        }
+
+        thread.IsLineHookEnabled = (mask.Contains('l'));
+        thread.IsCallHookEnabled = (mask.Contains('c'));
+        thread.IsReturnHookEnabled = (mask.Contains('r'));
+
+        if (thread.IsLineHookEnabled)
+        {
+            thread.LastPc = thread.CallStack.Count > 0 ? thread.GetCurrentFrame().CallerInstructionIndex : -1;
+        }
+
+        thread.Hook = hook;
+        if (thread.IsReturnHookEnabled && context.Thread == thread)
+        {
+            var stack = thread.Stack;
+            stack.Push("return");
+            stack.Push(LuaValue.Nil);
+            var funcContext = new LuaFunctionExecutionContext
+            {
+                State = context.State,
+                Thread = context.Thread,
+                ArgumentCount = 2,
+                FrameBase = stack.Count - 2,
+            };
+            var frame = new CallStackFrame
+            {
+                Base = funcContext.FrameBase,
+                VariableArgumentCount = hook.GetVariableArgumentCount(2),
+                Function = hook,
+            };
+            frame.Flags |= CallStackFrameFlags.InHook;
+            thread.PushCallStackFrame(frame);
+            try
+            {
+                thread.IsInHook = true;
+                await hook.Func(funcContext, Memory<LuaValue>.Empty, cancellationToken);
+            }
+            finally
+            {
+                thread.IsInHook = false;
+            }
+
+            thread.PopCallStackFrame();
+        }
+
+        return 0;
+    }
+
+
+    public ValueTask<int> GetHook(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var thread = GetLuaThread(context, out var argOffset);
+        if (thread.Hook is null)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = LuaValue.Nil;
+            buffer.Span[2] = LuaValue.Nil;
+            return new(3);
+        }
+
+        buffer.Span[0] = thread.Hook;
+        buffer.Span[1] = (
+            (thread.IsCallHookEnabled ? "c" : "") +
+            (thread.IsReturnHookEnabled ? "r" : "") +
+            (thread.IsLineHookEnabled ? "l" : "")
+        );
+        buffer.Span[2] = thread.BaseHookCount;
+        return new(3);
+    }
+
+    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)
+            {
+                buffer.Span[0] = LuaValue.Nil;
+                return new(1);
+            }
+
+
+            currentFrame = thread.GetCallStackFrames()[^(level)];
+            previousFrame = level + 1 <= callStack.Length ? callStack[^(level + 1)] : null;
+            if (level != 1)
+            {
+                pc = thread.GetCallStackFrames()[^(level - 1)].CallerInstructionIndex;
+            }
+
+            functionToInspect = currentFrame.Value.Function;
+        }
+        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'))
+        {
+            if (functionToInspect is LuaClosure closure)
+            {
+                var activeLines = new LuaTable(0, 8);
+                foreach (var pos in closure.Proto.SourcePositions)
+                {
+                    activeLines[pos.Line] = true;
+                }
+
+                table["activelines"] = activeLines;
+            }
+        }
+
+        buffer.Span[0] = table;
+
+        return new(1);
+    }
+}

+ 4 - 3
src/Lua/Standard/FileHandle.cs

@@ -176,11 +176,12 @@ public class FileHandle : ILuaUserData
             ? context.Arguments[1]
             ? context.Arguments[1]
             : "*l";
             : "*l";
 
 
-        LuaValue[] formats = [format];
 
 
-        buffer.Span[0] = new LuaFunction("iterator", (context, buffer, cancellationToken) =>
+        buffer.Span[0] = new CSharpClosure("iterator", [new (file),format],static (context, buffer, cancellationToken) =>
         {
         {
-            var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, buffer, true);
+            var upValues = context.GetCsClosure()!.UpValues.AsSpan();
+            var file = upValues[0].Read<FileHandle>();
+            var resultCount = IOHelper.Read(context.State, file, "lines", 0, upValues[1..], buffer, true);
             return new(resultCount);
             return new(resultCount);
         });
         });
 
 

+ 10 - 3
src/Lua/Standard/IOLibrary.cs

@@ -1,4 +1,5 @@
 using Lua.Internal;
 using Lua.Internal;
+using Lua.Runtime;
 using Lua.Standard.Internal;
 using Lua.Standard.Internal;
 
 
 namespace Lua.Standard;
 namespace Lua.Standard;
@@ -96,8 +97,9 @@ public sealed class IOLibrary
         if (context.ArgumentCount == 0)
         if (context.ArgumentCount == 0)
         {
         {
             var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
             var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
-            buffer.Span[0] = new LuaFunction("iterator", (context, buffer, ct) =>
+            buffer.Span[0] = new CSharpClosure("iterator",[new (file)] ,static (context, buffer, ct) =>
             {
             {
+                var file = context.GetCsClosure()!.UpValues[0].Read<FileHandle>();
                 var resultCount = IOHelper.Read(context.State, file, "lines", 0, [], buffer, true);
                 var resultCount = IOHelper.Read(context.State, file, "lines", 0, [], buffer, true);
                 if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil)
                 if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil)
                 {
                 {
@@ -115,10 +117,15 @@ public sealed class IOLibrary
             IOHelper.Open(context.State, fileName, "r", methodBuffer.AsMemory(), true);
             IOHelper.Open(context.State, fileName, "r", methodBuffer.AsMemory(), true);
 
 
             var file = methodBuffer[0].Read<FileHandle>();
             var file = methodBuffer[0].Read<FileHandle>();
-            var formats = context.Arguments[1..].ToArray();
+            var upValues = new LuaValue[context.Arguments.Length];
+            upValues[0] = new(file);
+            context.Arguments[1..].CopyTo(upValues[1..]);
 
 
-            buffer.Span[0] = new LuaFunction("iterator", (context, buffer, ct) =>
+            buffer.Span[0] = new CSharpClosure("iterator", upValues, static (context, buffer, ct) =>
             {
             {
+                var upValues = context.GetCsClosure()!.UpValues;
+                var file = upValues[0].Read<FileHandle>();
+                var formats = upValues.AsSpan(1);
                 var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, buffer, true);
                 var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, buffer, true);
                 if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil)
                 if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil)
                 {
                 {

+ 39 - 0
src/Lua/Standard/Internal/ConsoleHelper.cs

@@ -0,0 +1,39 @@
+namespace Lua.Standard.Internal;
+
+public class ConsoleHelper
+{
+    public static bool SupportStandardConsole => LuaPlatformUtility.IsSandBox;
+
+    private static Stream? _inputStream;
+    private static TextReader? _inputReader;
+
+    public static Stream OpenStandardInput()
+    {
+        if (SupportStandardConsole)
+        {
+            return Console.OpenStandardInput();
+        }
+        _inputStream ??= new MemoryStream();
+        _inputReader ??= new StreamReader(_inputStream);
+        return _inputStream;
+    }
+
+    public static int Read()
+    {
+        if (SupportStandardConsole)
+        {
+            return Console.Read();
+        }
+        return _inputReader?.Read() ?? 0;
+    }
+    
+    public static Stream OpenStandardOutput()
+    {
+        return Console.OpenStandardOutput();
+    }
+    
+    public static Stream OpenStandardError()
+    {
+        return Console.OpenStandardError();
+    }
+}

+ 31 - 0
src/Lua/Standard/Internal/LuaPlatformUtility.cs

@@ -0,0 +1,31 @@
+namespace Lua.Standard.Internal;
+
+public class LuaPlatformUtility
+{
+    public static bool IsSandBox => SupportStdio;
+    public static bool SupportStdio => _supportStdioTryLazy.Value;
+    
+    private static Lazy<bool> _supportStdioTryLazy = new Lazy<bool>(() =>
+    {
+        try
+        {
+#if NET6_0_OR_GREATER
+            var isDesktop = System.OperatingSystem.IsWindows() || 
+                            System.OperatingSystem.IsLinux() || 
+                            System.OperatingSystem.IsMacOS();
+            if (!isDesktop)
+            {
+                return false;
+            }
+#endif
+            _ = Console.OpenStandardInput();
+            _ = Console.OpenStandardOutput();
+            return true;
+        }
+        catch (Exception)
+        {
+            return false;
+        }
+    });
+
+}

+ 18 - 7
src/Lua/Standard/MathematicsLibrary.cs

@@ -224,23 +224,34 @@ public sealed class MathematicsLibrary
     public ValueTask<int> Random(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     public ValueTask<int> Random(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
         var rand = context.State.Environment[RandomInstanceKey].Read<RandomUserData>().Random;
         var rand = context.State.Environment[RandomInstanceKey].Read<RandomUserData>().Random;
-
+        // When we call it without arguments, it returns a pseudo-random real number with uniform distribution in the interval [0,1
         if (context.ArgumentCount == 0)
         if (context.ArgumentCount == 0)
         {
         {
             buffer.Span[0] = rand.NextDouble();
             buffer.Span[0] = rand.NextDouble();
         }
         }
+        // When we call it with only one argument, an integer n, it returns an integer pseudo-random number such that 1 <= x <= n.
+        // This is different from the C# random functions.
+        // See: https://www.lua.org/pil/18.html
         else if (context.ArgumentCount == 1)
         else if (context.ArgumentCount == 1)
         {
         {
-            var arg0 = context.GetArgument<double>(0);
-            buffer.Span[0] = rand.NextDouble() * (arg0 - 1) + 1;
+            var arg0 = context.GetArgument<int>(0);
+            if (arg0 < 0)
+            {
+                LuaRuntimeException.BadArgument(context.State.GetTraceback(), 0, "random");
+            }
+            buffer.Span[0] = rand.Next(1, arg0 + 1);
         }
         }
+        // Finally, we can call random with two integer arguments, l and u, to get a pseudo-random integer x such that l <= x <= u.
         else
         else
         {
         {
-            var arg0 = context.GetArgument<double>(0);
-            var arg1 = context.GetArgument<double>(1);
-            buffer.Span[0] = rand.NextDouble() * (arg1 - arg0) + arg0;
+            var arg0 = context.GetArgument<int>(0);
+            var arg1 = context.GetArgument<int>(1);
+            if (arg0 < 1 || arg1 <= arg0)
+            {
+                LuaRuntimeException.BadArgument(context.State.GetTraceback(), 1, "random");
+            }
+            buffer.Span[0] = rand.Next(arg0, arg1 + 1);
         }
         }
-
         return new(1);
         return new(1);
     }
     }
 
 

+ 1 - 1
src/Lua/Standard/ModuleLibrary.cs

@@ -26,7 +26,7 @@ public sealed class ModuleLibrary
             var chunk = LuaCompiler.Default.Compile(module.ReadText(), module.Name);
             var chunk = LuaCompiler.Default.Compile(module.ReadText(), module.Name);
 
 
             using var methodBuffer = new PooledArray<LuaValue>(1);
             using var methodBuffer = new PooledArray<LuaValue>(1);
-            await new Closure(context.State, chunk).InvokeAsync(context, methodBuffer.AsMemory(), cancellationToken);
+            await new LuaClosure(context.State, chunk).InvokeAsync(context, methodBuffer.AsMemory(), cancellationToken);
 
 
             loadedTable = methodBuffer[0];
             loadedTable = methodBuffer[0];
             loaded[arg0] = loadedTable;
             loaded[arg0] = loadedTable;

+ 19 - 5
src/Lua/Standard/OpenLibsExtensions.cs

@@ -1,4 +1,5 @@
 using Lua.Runtime;
 using Lua.Runtime;
+using Lua.Standard.Internal;
 
 
 namespace Lua.Standard;
 namespace Lua.Standard;
 
 
@@ -39,16 +40,16 @@ public static class OpenLibsExtensions
 
 
     public static void OpenIOLibrary(this LuaState state)
     public static void OpenIOLibrary(this LuaState state)
     {
     {
+        
         var io = new LuaTable(0, IOLibrary.Instance.Functions.Length);
         var io = new LuaTable(0, IOLibrary.Instance.Functions.Length);
         foreach (var func in IOLibrary.Instance.Functions)
         foreach (var func in IOLibrary.Instance.Functions)
         {
         {
             io[func.Name] = func;
             io[func.Name] = func;
         }
         }
-
-        io["stdio"] = new LuaValue(new FileHandle(Console.OpenStandardInput()));
-        io["stdout"] = new LuaValue(new FileHandle(Console.OpenStandardOutput()));
-        io["stderr"] = new LuaValue(new FileHandle(Console.OpenStandardError()));
-
+        io["stdio"] = new LuaValue(new FileHandle(ConsoleHelper.OpenStandardInput()));
+        io["stdout"] = new LuaValue(new FileHandle(ConsoleHelper.OpenStandardOutput()));
+        io["stderr"] = new LuaValue(new FileHandle(ConsoleHelper.OpenStandardError()));
+        
         state.Environment["io"] = io;
         state.Environment["io"] = io;
         state.LoadedModules["io"] = io;
         state.LoadedModules["io"] = io;
     }
     }
@@ -130,6 +131,18 @@ public static class OpenLibsExtensions
         state.Environment["table"] = table;
         state.Environment["table"] = table;
         state.LoadedModules["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)
     public static void OpenStandardLibraries(this LuaState state)
     {
     {
@@ -142,5 +155,6 @@ public static class OpenLibsExtensions
         state.OpenOperatingSystemLibrary();
         state.OpenOperatingSystemLibrary();
         state.OpenStringLibrary();
         state.OpenStringLibrary();
         state.OpenTableLibrary();
         state.OpenTableLibrary();
+        state.OpenDebugLibrary();
     }
     }
 }
 }

+ 7 - 3
src/Lua/Standard/StringLibrary.cs

@@ -1,5 +1,7 @@
 using System.Text;
 using System.Text;
+using System.Text.RegularExpressions;
 using Lua.Internal;
 using Lua.Internal;
+using Lua.Runtime;
 
 
 namespace Lua.Standard;
 namespace Lua.Standard;
 
 
@@ -429,17 +431,19 @@ public sealed class StringLibrary
 
 
         var regex = StringHelper.ToRegex(pattern);
         var regex = StringHelper.ToRegex(pattern);
         var matches = regex.Matches(s);
         var matches = regex.Matches(s);
-        var i = 0;
 
 
-        buffer.Span[0] = new LuaFunction("iterator", (context, buffer, cancellationToken) =>
+        buffer.Span[0] = new CSharpClosure("iterator",[new LuaValue(matches),0],static (context, buffer, cancellationToken) =>
         {
         {
+            var upValues = context.GetCsClosure()!.UpValues;
+            var matches = upValues[0].Read<MatchCollection>();
+            var i = upValues[1].Read<int>();
             if (matches.Count > i)
             if (matches.Count > i)
             {
             {
                 var match = matches[i];
                 var match = matches[i];
                 var groups = match.Groups;
                 var groups = match.Groups;
 
 
                 i++;
                 i++;
-
+                 upValues[1] = i;
                 if (groups.Count == 1)
                 if (groups.Count == 1)
                 {
                 {
                     buffer.Span[0] = match.Value;
                     buffer.Span[0] = match.Value;

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

@@ -41,7 +41,11 @@ public sealed class TableLibrary
         ],
         ],
         ParameterCount = 2,
         ParameterCount = 2,
         UpValues = [],
         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,
         MaxStackPosition = 2,
+        HasVariableArguments = false,
+        LineDefined = 0,
+        LastLineDefined = 0,
     };
     };
 
 
     public ValueTask<int> Concat(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     public ValueTask<int> Concat(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
@@ -159,7 +163,7 @@ public sealed class TableLibrary
         var arg0 = context.GetArgument<LuaTable>(0);
         var arg0 = context.GetArgument<LuaTable>(0);
         var arg1 = context.HasArgument(1)
         var arg1 = context.HasArgument(1)
             ? context.GetArgument<LuaFunction>(1)
             ? context.GetArgument<LuaFunction>(1)
-            : new Closure(context.State, defaultComparer);
+            : new LuaClosure(context.State, defaultComparer);
         
         
         context.Thread.PushCallStackFrame(new ()
         context.Thread.PushCallStackFrame(new ()
         {
         {

+ 5 - 1
tests/Lua.Tests/HexConverterTests.cs

@@ -7,8 +7,12 @@ public class HexConverterTests
     [TestCase("0x10", 16)]
     [TestCase("0x10", 16)]
     [TestCase("0x0p12", 0)]
     [TestCase("0x0p12", 0)]
     [TestCase("-0x1.0p-1", -0.5)]
     [TestCase("-0x1.0p-1", -0.5)]
+    [TestCase("0x0.1e", 0.1171875)]
+    [TestCase("0xA23p-4", 162.1875)]
+    [TestCase("0X1.921FB54442D18P+1", 3.1415926535898)]
+    [TestCase("0X1.bcde19p+1", 3.475527882576)]
     public void Test_ToDouble(string text, double expected)
     public void Test_ToDouble(string text, double expected)
     {
     {
-        Assert.That(HexConverter.ToDouble(text), Is.EqualTo(expected));
+        Assert.That(Math.Abs(HexConverter.ToDouble(text) - expected), Is.LessThanOrEqualTo(0.00001d));
     }
     }
 }
 }

+ 25 - 0
tests/Lua.Tests/LuaObjectTests.cs

@@ -1,3 +1,5 @@
+using Lua.Standard;
+
 namespace Lua.Tests;
 namespace Lua.Tests;
 
 
 [LuaObject]
 [LuaObject]
@@ -32,6 +34,12 @@ public partial class TestUserData
     {
     {
         return Property;
         return Property;
     }
     }
+
+    [LuaMetamethod(LuaObjectMetamethod.Call)]
+    public string Call()
+    {
+        return "Called!";
+    }
 }
 }
 
 
 public class LuaObjectTests
 public class LuaObjectTests
@@ -120,4 +128,21 @@ public class LuaObjectTests
         Assert.That(results, Has.Length.EqualTo(1));
         Assert.That(results, Has.Length.EqualTo(1));
         Assert.That(results[0], Is.EqualTo(new LuaValue(1)));
         Assert.That(results[0], Is.EqualTo(new LuaValue(1)));
     }
     }
+
+    [Test]
+    public async Task Test_CallMetamethod()
+    {
+        var userData = new TestUserData();
+
+        var state = LuaState.Create();
+        state.OpenBasicLibrary();
+        state.Environment["test"] = userData;
+        var results = await state.DoStringAsync("""
+                                                assert(test() == 'Called!')
+                                                return test()
+                                                """);
+
+        Assert.That(results, Has.Length.EqualTo(1));
+        Assert.That(results[0], Is.EqualTo(new LuaValue("Called!")));
+    }
 }
 }

+ 7 - 1
tests/Lua.Tests/LuaTests.cs

@@ -12,7 +12,7 @@ public class LuaTests
         state = LuaState.Create();
         state = LuaState.Create();
         state.OpenStandardLibraries();
         state.OpenStandardLibraries();
     }
     }
-    
+
     [Test]
     [Test]
     public async Task Test_Closure()
     public async Task Test_Closure()
     {
     {
@@ -55,6 +55,12 @@ public class LuaTests
         await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/coroutine.lua"));
         await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/coroutine.lua"));
     }
     }
 
 
+    [Test]
+    public async Task Test_Debug_Mini()
+    {
+        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/db.lua"));
+    }
+
     [Test]
     [Test]
     public async Task Test_VeryBig()
     public async Task Test_VeryBig()
     {
     {

+ 67 - 1
tests/Lua.Tests/MetatableTests.cs

@@ -10,7 +10,7 @@ public class MetatableTests
     public void SetUp()
     public void SetUp()
     {
     {
         state = LuaState.Create();
         state = LuaState.Create();
-        state.OpenBasicLibrary();
+        state.OpenStandardLibraries();
     }
     }
 
 
     [Test]
     [Test]
@@ -86,4 +86,70 @@ assert(metatable.__newindex.x == 2)
 ";
 ";
         await state.DoStringAsync(source);
         await state.DoStringAsync(source);
     }
     }
+
+    [Test]
+    public async Task Test_Metamethod_Call()
+    {
+        var source = @"
+metatable = {
+    __call = function(a, b)
+        return a.x + b
+    end
+}
+
+local a = {}
+a.x = 1
+setmetatable(a, metatable)
+assert(a(2) == 3)
+function tail(a, b)
+    return a(b)
+end
+tail(a, 3)
+assert(tail(a, 3) == 4)
+";
+        await state.DoStringAsync(source);
+    }
+    
+    [Test]
+    public async Task Test_Metamethod_TForCall()
+    {
+        var source = @"
+local i =3
+function a(...)
+  local v ={...}
+   assert(v[1] ==t)
+   assert(v[2] == nil)
+   if i ==3 then
+       assert(v[3] == nil)
+    else
+      assert(v[3] == i)
+    end
+   
+   i  =i -1
+   if i ==0 then return nil end
+   return i
+end
+
+t =setmetatable({},{__call = a})
+
+for i in t do 
+end
+";
+        await state.DoStringAsync(source);
+    }
+    [Test]
+    public async Task Test_Hook_Metamethods()
+    {
+        var source = """ 
+                     local t = {}
+                     local a =setmetatable({},{__add =function (a,b) return a end})
+
+                     debug.sethook(function () table.insert(t,debug.traceback()) end,"c")
+                     a =a+a
+                     return t
+                     """;
+        var r = await state.DoStringAsync(source);
+        Assert.That(r, Has.Length.EqualTo(1));
+        Assert.That(r[0].Read<LuaTable>()[1].Read<string>(), Does.Contain("stack traceback:"));
+    }
 }
 }

+ 2 - 2
tests/Lua.Tests/ParserTests.cs

@@ -17,8 +17,8 @@ else
 end";
 end";
             var actual = LuaSyntaxTree.Parse(source).Nodes[0];
             var actual = LuaSyntaxTree.Parse(source).Nodes[0];
             var expected = new IfStatementNode(
             var expected = new IfStatementNode(
-                new() { ConditionNode = new BooleanLiteralNode(true, new(1, 3)), ThenNodes = [] },
-                [new() { ConditionNode = new BooleanLiteralNode(true, new(2, 7)), ThenNodes = [] }],
+                new() { Position = new(1,8),ConditionNode = new BooleanLiteralNode(true, new(1, 3)), ThenNodes = [] },
+                [new() {Position = new(2,13), ConditionNode = new BooleanLiteralNode(true, new(2, 7)), ThenNodes = [] }],
                 [],
                 [],
                 new(1, 0));
                 new(1, 0));
 
 

+ 17 - 0
tests/Lua.Tests/StringTests.cs

@@ -0,0 +1,17 @@
+using Lua.CodeAnalysis.Syntax;
+using Lua.CodeAnalysis.Syntax.Nodes;
+
+namespace Lua.Tests;
+
+public class StringTests
+{
+    [TestCase("\r")]
+    [TestCase("\n")]
+    [TestCase("\r\n")]
+    public async Task Test_ShortString_RealNewLine(string newLine)
+    {
+        var result = await LuaState.Create().DoStringAsync($"return \"\\{newLine}\"");
+        Assert.That(result, Has.Length.EqualTo(1));
+        Assert.That(result[0], Is.EqualTo(new LuaValue("\n")));
+    }
+}

+ 21 - 0
tests/Lua.Tests/TableTests.cs

@@ -41,4 +41,25 @@ public class TableTests
         Assert.That(value, Is.EqualTo(new LuaValue(2)));
         Assert.That(value, Is.EqualTo(new LuaValue(2)));
         Assert.That(table[2], Is.EqualTo(new LuaValue(3)));
         Assert.That(table[2], Is.EqualTo(new LuaValue(3)));
     }
     }
+
+    [Test]
+    public void Test_TableResize()
+    {
+        var table = new LuaTable();
+        int i = 1;
+        int count = 10000;
+        while (count > 0)
+        {
+            var key = i;
+            table[key] = key;
+            table[key * 2 - key / 2] = key;
+            i += key;
+            count--;
+        }
+
+        table[1] = 0;
+        table[int.MaxValue - 1] = 0;
+        Assert.That(table[1], Is.EqualTo(new LuaValue(0)));
+        Assert.That(table[int.MaxValue - 1], Is.EqualTo(new LuaValue(0)));
+    }
 }
 }

+ 32 - 32
tests/Lua.Tests/tests-lua/coroutine.lua

@@ -54,15 +54,15 @@ assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead")
 
 
 
 
 -- yields in tail calls
 -- yields in tail calls
--- local function foo (i) return coroutine.yield(i) end
--- f = coroutine.wrap(function ()
---   for i=1,10 do
---     assert(foo(i) == _G.x)
---   end
---   return 'a'
--- end)
--- for i=1,10 do _G.x = i; assert(f(i) == i) end
--- _G.x = 'xuxu'; assert(f('xuxu') == 'a')
+local function foo (i) return coroutine.yield(i) end
+f = coroutine.wrap(function ()
+  for i=1,10 do
+    assert(foo(i) == _G.x)
+  end
+  return 'a'
+end)
+for i=1,10 do _G.x = i; assert(f(i) == i) end
+_G.x = 'xuxu'; assert(f('xuxu') == 'a')
 
 
 -- recursive
 -- recursive
 function pf (n, i)
 function pf (n, i)
@@ -78,33 +78,33 @@ for i=1,10 do
 end
 end
 
 
 -- sieve
 -- sieve
--- function gen (n)
---   return coroutine.wrap(function ()
---     for i=2,n do coroutine.yield(i) end
---   end)
--- end
+function gen (n)
+  return coroutine.wrap(function ()
+    for i=2,n do coroutine.yield(i) end
+  end)
+end
 
 
 
 
--- function filter (p, g)
---   return coroutine.wrap(function ()
---     while 1 do
---       local n = g()
---       if n == nil then return end
---       if math.fmod(n, p) ~= 0 then coroutine.yield(n) end
---     end
---   end)
--- end
+function filter (p, g)
+  return coroutine.wrap(function ()
+    while 1 do
+      local n = g()
+      if n == nil then return end
+      if math.fmod(n, p) ~= 0 then coroutine.yield(n) end
+    end
+  end)
+end
 
 
--- local x = gen(100)
--- local a = {}
--- while 1 do
---   local n = x()
---   if n == nil then break end
---   table.insert(a, n)
---   x = filter(n, x)
--- end
+local x = gen(100)
+local a = {}
+while 1 do
+  local n = x()
+  if n == nil then break end
+  table.insert(a, n)
+  x = filter(n, x)
+end
 
 
--- assert(#a == 25 and a[#a] == 97)
+assert(#a == 25 and a[#a] == 97)
 
 
 
 
 -- yielding across C boundaries
 -- yielding across C boundaries

+ 19 - 17
tests/Lua.Tests/tests-lua/db.lua

@@ -28,7 +28,7 @@ do
   assert(debug.getinfo(1000) == nil)   -- out of range level
   assert(debug.getinfo(1000) == nil)   -- out of range level
   assert(debug.getinfo(-1) == nil)     -- out of range level
   assert(debug.getinfo(-1) == nil)     -- out of range level
   local a = debug.getinfo(print)
   local a = debug.getinfo(print)
-  assert(a.what == "C" and a.short_src == "[C]")
+  assert(a.what == "C#" and a.short_src == "[C#]") -- changed C to C#
   a = debug.getinfo(print, "L")
   a = debug.getinfo(print, "L")
   assert(a.activelines == nil)
   assert(a.activelines == nil)
   local b = debug.getinfo(test, "SfL")
   local b = debug.getinfo(test, "SfL")
@@ -82,7 +82,10 @@ repeat
   assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
   assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
 
 
   function f (x, name)   -- local!
   function f (x, name)   -- local!
-    name = name or 'f'
+    if not name then -- todo fix compiler bug Lua-CSharp
+        name = 'f'
+    end
+
     local a = debug.getinfo(1)
     local a = debug.getinfo(1)
     assert(a.name == name and a.namewhat == 'local')
     assert(a.name == name and a.namewhat == 'local')
     return x
     return x
@@ -238,7 +241,7 @@ function f(a,b)
   local _, y = debug.getlocal(1, 2)
   local _, y = debug.getlocal(1, 2)
   assert(x == a and y == b)
   assert(x == a and y == b)
   assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
   assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
-  assert(debug.setlocal(2, 4, "maçã") == "B")
+  assert(debug.setlocal(2, 4, "ma��") == "B")
   x = debug.getinfo(2)
   x = debug.getinfo(2)
   assert(x.func == g and x.what == "Lua" and x.name == 'g' and
   assert(x.func == g and x.what == "Lua" and x.name == 'g' and
          x.nups == 1 and string.find(x.source, "^@.*db%.lua$"))
          x.nups == 1 and string.find(x.source, "^@.*db%.lua$"))
@@ -253,10 +256,10 @@ function foo()
 end; foo()  -- set L
 end; foo()  -- set L
 -- check line counting inside strings and empty lines
 -- check line counting inside strings and empty lines
 
 
-_ = 'alo\
-alo' .. [[
-
-]]
+--_ = 'alo\  -- todo fix compiler bug Lua-CSharp
+--alo' .. [[
+--
+--]]
 --[[
 --[[
 ]]
 ]]
 assert(debug.getinfo(1, "l").currentline == L+11)  -- check count of lines
 assert(debug.getinfo(1, "l").currentline == L+11)  -- check count of lines
@@ -266,9 +269,9 @@ function g(...)
   local arg = {...}
   local arg = {...}
   do local a,b,c; a=math.sin(40); end
   do local a,b,c; a=math.sin(40); end
   local feijao
   local feijao
-  local AAAA,B = "xuxu", "mamão"
+  local AAAA,B = "xuxu", "mam�o"
   f(AAAA,B)
   f(AAAA,B)
-  assert(AAAA == "pera" and B == "maçã")
+  assert(AAAA == "pera" and B == "ma��")
   do
   do
      local B = 13
      local B = 13
      local x,y = debug.getlocal(1,5)
      local x,y = debug.getlocal(1,5)
@@ -463,8 +466,8 @@ co = load[[
 local a = 0
 local a = 0
 -- 'A' should be visible to debugger only after its complete definition
 -- 'A' should be visible to debugger only after its complete definition
 debug.sethook(function (e, l)
 debug.sethook(function (e, l)
-  if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(*temporary)")
-  elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
+  if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == nil)-- assert(debug.getlocal(2, 1) == "(*temporary)") --changed behavior Lua-CSharp
+   elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
   end
   end
 end, "l")
 end, "l")
 co()  -- run local function definition
 co()  -- run local function definition
@@ -620,12 +623,11 @@ setmetatable(a, {
 
 
 local b = setmetatable({}, getmetatable(a))
 local b = setmetatable({}, getmetatable(a))
 
 
-assert(a[3] == "__index" and a^3 == "__pow" and a..a == "__concat")
-assert(a/3 == "__div" and 3%a == "__mod")
-assert (a==b and a.op == "__eq")
-assert (a>=b and a.op == "__le")
-assert (a>b and a.op == "__lt")
+assert(a[3] == "index" and a^3 == "pow" and a..a == "concat")
+assert(a/3 == "div" and 3%a == "mod")
+assert (a==b and a.op == "eq")
+assert (a>=b and a.op == "le")
+assert (a>b and a.op == "lt")
 
 
 
 
 print"OK"
 print"OK"
-

+ 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)