Browse Source

Change: New lua compiler and API

Akeit0 7 months ago
parent
commit
ae7ac649d3
100 changed files with 5274 additions and 6246 deletions
  1. 7 53
      sandbox/Benchmark/InterpreterSteps.cs
  2. 15 15
      sandbox/ConsoleApp1/ConsoleApp1.csproj
  3. 1 4
      sandbox/ConsoleApp1/LVec3.cs
  4. 13 16
      sandbox/ConsoleApp1/Program.cs
  5. 4 113
      sandbox/ConsoleApp1/test.lua
  6. 2 0
      src/Lua.SourceGenerator/Utilities/CodeBuilder.cs
  7. 120 0
      src/Lua/CodeAnalysis/Compilation/Declarements.cs
  8. 0 36
      src/Lua/CodeAnalysis/Compilation/Descriptions.cs
  9. 486 0
      src/Lua/CodeAnalysis/Compilation/Dump.cs
  10. 1616 0
      src/Lua/CodeAnalysis/Compilation/Function.cs
  11. 0 499
      src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs
  12. 0 1287
      src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs
  13. 1026 0
      src/Lua/CodeAnalysis/Compilation/Parser.cs
  14. 77 0
      src/Lua/CodeAnalysis/Compilation/PrototypeBuilder.cs
  15. 835 0
      src/Lua/CodeAnalysis/Compilation/Scanner.cs
  16. 0 188
      src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs
  17. 17 0
      src/Lua/CodeAnalysis/Compilation/Token.cs
  18. 7 0
      src/Lua/CodeAnalysis/LocalVariable.cs
  19. 0 14
      src/Lua/CodeAnalysis/SourcePosition.cs
  20. 0 555
      src/Lua/CodeAnalysis/Syntax/DisplayStringSyntaxVisitor.cs
  21. 0 40
      src/Lua/CodeAnalysis/Syntax/ISyntaxNodeVisitor.cs
  22. 0 61
      src/Lua/CodeAnalysis/Syntax/Keywords.cs
  23. 0 543
      src/Lua/CodeAnalysis/Syntax/Lexer.cs
  24. 0 30
      src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs
  25. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/AssignmentStatementNode.cs
  26. 0 54
      src/Lua/CodeAnalysis/Syntax/Nodes/BinaryExpressionNode.cs
  27. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/BooleanLiteralNode.cs
  28. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/BreakStatementNode.cs
  29. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/CallFunctionExpressionNode.cs
  30. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/CallFunctionStatementNode.cs
  31. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/CallTableMethodExpressionNode.cs
  32. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/CallTableMethodStatementNode.cs
  33. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/DoStatementNode.cs
  34. 0 3
      src/Lua/CodeAnalysis/Syntax/Nodes/ExpressionNode.cs
  35. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs
  36. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs
  37. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs
  38. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/GotoStatementNode.cs
  39. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/GroupedExpressionNode.cs
  40. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/IdentifierNode.cs
  41. 0 16
      src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs
  42. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/LabelStatementNode.cs
  43. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/LocalAssignmentStatementNode.cs
  44. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs
  45. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/NilLiteralNode.cs
  46. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs
  47. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/NumericLiteralNode.cs
  48. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/RepeatStatementNode.cs
  49. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/ReturnStatementNode.cs
  50. 0 3
      src/Lua/CodeAnalysis/Syntax/Nodes/StatementNode.cs
  51. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/StringLiteralNode.cs
  52. 0 14
      src/Lua/CodeAnalysis/Syntax/Nodes/TableConstructorExpressionNode.cs
  53. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/TableIndexerAccessExpressionNode.cs
  54. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/TableMemberAccessExpressionNode.cs
  55. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs
  56. 0 30
      src/Lua/CodeAnalysis/Syntax/Nodes/UnaryExpressionNode.cs
  57. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/VariableArgumentsExpressionNode.cs
  58. 0 9
      src/Lua/CodeAnalysis/Syntax/Nodes/WhileStatementNode.cs
  59. 0 49
      src/Lua/CodeAnalysis/Syntax/OperatorPrecedence.cs
  60. 0 1037
      src/Lua/CodeAnalysis/Syntax/Parser.cs
  61. 0 6
      src/Lua/CodeAnalysis/Syntax/SyntaxNode.cs
  62. 0 364
      src/Lua/CodeAnalysis/Syntax/SyntaxToken.cs
  63. 0 60
      src/Lua/CodeAnalysis/Syntax/SyntaxTokenEnumerator.cs
  64. 8 0
      src/Lua/CodeAnalysis/UpValueDesc.cs
  65. 44 51
      src/Lua/Exceptions.cs
  66. 0 24
      src/Lua/Internal/BitFlags256.cs
  67. 0 1
      src/Lua/Internal/CancellationExtensions.cs
  68. 12 0
      src/Lua/Internal/Constants.cs
  69. 16 11
      src/Lua/Internal/FarmHash.cs
  70. 11 3
      src/Lua/Internal/FastListCore.cs
  71. 1 0
      src/Lua/Internal/FastStackCore.cs
  72. 32 33
      src/Lua/Internal/LuaDebug.cs
  73. 67 0
      src/Lua/Internal/Pool.cs
  74. 3 4
      src/Lua/Internal/PooledList.cs
  75. 3 0
      src/Lua/Internal/StringHelper.cs
  76. 15 16
      src/Lua/Loaders/CompositeModuleLoader.cs
  77. 0 1
      src/Lua/Loaders/FileModuleLoader.cs
  78. 1 1
      src/Lua/LuaCoroutine.cs
  79. 1 2
      src/Lua/LuaFunctionExecutionContext.cs
  80. 45 6
      src/Lua/LuaState.cs
  81. 13 17
      src/Lua/LuaStateExtensions.cs
  82. 3 3
      src/Lua/LuaTable.cs
  83. 0 30
      src/Lua/Runtime/Chunk.cs
  84. 389 126
      src/Lua/Runtime/Instruction.cs
  85. 23 13
      src/Lua/Runtime/LuaClosure.cs
  86. 2 2
      src/Lua/Runtime/LuaValueRuntimeExtensions.cs
  87. 7 8
      src/Lua/Runtime/LuaVirtualMachine.Debug.cs
  88. 208 159
      src/Lua/Runtime/LuaVirtualMachine.cs
  89. 31 0
      src/Lua/Runtime/Prototype.cs
  90. 38 9
      src/Lua/Runtime/Tracebacks.cs
  91. 17 19
      src/Lua/Standard/BasicLibrary.cs
  92. 14 14
      src/Lua/Standard/DebugLibrary.cs
  93. 4 2
      src/Lua/Standard/Internal/ConsoleHelper.cs
  94. 3 4
      src/Lua/Standard/Internal/LuaPlatformUtility.cs
  95. 1 2
      src/Lua/Standard/ModuleLibrary.cs
  96. 7 19
      src/Lua/Standard/TableLibrary.cs
  97. 0 241
      tests/Lua.Tests/LexerTests.cs
  98. 28 53
      tests/Lua.Tests/LuaTests.cs
  99. 1 1
      tests/Lua.Tests/OperatorTests.cs
  100. 0 29
      tests/Lua.Tests/ParserTests.cs

+ 7 - 53
sandbox/Benchmark/InterpreterSteps.cs

@@ -1,7 +1,5 @@
 using BenchmarkDotNet.Attributes;
 using BenchmarkDotNet.Attributes;
 using Lua;
 using Lua;
-using Lua.CodeAnalysis.Compilation;
-using Lua.CodeAnalysis.Syntax;
 using Lua.Runtime;
 using Lua.Runtime;
 using Lua.Standard;
 using Lua.Standard;
 
 
@@ -10,38 +8,16 @@ public class InterpreterSteps
 {
 {
     string sourceText = default!;
     string sourceText = default!;
     LuaState state = default!;
     LuaState state = default!;
-    SyntaxToken[] tokens = [];
-    LuaSyntaxTree ast = default!;
-    Chunk chunk = default!;
-    LuaValue[] results = new LuaValue[1];
+    LuaClosure closure = default!;
 
 
     [GlobalSetup]
     [GlobalSetup]
     public void GlobalSetup()
     public void GlobalSetup()
     {
     {
         var filePath = FileHelper.GetAbsolutePath("n-body.lua");
         var filePath = FileHelper.GetAbsolutePath("n-body.lua");
         sourceText = File.ReadAllText(filePath);
         sourceText = File.ReadAllText(filePath);
-
-        var lexer = new Lexer
-        {
-            Source = sourceText.AsMemory()
-        };
-
-        var buffer = new List<SyntaxToken>();
-        while (lexer.MoveNext())
-        {
-            buffer.Add(lexer.Current);
-        }
-
-        tokens = buffer.ToArray();
-
-        var parser = new Parser();
-        foreach (var token in tokens)
-        {
-            parser.Add(token);
-        }
-
-        ast = parser.Parse();
-        chunk = LuaCompiler.Default.Compile(ast);
+        state = LuaState.Create();
+        state.OpenStandardLibraries();
+        closure = state.Compile(sourceText,sourceText);
     }
     }
 
 
     [IterationSetup]
     [IterationSetup]
@@ -60,39 +36,17 @@ public class InterpreterSteps
         LuaState.Create();
         LuaState.Create();
     }
     }
 
 
-    [Benchmark]
-    public void Lexer()
-    {
-        var lexer = new Lexer
-        {
-            Source = sourceText.AsMemory()
-        };
-
-        while (lexer.MoveNext()) { }
-    }
-
-    [Benchmark]
-    public LuaSyntaxTree Parser()
-    {
-        var parser = new Parser();
-        foreach (var token in tokens)
-        {
-            parser.Add(token);
-        }
-
-        return parser.Parse();
-    }
 
 
     [Benchmark]
     [Benchmark]
-    public Chunk Compile()
+    public LuaClosure Compile()
     {
     {
-        return LuaCompiler.Default.Compile(ast);
+        return state.Compile(sourceText, sourceText);
     }
     }
 
 
     [Benchmark]
     [Benchmark]
     public async ValueTask RunAsync()
     public async ValueTask RunAsync()
     {
     {
-        using (await state.RunAsync(chunk))
+        using (await state.RunAsync(closure))
         {
         {
         }
         }
     }
     }

+ 15 - 15
sandbox/ConsoleApp1/ConsoleApp1.csproj

@@ -1,21 +1,21 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
-  <ItemGroup>
-    <ProjectReference Include="..\..\src\Lua\Lua.csproj" />
-    <ProjectReference Include="..\..\src\Lua.SourceGenerator\Lua.SourceGenerator.csproj">
-			<OutputItemType>Analyzer</OutputItemType>
-			<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
-		</ProjectReference>
-  </ItemGroup>
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\Lua\Lua.csproj"/>
+        <ProjectReference Include="..\..\src\Lua.SourceGenerator\Lua.SourceGenerator.csproj">
+            <OutputItemType>Analyzer</OutputItemType>
+            <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+        </ProjectReference>
+    </ItemGroup>
 
 
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>net8.0</TargetFramework>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <Nullable>enable</Nullable>
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
 
 
-    <EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
-    <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
-  </PropertyGroup>
+        <EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
+        <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
+    </PropertyGroup>
 
 
 </Project>
 </Project>

+ 1 - 4
sandbox/ConsoleApp1/LVec3.cs

@@ -30,10 +30,7 @@ public partial class LVec3
     [LuaMember("create")]
     [LuaMember("create")]
     public static LVec3 Create(float x, float y, float z)
     public static LVec3 Create(float x, float y, float z)
     {
     {
-        return new LVec3()
-        {
-            value = new Vector3(x, y, z)
-        };
+        return new LVec3() { value = new Vector3(x, y, z) };
     }
     }
 
 
     public override string ToString()
     public override string ToString()

+ 13 - 16
sandbox/ConsoleApp1/Program.cs

@@ -1,5 +1,4 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
-using Lua.CodeAnalysis.Syntax;
 using Lua.CodeAnalysis.Compilation;
 using Lua.CodeAnalysis.Compilation;
 using Lua.Runtime;
 using Lua.Runtime;
 using Lua;
 using Lua;
@@ -14,20 +13,18 @@ try
 {
 {
     var source = File.ReadAllText(GetAbsolutePath("test.lua"));
     var source = File.ReadAllText(GetAbsolutePath("test.lua"));
 
 
-    var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua");
 
 
     Console.WriteLine("Source Code " + new string('-', 50));
     Console.WriteLine("Source Code " + new string('-', 50));
 
 
-    var debugger = new DisplayStringSyntaxVisitor();
-    Console.WriteLine(debugger.GetDisplayString(syntaxTree));
+    Console.WriteLine(source);
 
 
-    var chunk = LuaCompiler.Default.Compile(syntaxTree, "test.lua");
+    var closure = state.Compile(source, "test.lua");
 
 
-    DebugChunk(chunk, 0);
+    DebugChunk(closure.Proto, 0);
 
 
     Console.WriteLine("Output " + new string('-', 50));
     Console.WriteLine("Output " + new string('-', 50));
 
 
-    using var results = await state.RunAsync(chunk);
+    using var results = await state.RunAsync(closure);
 
 
     Console.WriteLine("Result " + new string('-', 50));
     Console.WriteLine("Result " + new string('-', 50));
 
 
@@ -52,24 +49,24 @@ static string GetAbsolutePath(string relativePath, [CallerFilePath] string calle
     return Path.Combine(Path.GetDirectoryName(callerFilePath)!, relativePath);
     return Path.Combine(Path.GetDirectoryName(callerFilePath)!, relativePath);
 }
 }
 
 
-static void DebugChunk(Chunk chunk, int id)
+static void DebugChunk(Prototype chunk, int id)
 {
 {
     Console.WriteLine($"Chunk[{id}]" + new string('=', 50));
     Console.WriteLine($"Chunk[{id}]" + new string('=', 50));
     Console.WriteLine($"Parameters:{chunk.ParameterCount}");
     Console.WriteLine($"Parameters:{chunk.ParameterCount}");
 
 
-    Console.WriteLine("Instructions " + new string('-', 50));
+    Console.WriteLine("Code " + new string('-', 50));
     var index = 0;
     var index = 0;
-    foreach (var inst in chunk.Instructions.ToArray())
+    foreach (var inst in chunk.Code)
     {
     {
-        Console.WriteLine($"[{index}]\t{chunk.SourcePositions[index]}\t\t{inst}");
+        Console.WriteLine($"[{index}]\t{chunk.LineInfo[index]}\t\t{inst}");
         index++;
         index++;
     }
     }
 
 
-    Console.WriteLine("Locals " + new string('-', 50));
+    Console.WriteLine("LocalVariables " + new string('-', 50));
     index = 0;
     index = 0;
-    foreach (var local in chunk.Locals.ToArray())
+    foreach (var local in chunk.LocalVariables)
     {
     {
-        Console.WriteLine($"[{index}]\t{local.Index}\t{local.Name}\t{local.StartPc}\t{local.EndPc}");
+        Console.WriteLine($"[{index}]\t{local.Name}\t{local.StartPc}\t{local.EndPc}");
         index++;
         index++;
     }
     }
 
 
@@ -85,14 +82,14 @@ static void DebugChunk(Chunk chunk, int id)
     index = 0;
     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.IsLocal ? 1 : 0)}\t{upValue.Index}");
         index++;
         index++;
     }
     }
 
 
     Console.WriteLine();
     Console.WriteLine();
 
 
     var nestedChunkId = 0;
     var nestedChunkId = 0;
-    foreach (var localChunk in chunk.Functions)
+    foreach (var localChunk in chunk.ChildPrototypes)
     {
     {
         DebugChunk(localChunk, nestedChunkId);
         DebugChunk(localChunk, nestedChunkId);
         nestedChunkId++;
         nestedChunkId++;

+ 4 - 113
sandbox/ConsoleApp1/test.lua

@@ -1,115 +1,6 @@
-sun = {}
-jupiter = {}
-saturn = {}
-uranus = {}
-neptune = {}
 
 
-local sqrt = math.sqrt
-
-local PI = 3.141592653589793
-local SOLAR_MASS = 4 * PI * PI
-local DAYS_PER_YEAR = 365.24
-sun.x = 0.0
-sun.y = 0.0
-sun.z = 0.0
-sun.vx = 0.0
-sun.vy = 0.0
-sun.vz = 0.0
-sun.mass = SOLAR_MASS
-jupiter.x = 4.84143144246472090e+00
-jupiter.y = -1.16032004402742839e+00
-jupiter.z = -1.03622044471123109e-01
-jupiter.vx = 1.66007664274403694e-03 * DAYS_PER_YEAR
-jupiter.vy = 7.69901118419740425e-03 * DAYS_PER_YEAR
-jupiter.vz = -6.90460016972063023e-05 * DAYS_PER_YEAR
-jupiter.mass = 9.54791938424326609e-04 * SOLAR_MASS
-saturn.x = 8.34336671824457987e+00
-saturn.y = 4.12479856412430479e+00
-saturn.z = -4.03523417114321381e-01
-saturn.vx = -2.76742510726862411e-03 * DAYS_PER_YEAR
-saturn.vy = 4.99852801234917238e-03 * DAYS_PER_YEAR
-saturn.vz = 2.30417297573763929e-05 * DAYS_PER_YEAR
-saturn.mass = 2.85885980666130812e-04 * SOLAR_MASS
-uranus.x = 1.28943695621391310e+01
-uranus.y = -1.51111514016986312e+01
-uranus.z = -2.23307578892655734e-01
-uranus.vx = 2.96460137564761618e-03 * DAYS_PER_YEAR
-uranus.vy = 2.37847173959480950e-03 * DAYS_PER_YEAR
-uranus.vz = -2.96589568540237556e-05 * DAYS_PER_YEAR
-uranus.mass = 4.36624404335156298e-05 * SOLAR_MASS
-neptune.x = 1.53796971148509165e+01
-neptune.y = -2.59193146099879641e+01
-neptune.z = 1.79258772950371181e-01
-neptune.vx = 2.68067772490389322e-03 * DAYS_PER_YEAR
-neptune.vy = 1.62824170038242295e-03 * DAYS_PER_YEAR
-neptune.vz = -9.51592254519715870e-05 * DAYS_PER_YEAR
-neptune.mass = 5.15138902046611451e-05 * SOLAR_MASS
-
-local bodies = { sun, jupiter, saturn, uranus, neptune }
-
-local function advance(bodies, nbody, dt)
-    for i = 1, nbody do
-        local bi = bodies[i]
-        local bix, biy, biz, bimass = bi.x, bi.y, bi.z, bi.mass
-        local bivx, bivy, bivz = bi.vx, bi.vy, bi.vz
-        for j = i + 1, nbody do
-            local bj = bodies[j]
-            local dx, dy, dz = bix - bj.x, biy - bj.y, biz - bj.z
-            local dist2 = dx * dx + dy * dy + dz * dz
-            local mag = sqrt(dist2)
-            mag = dt / (mag * dist2)
-            local bm = bj.mass * mag
-            bivx = bivx - (dx * bm)
-            bivy = bivy - (dy * bm)
-            bivz = bivz - (dz * bm)
-            bm = bimass * mag
-            bj.vx = bj.vx + (dx * bm)
-            bj.vy = bj.vy + (dy * bm)
-            bj.vz = bj.vz + (dz * bm)
-        end
-        bi.vx = bivx
-        bi.vy = bivy
-        bi.vz = bivz
-        bi.x = bix + dt * bivx
-        bi.y = biy + dt * bivy
-        bi.z = biz + dt * bivz
-    end
-end
-
-local function energy(bodies, nbody)
-    local e = 0
-    for i = 1, nbody do
-        local bi = bodies[i]
-        local vx, vy, vz, bim = bi.vx, bi.vy, bi.vz, bi.mass
-        e = e + (0.5 * bim * (vx * vx + vy * vy + vz * vz))
-        for j = i + 1, nbody do
-            local bj = bodies[j]
-            local dx, dy, dz = bi.x - bj.x, bi.y - bj.y, bi.z - bj.z
-            local distance = sqrt(dx * dx + dy * dy + dz * dz)
-            e = e - ((bim * bj.mass) / distance)
-        end
-    end
-    return e
+function f(n, a, ...)
+  local b
+    n, b, a = n-1, ..., a
+    assert(b == ...)
 end
 end
-
-local function offsetMomentum(b, nbody)
-    local px, py, pz = 0, 0, 0
-    for i = 1, nbody do
-        local bi = b[i]
-        local bim = bi.mass
-        px = px + (bi.vx * bim)
-        py = py + (bi.vy * bim)
-        pz = pz + (bi.vz * bim)
-    end
-    b[1].vx = -px / SOLAR_MASS
-    b[1].vy = -py / SOLAR_MASS
-    b[1].vz = -pz / SOLAR_MASS
-end
-
-local N = tonumber(arg and arg[1]) or 1000
-local nbody = #bodies
-
-offsetMomentum(bodies, nbody)
-energy(bodies, nbody)
-for i = 1, N do advance(bodies, nbody, 0.01) end
-energy(bodies, nbody)

+ 2 - 0
src/Lua.SourceGenerator/Utilities/CodeBuilder.cs

@@ -82,9 +82,11 @@ internal sealed class CodeBuilder
             {
             {
                 buffer.Append(", ");
                 buffer.Append(", ");
             }
             }
+
             buffer.Append(x);
             buffer.Append(x);
             first = false;
             first = false;
         }
         }
+
         buffer.Append(" }");
         buffer.Append(" }");
     }
     }
 
 

+ 120 - 0
src/Lua/CodeAnalysis/Compilation/Declarements.cs

@@ -0,0 +1,120 @@
+using Lua.Internal;
+using System.Runtime.CompilerServices;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+
+unsafe struct TextReader (char* Ptr, int Length)
+{
+    public int Position;
+
+    public (char, bool) Read()
+    {
+        if (Position >= Length) return ('\0', false);
+        return (Ptr[Position++], true);
+    }
+
+    public bool TryRead(out char c)
+    {
+        if (Position >= Length)
+        {
+            c = '\0';
+            return false;
+        }
+
+        c = Ptr[Position++];
+        return true;
+    }
+
+    public char Current => Ptr[Position];
+}
+
+internal  unsafe struct AssignmentTarget (ref AssignmentTarget previous, ExprDesc exprDesc)
+{
+    public readonly AssignmentTarget* Previous = (AssignmentTarget*)Unsafe.AsPointer(ref previous);
+    public  ExprDesc Description = exprDesc;
+}
+
+internal struct Label
+{
+    public string Name;
+    public int Pc, Line;
+    public int ActiveVariableCount;
+}
+
+
+internal class Block : IPoolNode<Block>
+{
+    public Block? Previous;
+    public int FirstLabel, FirstGoto;
+    public int ActiveVariableCount;
+    public bool HasUpValue, IsLoop;
+    Block(){}
+    ref Block? IPoolNode<Block>.NextNode => ref Previous;
+
+    static LinkedPool<Block> pool;
+
+    public static Block Get(Block? previous, int firstLabel, int firstGoto, int activeVariableCount, bool hasUpValue, bool isLoop)
+    {
+        if (!pool.TryPop(out var block))
+        {
+            block = new Block();
+        }
+
+        block.Previous = previous;
+        block.FirstLabel = firstLabel;
+        block.FirstGoto = firstGoto;
+        block.ActiveVariableCount = activeVariableCount;
+        block.HasUpValue = hasUpValue;
+        block.IsLoop = isLoop;
+        
+
+        return block;
+    }
+
+    public void Release()
+    {
+        Previous = null;
+        pool.TryPush(this);
+    }
+}
+
+
+internal  struct ExprDesc
+{
+    public Kind Kind;
+    public int Index;
+    public int Table;
+    public Kind TableType;
+    public int Info;
+    public int T, F;
+    public double Value;
+    public readonly bool HasJumps() => T != F;
+
+    public readonly bool IsNumeral() => Kind == Kind.Number && T == Function.NoJump && F == Function.NoJump;
+
+    public readonly bool IsVariable() => Kind.Local <= Kind && Kind <= Kind.Indexed;
+
+    public readonly bool HasMultipleReturns() => Kind == Kind.Call || Kind == Kind.VarArg;
+}
+
+
+
+internal enum Kind
+{
+    Void = 0,
+    Nil = 1,
+    True = 2,
+    False = 3,
+    Constant = 4,
+    Number = 5,
+    NonRelocatable = 6,
+    Local = 7,
+    UpValue = 8,
+    Indexed = 9,
+    Jump = 10,
+    Relocatable = 11,
+    Call = 12,
+    VarArg = 13
+}
+

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

@@ -1,36 +0,0 @@
-using Lua.Internal;
-using Lua.Runtime;
-
-namespace Lua.CodeAnalysis.Compilation
-{
-    public readonly record struct LocalVariableDescription
-    {
-        public required byte RegisterIndex { get; init; }
-        public required int StartPc { get; init; }
-    }
-
-    public readonly record struct FunctionDescription
-    {
-        public required int Index { get; init; }
-        public required int? ReturnValueCount { get; init; }
-        public required Chunk Chunk { get; init; }
-    }
-
-    public readonly record struct LabelDescription
-    {
-        public required ReadOnlyMemory<char> Name { get; init; }
-        public required int Index { get; init; }
-        public required byte RegisterIndex { get; init; }
-    }
-
-    public readonly record struct GotoDescription
-    {
-        public required ReadOnlyMemory<char> Name { get; init; }
-        public required int JumpInstructionIndex { get; init; }
-    }
-
-    public record struct BreakDescription
-    {
-        public required int Index { get; set; }
-    }
-}

+ 486 - 0
src/Lua/CodeAnalysis/Compilation/Dump.cs

@@ -0,0 +1,486 @@
+using Lua.Internal;
+using Lua.Runtime;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
+[StructLayout(LayoutKind.Sequential, Pack = 1)]
+internal unsafe struct Header
+{
+    public static ReadOnlySpan<byte> LuaSignature => "\eLua"u8;
+    public static ReadOnlySpan<byte> LuaTail => [0x19, 0x93, 0x0d, 0x0a, 0x1a, 0x0a];
+    public fixed byte Signature[4];
+    public byte Version, Format, Endianness, IntSize;
+    public byte PointerSize, InstructionSize;
+    public byte NumberSize, IntegralNumber;
+    public fixed byte Tail[6];
+
+    public const int Size = 18;
+
+    public Header(bool isLittleEndian)
+    {
+        fixed (byte* signature = this.Signature)
+        {
+            LuaSignature.CopyTo(new(signature, 4));
+        }
+
+        Version = Constants.VersionMajor << 4 | Constants.VersionMinor;
+        Format = 0;
+        Endianness = (byte)(isLittleEndian ? 1 : 0);
+        IntSize = 4;
+        PointerSize = (byte)sizeof(IntPtr);
+        InstructionSize = 4;
+        NumberSize = 8;
+        IntegralNumber = 0;
+        fixed (byte* tail = this.Tail)
+        {
+            LuaTail.CopyTo(new(tail, 6));
+        }
+    }
+
+    public void Validate(ReadOnlySpan<char> name)
+    {
+        fixed (byte* signature = this.Signature)
+        {
+            if (!LuaSignature.SequenceEqual(new(signature, 4)))
+            {
+                throw new Exception($"{name.ToString()}: is not a precompiled chunk");
+            }
+        }
+
+        var major = Version >> 4;
+        var minor = Version & 0xF;
+        if (major != Constants.VersionMajor || minor != Constants.VersionMinor)
+        {
+            throw new Exception($"{name.ToString()}: version mismatch in precompiled chunk {major}.{minor} != {Constants.VersionMajor}.{Constants.VersionMinor}");
+        }
+
+        if (IntSize != 4 || Format != 0 || IntegralNumber != 0 || PointerSize is not (4 or 8) || InstructionSize != 4 || NumberSize != 8)
+        {
+            goto ErrIncompatible;
+        }
+
+        fixed (byte* tail = this.Tail)
+        {
+            if (!LuaTail.SequenceEqual(new(tail, 6)))
+            {
+                goto ErrIncompatible;
+            }
+        }
+
+        return;
+    ErrIncompatible:
+        throw new Exception($"{name.ToString()}: incompatible precompiled chunk");
+    }
+}
+
+internal unsafe ref struct DumpState(IBufferWriter<byte> writer, bool reversedEndian)
+{
+    public readonly IBufferWriter<byte> Writer = writer;
+    Span<byte> UnWritten;
+
+    void Write(ReadOnlySpan<byte> span)
+    {
+        var toWrite = span;
+        var remaining = UnWritten.Length;
+        if (span.Length > remaining)
+        {
+            span[..remaining].CopyTo(UnWritten);
+            Writer.Advance(remaining);
+            toWrite = span[remaining..];
+            UnWritten = Writer.GetSpan(toWrite.Length);
+        }
+
+        toWrite.CopyTo(UnWritten);
+        Writer.Advance(toWrite.Length);
+        UnWritten = UnWritten[toWrite.Length..];
+    }
+
+    public bool IsReversedEndian => reversedEndian;
+
+    void DumpHeader()
+    {
+        var header = new Header(BitConverter.IsLittleEndian ^ IsReversedEndian);
+        Write(new(&header, Header.Size));
+    }
+
+    public void Dump(Prototype prototype)
+    {
+        if (UnWritten.Length == 0)
+        {
+            UnWritten = Writer.GetSpan(Header.Size + 32);
+        }
+
+        DumpHeader();
+        DumpFunction(prototype);
+    }
+
+
+    void DumpFunction(Prototype prototype)
+    {
+        WriteInt(prototype.LineDefined); //4
+        WriteInt(prototype.LastLineDefined); //4
+        WriteByte((byte)prototype.ParameterCount); //1
+        WriteByte((byte)prototype.MaxStackSize); //1
+        WriteByte((byte)(prototype.HasVariableArguments ? 1 : 0)); //1
+        WriteIntSpanWithLength(MemoryMarshal.Cast<Instruction, int>(prototype.Code)); //4
+        WriteConstants(prototype.Constants); //4
+        WritePrototypes(prototype.ChildPrototypes); //4
+        WriteUpValues(prototype.UpValues); //4
+
+        //Debug
+        WriteString(prototype.Source);
+        WriteIntSpanWithLength(prototype.LineInfo);
+        WriteLocalVariables(prototype.LocalVariables);
+        WriteInt(prototype.UpValues.Length);
+        foreach (var desc in prototype.UpValues)
+        {
+            WriteString(desc.Name);
+        }
+    }
+
+    void WriteInt(int v)
+    {
+        if (reversedEndian)
+        {
+            v = BinaryPrimitives.ReverseEndianness(v);
+        }
+
+        Write(new(&v, sizeof(int)));
+    }
+
+    void WriteLong(long v)
+    {
+        if (reversedEndian)
+        {
+            v = BinaryPrimitives.ReverseEndianness(v);
+        }
+
+        Write(new(&v, sizeof(long)));
+    }
+
+    void WriteByte(byte v)
+    {
+        Write(new(&v, sizeof(byte)));
+    }
+
+    void WriteDouble(double v)
+    {
+        long l = BitConverter.DoubleToInt64Bits(v);
+        WriteLong(l);
+    }
+
+
+    void WriteIntSpanWithLength(ReadOnlySpan<int> v)
+    {
+        WriteInt(v.Length);
+        if (IsReversedEndian)
+        {
+            foreach (var i in v)
+            {
+                var reversed = BinaryPrimitives.ReverseEndianness(i);
+                Write(new(&reversed, 4));
+            }
+        }
+        else
+        {
+            Write(MemoryMarshal.Cast<int, byte>(v));
+        }
+    }
+
+    void WriteBool(bool v)
+    {
+        WriteByte(v ? (byte)1 : (byte)0);
+    }
+
+    void WriteString(string v)
+    {
+        var bytes = Encoding.UTF8.GetBytes(v);
+        var len = bytes.Length;
+        if (bytes.Length != 0) len++;
+        if (sizeof(IntPtr) == 8) WriteLong(len);
+        else WriteInt(len);
+        if (len != 0)
+        {
+            Write(bytes);
+            WriteByte(0);
+        }
+    }
+
+    void WriteConstants(ReadOnlySpan<LuaValue> constants)
+    {
+        WriteInt(constants.Length);
+        foreach (var c in constants)
+        {
+            WriteByte((byte)c.Type);
+            switch (c.Type)
+            {
+                case LuaValueType.Nil: break;
+                case LuaValueType.Boolean:
+                    WriteBool(c.UnsafeReadDouble() != 0);
+                    break;
+                case LuaValueType.Number:
+                    WriteDouble(c.UnsafeReadDouble());
+                    break;
+                case LuaValueType.String:
+                    WriteString(c.UnsafeRead<string>()!);
+                    break;
+            }
+        }
+    }
+
+    void WritePrototypes(ReadOnlySpan<Prototype> prototypes)
+    {
+        WriteInt(prototypes.Length);
+        foreach (var p in prototypes)
+        {
+            DumpFunction(p);
+        }
+    }
+
+    void WriteLocalVariables(ReadOnlySpan<LocalVariable> localVariables)
+    {
+        WriteInt(localVariables.Length);
+        foreach (var v in localVariables)
+        {
+            WriteString(v.Name);
+            WriteInt(v.StartPc);
+            WriteInt(v.EndPc);
+        }
+    }
+
+    void WriteUpValues(ReadOnlySpan<UpValueDesc> upValues)
+    {
+        WriteInt(upValues.Length);
+        foreach (var u in upValues)
+        {
+            WriteBool(u.IsLocal);
+            WriteByte((byte)u.Index);
+        }
+    }
+}
+
+internal unsafe ref struct UnDumpState(ReadOnlySpan<byte> span,ReadOnlySpan<char> name)
+{
+    public ReadOnlySpan<byte> Unread = span;
+    bool otherEndian;
+    int pointerSize;
+    readonly ReadOnlySpan<char> name = name;
+
+    void Throw(string why) => throw new Exception($"{name.ToString()}: {why} precompiled chunk");
+    void ThrowTooShort() => Throw("truncate");
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void Read(Span<byte> dst)
+    {
+        if (Unread.Length < dst.Length) ThrowTooShort();
+        Unread[..dst.Length].CopyTo(dst);
+
+        Unread = Unread[dst.Length..];
+    }
+
+    byte ReadByte()
+    {
+        if (0 < Unread.Length)
+        {
+            var b = Unread[0];
+            Unread = Unread[1..];
+            return b;
+        }
+
+        ThrowTooShort();
+        return 0;
+    }
+
+    bool ReadBool()
+    {
+        if (0 < Unread.Length)
+        {
+            var b = Unread[0];
+            Unread = Unread[1..];
+            return b != 0;
+        }
+
+        ThrowTooShort();
+
+        return false;
+    }
+
+    int ReadInt()
+    {
+        var i = 0;
+        var span = new Span<byte>(&i, sizeof(int));
+        Read(span);
+
+        if (otherEndian) i = BinaryPrimitives.ReverseEndianness(i);
+        
+        return i;
+    }
+
+    long ReadLong()
+    {
+        long i = 0;
+        var span = new Span<byte>(&i, sizeof(long));
+        Read(span);
+
+        if (otherEndian) i = BinaryPrimitives.ReverseEndianness(i);
+        return i;
+    }
+
+    double ReadDouble()
+    {
+        long i = ReadLong();
+
+        return *(double*)(&i);
+    }
+
+
+    public Prototype UnDump()
+    {
+        Header h = default;
+        var span = new Span<byte>(&h, sizeof(Header));
+        Read(span);
+
+        h.Validate(name);
+        otherEndian = BitConverter.IsLittleEndian ^ (h.Endianness == 1);
+        pointerSize = h.PointerSize;
+        return UnDumpFunction();
+    }
+
+
+    Prototype UnDumpFunction()
+    {
+        var lineDefined = ReadInt(); //4
+        var lastLineDefined = ReadInt(); //4
+        var parameterCount = ReadByte(); //1
+        var maxStackSize = ReadByte(); //1
+        var isVarArg = ReadByte() == 1; //1
+        var codeLength = ReadInt();
+        var code = new Instruction[codeLength];
+        ReadInToIntSpan(MemoryMarshal.Cast<Instruction, int>(code));
+        var constants = ReadConstants();
+        var prototypes = ReadPrototypes();
+        var upValues = ReadUpValues();
+
+        //Debug
+        var source = ReadString();
+        var lineInfoLength = ReadInt();
+        var lineInfo = new int[lineInfoLength];
+        ReadInToIntSpan(lineInfo.AsSpan());
+        var localVariables = ReadLocalVariables();
+
+        foreach (ref var desc in upValues.AsSpan())
+        {
+            var name = ReadString();
+            desc.Name = name;
+        }
+
+        return new Prototype(source, lineDefined, lastLineDefined, parameterCount, maxStackSize, isVarArg, constants, code, prototypes, lineInfo, localVariables, upValues);
+    }
+
+
+    void ReadInToIntSpan(Span<int> toWrite)
+    {
+        for (int i = 0; i < toWrite.Length; i++)
+        {
+            toWrite[i] = ReadInt();
+        }
+    }
+
+
+    string ReadString()
+    {
+        var len = pointerSize == 4 ? ReadInt() : (int)ReadLong();
+        if (len == 0) return "";
+        len--;
+        var arrayPooled = ArrayPool<byte>.Shared.Rent(len);
+        try
+        {
+            var span = arrayPooled.AsSpan(0, len);
+            Read(span);
+
+            var l = ReadByte();
+            Debug.Assert(l == 0);
+            var str = Encoding.UTF8.GetString(span);
+            return str;
+        }
+        finally
+        {
+            ArrayPool<byte>.Shared.Return(arrayPooled);
+        }
+    }
+
+    LuaValue[] ReadConstants()
+    {
+        var count = ReadInt();
+        var constants = new LuaValue[count];
+        for (int i = 0; i < count; i++)
+        {
+            var type = (LuaValueType)ReadByte();
+            switch (type)
+            {
+                case LuaValueType.Nil: break;
+                case LuaValueType.Boolean:
+                    constants[i] = (ReadByte() == 1);
+                    break;
+                case LuaValueType.Number:
+                    constants[i] = (ReadDouble());
+                    break;
+                case LuaValueType.String:
+                    constants[i] = (ReadString());
+                    break;
+            }
+        }
+
+        return constants;
+    }
+
+    Prototype[] ReadPrototypes()
+    {
+        var count = ReadInt();
+        var prototypes = count != 0 ? new Prototype[count] : [];
+        for (int i = 0; i < count; i++)
+        {
+            prototypes[i] = UnDumpFunction();
+        }
+
+        return prototypes;
+    }
+
+    LocalVariable[] ReadLocalVariables()
+    {
+        var count = ReadInt();
+        var localVariables = new LocalVariable[count];
+        for (int i = 0; i < count; i++)
+        {
+            var name = ReadString();
+            var startPc = ReadInt();
+            var endPc = ReadInt();
+            localVariables[i] = new LocalVariable() { Name = name, StartPc = startPc, EndPc = endPc };
+        }
+
+        return localVariables;
+    }
+
+    UpValueDesc[] ReadUpValues()
+    {
+        var count = ReadInt();
+        Debug.Assert(count < 100, $" too many upvalues :{count}");
+        var upValues = new UpValueDesc[count];
+        for (int i = 0; i < count; i++)
+        {
+            var isLocal = ReadBool();
+            var index = ReadByte();
+            upValues[i] = new UpValueDesc() { IsLocal = isLocal, Index = index };
+        }
+
+        return upValues;
+    }
+}

+ 1616 - 0
src/Lua/CodeAnalysis/Compilation/Function.cs

@@ -0,0 +1,1616 @@
+using Lua.Runtime;
+using System.Diagnostics;
+using Constants = Lua.Internal.Constants;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+using static Debug;
+using static Lua.Runtime.Instruction;
+using Internal;
+using static Constants;
+
+internal class Function : IPoolNode<Function>
+{
+    public readonly Dictionary<LuaValue, int> ConstantLookup = new();
+    public PrototypeBuilder Proto = null!;
+    public Function? Previous;
+    public Parser P = null!;
+    public Block Block = null!;
+    public int JumpPc = NoJump, LastTarget;
+    public int FreeRegisterCount;
+    public int ActiveVariableCount;
+    public int FirstLocal;
+    internal Function() { }
+
+    static LinkedPool<Function> pool;
+
+    ref Function? IPoolNode<Function>.NextNode => ref Previous;
+
+    internal static Function Get(Parser p, PrototypeBuilder proto)
+    {
+        if (!pool.TryPop(out var f))
+        {
+            f = new Function();
+        }
+
+        f.P = p;
+        f.Proto = proto;
+        return f;
+    }
+
+    internal void Release()
+    {
+        Previous = null;
+        ConstantLookup.Clear();
+        JumpPc = NoJump;
+        Proto = null!;
+        LastTarget = 0;
+        FreeRegisterCount = 0;
+        ActiveVariableCount = 0;
+        FirstLocal = 0;
+        pool.TryPush(this);
+    }
+
+
+    public const int OprMinus = 0;
+
+    public const int OprNot = 1;
+
+    public const int OprLength = 2;
+
+    public const int OprNoUnary = 3;
+    public const int NoJump = -1;
+
+    public const int NoRegister = MaxArgA;
+
+    public const int MaxLocalVariables = 200;
+
+
+    public const int OprAdd = 0;
+
+    public const int OprSub = 1;
+
+    public const int OprMul = 2;
+
+    public const int OprDiv = 3;
+
+    public const int OprMod = 4;
+
+    public const int OprPow = 5;
+
+    public const int OprConcat = 6;
+
+    public const int OprEq = 7;
+
+    public const int OprLT = 8;
+
+    public const int OprLE = 9;
+
+    public const int OprNE = 10;
+
+    public const int OprGT = 11;
+
+    public const int OprGE = 12;
+
+    public const int OprAnd = 13;
+
+    public const int OprOr = 14;
+
+    public const int OprNoBinary = 15;
+
+
+    public void OpenFunction(int line)
+    {
+        var newProto = PrototypeBuilder.Get(P.Scanner.Source);
+        newProto.Source = P.Scanner.Source;
+        newProto.MaxStackSize = 2;
+        newProto.LineDefined = line;
+
+        Proto.PrototypeList.Add(newProto);
+        var f = Get(P, Proto.PrototypeList[^1]);
+        f.Previous = this;
+        f.FirstLocal = P.ActiveVariables.Length;
+        P.Function = f;
+
+        P.Function.EnterBlock(false);
+    }
+
+
+    public ExprDesc CloseFunction()
+    {
+        var e = P.Function!.Previous!.ExpressionToNextRegister(MakeExpression(Kind.Relocatable, Previous!.EncodeABx(OpCode.Closure, 0, Previous!.Proto.PrototypeList.Length - 1)));
+        P.Function.ReturnNone();
+        P.Function.LeaveBlock();
+        Assert(P.Function.Block == null);
+        var f = P.Function;
+        P.Function = f.Previous;
+        f.Release();
+        return e;
+    }
+
+
+    public void EnterBlock(bool isLoop)
+    {
+        var b = Block.Get(Block, P.ActiveLabels.Length, P.PendingGotos.Length, ActiveVariableCount, false, isLoop);
+        Block = b;
+        Assert(FreeRegisterCount == ActiveVariableCount);
+    }
+
+
+    public void UndefinedGotoError(Label g)
+    {
+        if (Scanner.IsReserved(g.Name))
+        {
+            SemanticError($"<{g.Name}> at line {g.Line} not inside a loop");
+        }
+        else
+        {
+            SemanticError($"no visible label '{g.Name}' for <goto> at line {g.Line}");
+        }
+    }
+
+
+    public ref LocalVariable LocalVariable(int i)
+    {
+        var index = P.ActiveVariables[FirstLocal + i];
+        return ref Proto.LocalVariablesList[index];
+    }
+
+
+    public void AdjustLocalVariables(int n)
+    {
+        for (ActiveVariableCount += n; n != 0; n--)
+        {
+            LocalVariable(ActiveVariableCount - n).StartPc = ((Proto.CodeList.Length));
+        }
+    }
+
+
+    public void RemoveLocalVariables(int level)
+    {
+        for (var i = level; i < ActiveVariableCount; i++)
+        {
+            LocalVariable(i).EndPc = ((Proto.CodeList.Length));
+        }
+
+        P.ActiveVariables.Shrink((P.ActiveVariables.Length - (ActiveVariableCount - level)));
+        ActiveVariableCount = level;
+    }
+
+
+    public void MakeLocalVariable(string name)
+    {
+        var r = Proto.LocalVariablesList.Length;
+        Proto.LocalVariablesList.Add(new() { Name = name });
+        P.CheckLimit((P.ActiveVariables.Length + 1 - FirstLocal), MaxLocalVariables, "local variables");
+        P.ActiveVariables.Add(r);
+    }
+
+
+    public void MakeGoto(string name, int line, int pc)
+    {
+        P.PendingGotos.Add(new() { Name = name, Line = line, Pc = pc, ActiveVariableCount = ActiveVariableCount });
+        FindLabel((P.PendingGotos.Length - 1));
+    }
+
+
+    public int MakeLabel(string name, int line)
+    {
+        P.ActiveLabels.Add(new() { Name = name, Line = line, Pc = Proto.CodeList.Length, ActiveVariableCount = ActiveVariableCount });
+        return (P.ActiveLabels.Length - 1);
+    }
+
+
+    public void CloseGoto(int i, Label l)
+    {
+        var g = P.PendingGotos[i];
+        Assert(g.Name == l.Name);
+        if (g.ActiveVariableCount < l.ActiveVariableCount)
+        {
+            SemanticError($"<goto {g.Name}> at line {g.Line} jumps into the scope of local '{LocalVariable(g.ActiveVariableCount).Name}'");
+        }
+
+        PatchList(g.Pc, l.Pc);
+        P.PendingGotos.RemoveAtSwapBack(i);
+    }
+
+
+    public int FindLabel(int i)
+    {
+        var g = P.PendingGotos[i];
+        var b = Block;
+        foreach (var l in P.ActiveLabels.AsSpan().Slice(b.FirstLabel))
+        {
+            if (l.Name == g.Name)
+            {
+                if (g.ActiveVariableCount > l.ActiveVariableCount && (b.HasUpValue || P.ActiveLabels.Length > b.FirstLabel))
+                {
+                    PatchClose(g.Pc, l.ActiveVariableCount);
+                }
+
+                CloseGoto(i, l);
+                return 0;
+            }
+        }
+
+        return 1;
+    }
+
+
+    public void CheckRepeatedLabel(string name)
+    {
+        foreach (var l in P.ActiveLabels.AsSpan().Slice(Block!.FirstLabel))
+        {
+            if (l.Name == name)
+            {
+                SemanticError($"label '{name}' already defined on line {l.Line}");
+            }
+        }
+    }
+
+
+    public void FindGotos(int label)
+    {
+        for (var i = Block!.FirstGoto; i < P.PendingGotos.Length;)
+        {
+            var l = P.ActiveLabels[label];
+            if (P.PendingGotos[i].Name == l.Name)
+            {
+                CloseGoto(i, l);
+            }
+            else
+            {
+                i++;
+            }
+        }
+    }
+
+    public void MoveGotosOut(Block b)
+    {
+        for (var i = b.FirstGoto; i < P.PendingGotos.Length; i += FindLabel(i))
+        {
+            if (P.PendingGotos[i].ActiveVariableCount > b.ActiveVariableCount)
+            {
+                if (b.HasUpValue)
+                {
+                    PatchClose(P.PendingGotos[i].Pc, b.ActiveVariableCount);
+                }
+
+                P.PendingGotos[i].ActiveVariableCount = b.ActiveVariableCount;
+            }
+        }
+    }
+
+
+    public void LeaveBlock()
+    {
+        var b = Block;
+        if (b.Previous != null && b.HasUpValue) // create a 'jump to here' to close upvalues
+        {
+            var j = Jump();
+            PatchClose(j, b.ActiveVariableCount);
+            PatchToHere(j);
+        }
+
+        if (b.IsLoop)
+        {
+            BreakLabel();
+        }
+
+        Block = b.Previous!;
+        RemoveLocalVariables(b.ActiveVariableCount);
+        Assert(b.ActiveVariableCount == ActiveVariableCount);
+        FreeRegisterCount = ActiveVariableCount;
+        P.ActiveLabels.Shrink(b.FirstLabel);
+        if (b.Previous != null) // inner block
+        {
+            MoveGotosOut(b); // update pending gotos to outer block
+        }
+        else if (b.FirstGoto < P.PendingGotos.Length) // pending gotos in outer block
+        {
+            UndefinedGotoError(P.PendingGotos[b.FirstGoto]);
+        }
+
+        b.Release();
+    }
+
+
+    public static int Not(int b) => b == 0 ? 1 : 0;
+
+
+    public static ExprDesc MakeExpression(Kind kind, int info) => new() { F = NoJump, T = NoJump, Kind = kind, Info = info };
+
+
+    public void SemanticError(string message)
+    {
+        P.Scanner.Token.T = default;
+        P.Scanner.SyntaxError(message);
+    }
+
+
+    public void BreakLabel() => FindGotos(MakeLabel("break", 0));
+
+    [Conditional("DEBUG")]
+    public void Unreachable() => Assert(false);
+
+
+    public ref Instruction Instruction(ExprDesc e) => ref Proto.CodeList[e.Info];
+
+
+    [Conditional("DEBUG")]
+    public void AssertEqual(int a, int b) => Assert(a == b, $"{a} != {b}");
+
+
+    public int Encode(Instruction i)
+    {
+        Assert(Proto.CodeList.Length == Proto.LineInfoList.Length);
+        DischargeJumpPc();
+        Proto.CodeList.Add(i);
+        Proto.LineInfoList.Add(P.Scanner.LastLine);
+        return Proto.CodeList.Length - 1;
+    }
+
+    public void DropLastInstruction()
+    {
+        Assert(Proto.CodeList.Length == Proto.LineInfoList.Length);
+        Proto.CodeList.Pop();
+        Proto.LineInfoList.Pop();
+    }
+
+
+    public int EncodeABC(OpCode op, int a, int b, int c)
+    {
+        return Encode(CreateABC(op, a, b, c));
+    }
+
+
+    public int EncodeABx(OpCode op, int a, int bx) => Encode(CreateABx(op, a, bx));
+
+
+    public int EncodeAsBx(OpCode op, int a, int sbx) => EncodeABx(op, a, sbx + MaxArgSBx);
+
+    public int EncodeExtraArg(int a) => Encode(CreateAx(OpCode.ExtraArg, a));
+
+
+    public int EncodeConstant(int r, int constant)
+    {
+        if (constant <= MaxArgBx)
+            return EncodeABx(OpCode.LoadK, r, constant);
+        else
+        {
+            var pc = EncodeABx(OpCode.LoadK, r, 0);
+            EncodeExtraArg(constant);
+            return pc;
+        }
+    }
+
+
+    public ExprDesc EncodeString(string s) => MakeExpression(Kind.Constant, StringConstant(s));
+
+
+    public void LoadNil(int from, int n)
+    {
+        if (Proto.CodeList.Length > LastTarget) // no jumps to current position
+        {
+            ref var previous = ref Proto.CodeList[^1];
+            if (previous.OpCode == OpCode.LoadNil)
+            {
+                var pf = previous.A;
+                var pl = previous.A + previous.B;
+                var l = from + n - 1;
+                if (pf <= from && from <= pl + 1 || from <= pf && pf <= l + 1) // can connect both
+                {
+                    from = Math.Min(from, pf);
+                    l = Math.Max(l, pl);
+                    previous.A = from;
+                    previous.B = l - from;
+                    return;
+                }
+            }
+        }
+
+        EncodeABC(OpCode.LoadNil, from, n - 1, 0);
+    }
+
+
+    public int Jump()
+    {
+        Assert(IsJumpListWalkable(JumpPc));
+        var jumpPc = JumpPc;
+        JumpPc = NoJump;
+        return Concatenate(EncodeAsBx(OpCode.Jmp, 0, NoJump), jumpPc);
+    }
+
+    public void JumpTo(int target)
+    {
+        PatchList(Jump(), target);
+    }
+
+    public void ReturnNone()
+    {
+        EncodeABC(OpCode.Return, 0, 1, 0);
+    }
+
+    public void SetMultipleReturns(ExprDesc e)
+    {
+        SetReturns(e, MultipleReturns);
+    }
+
+
+    public void Return(ExprDesc e, int resultCount)
+    {
+        if (e.HasMultipleReturns())
+        {
+            SetMultipleReturns(e);
+            if (e.Kind == Kind.Call && resultCount == 1)
+            {
+                Instruction(e).OpCode = OpCode.TailCall;
+                Assert(Instruction(e).A == ActiveVariableCount);
+            }
+
+            EncodeABC(OpCode.Return, ActiveVariableCount, MultipleReturns + 1, 0);
+        }
+        else if (resultCount == 1)
+            EncodeABC(OpCode.Return, ExpressionToAnyRegister(e).Info, 1 + 1, 0);
+        else
+        {
+            ExpressionToNextRegister(e);
+            Assert(resultCount == FreeRegisterCount - ActiveVariableCount);
+            EncodeABC(OpCode.Return, ActiveVariableCount, resultCount + 1, 0);
+        }
+    }
+
+
+    public int ConditionalJump(OpCode op, int a, int b, int c)
+    {
+        EncodeABC(op, a, b, c);
+        return Jump();
+    }
+
+
+    public void FixJump(int pc, int dest)
+    {
+        Assert(IsJumpListWalkable(pc));
+        Assert(dest != NoJump);
+        var offset = dest - (pc + 1);
+        if (Math.Abs(offset) > MaxArgSBx)
+            P.Scanner.SyntaxError("control structure too long");
+        Proto.CodeList[pc].SBx = (offset);
+    }
+
+    public int Label()
+    {
+        LastTarget = Proto.CodeList.Length;
+        return LastTarget;
+    }
+
+
+    public int Jump(int pc)
+    {
+        Assert(IsJumpListWalkable(pc));
+        var offset = Proto.CodeList[pc].SBx;
+        if (offset != NoJump)
+            return pc + 1 + offset;
+        return NoJump;
+    }
+
+
+    public bool IsJumpListWalkable(int list)
+    {
+        if (list == NoJump)
+            return true;
+        if (list < 0 || list >= Proto.CodeList.Length)
+            return false;
+        var offset = Proto.CodeList[list].SBx;
+        return offset == NoJump || IsJumpListWalkable(list + 1 + offset);
+    }
+
+
+    public ref Instruction JumpControl(int pc)
+    {
+        if (pc >= 1 && TestTMode(Proto.CodeList[pc - 1].OpCode))
+            return ref Proto.CodeList[pc - 1];
+        return ref Proto.CodeList[pc];
+    }
+
+
+    public bool NeedValue(int list)
+    {
+        Assert(IsJumpListWalkable(list));
+        for (; list != NoJump; list = Jump(list))
+        {
+            if (JumpControl(list).OpCode != OpCode.TestSet)
+                return true;
+        }
+
+        return false;
+    }
+
+
+    public bool PatchTestRegister(int node, int register)
+    {
+        ref var i = ref JumpControl(node);
+        if (i.OpCode != OpCode.TestSet)
+            return false;
+        if (register != NoRegister && register != i.B)
+            i.A = register;
+        else
+            i = CreateABC(OpCode.Test, i.B, 0, i.C);
+        return true;
+    }
+
+    public void RemoveValues(int list)
+    {
+        Assert(IsJumpListWalkable(list));
+        for (; list != NoJump; list = Jump(list))
+        {
+            PatchTestRegister(list, NoRegister);
+        }
+    }
+
+
+    public void PatchListHelper(int list, int target, int register, int defaultTarget)
+    {
+        Assert(IsJumpListWalkable(list));
+
+        while (list != NoJump)
+        {
+            var next = Jump(list);
+            if (PatchTestRegister(list, register))
+            {
+                FixJump(list, target);
+            }
+            else
+            {
+                FixJump(list, defaultTarget);
+            }
+
+            list = next;
+        }
+    }
+
+    public void DischargeJumpPc()
+    {
+        Assert(IsJumpListWalkable(JumpPc));
+        PatchListHelper(JumpPc, Proto.CodeList.Length, NoRegister, Proto.CodeList.Length);
+        JumpPc = NoJump;
+    }
+
+    public void PatchList(int list, int target)
+    {
+        if (target == Proto.CodeList.Length)
+        {
+            PatchToHere(list);
+        }
+        else
+        {
+            Assert(target < Proto.CodeList.Length);
+            PatchListHelper(list, target, NoRegister, target);
+        }
+    }
+
+    public void PatchClose(int list, int level)
+    {
+        Assert(IsJumpListWalkable(list));
+        level++;
+        for (int next; list != NoJump; list = next)
+        {
+            next = Jump(list);
+            Assert(Proto.CodeList[list].OpCode == OpCode.Jmp && Proto.CodeList[list].A == 0 || Proto.CodeList[list].A >= level);
+            Proto.CodeList[list].A = level;
+        }
+    }
+
+
+    public void PatchToHere(int list)
+    {
+        Assert(IsJumpListWalkable(list));
+        Assert(IsJumpListWalkable(JumpPc));
+        Label();
+        JumpPc = Concatenate(JumpPc, list);
+        Assert(IsJumpListWalkable(JumpPc));
+    }
+
+    public int Concatenate(int l1, int l2)
+    {
+        Assert(IsJumpListWalkable(l1));
+
+        if (l2 == NoJump) return l1;
+        if (l1 == NoJump)
+        {
+            return l2;
+        }
+
+        var list = l1;
+        for (var next = Jump(list); next != NoJump;)
+        {
+            (list, next) = (next, Jump(next));
+        }
+
+        FixJump(list, l2);
+        return l1;
+    }
+
+    public int AddConstant(LuaValue k, LuaValue v)
+    {
+        if (ConstantLookup.TryGetValue(k, out var index) && Proto.ConstantsList[index] == v)
+        {
+            return index;
+        }
+
+        index = Proto.ConstantsList.Length;
+        ConstantLookup[k] = index;
+        Proto.ConstantsList.Add(v);
+        return index;
+    }
+
+
+    public unsafe int NumberConstant(double n)
+    {
+        if (n == 0.0 || double.IsNaN(n))
+        {
+            return AddConstant(*(long*)&n, n);
+        }
+
+        return AddConstant(n, n);
+    }
+
+    public void CheckStack(int n)
+    {
+        n += FreeRegisterCount;
+        if (n >= MaxStack)
+        {
+            P.Scanner.SyntaxError("function or expression too complex");
+        }
+        else if (n > Proto.MaxStackSize)
+        {
+            Proto.MaxStackSize = n;
+        }
+    }
+
+
+    public void ReserveRegisters(int n)
+    {
+        CheckStack(n);
+        FreeRegisterCount += n;
+    }
+
+
+    public void FreeRegister(int r)
+    {
+        if (!IsConstant(r) && r >= ActiveVariableCount)
+        {
+            FreeRegisterCount--;
+            AssertEqual(r, FreeRegisterCount);
+        }
+    }
+
+    public void FreeExpression(ExprDesc e)
+    {
+        if (e.Kind == Kind.NonRelocatable)
+        {
+            FreeRegister(e.Info);
+        }
+    }
+
+
+    public int StringConstant(string s)
+    {
+        return AddConstant(s, s);
+    }
+
+    public int BooleanConstant(bool b)
+    {
+        return AddConstant(b, b);
+    }
+
+    public int NilConstant()
+    {
+        return AddConstant(default, default);
+    }
+
+    public void SetReturns(ExprDesc e, int resultCount)
+    {
+        if (e.Kind == Kind.Call)
+        {
+            Instruction(e).C = resultCount + 1;
+        }
+        else if (e.Kind == Kind.VarArg)
+        {
+            Instruction(e).B = resultCount + 1;
+            Instruction(e).A = FreeRegisterCount;
+            ReserveRegisters(1);
+        }
+    }
+
+    public ExprDesc SetReturn(ExprDesc e)
+    {
+        if (e.Kind == Kind.Call)
+        {
+            e.Kind = Kind.NonRelocatable;
+            e.Info = Instruction(e).A;
+        }
+        else if (e.Kind == Kind.VarArg)
+        {
+            Instruction(e).B = 2;
+            e.Kind = Kind.Relocatable;
+        }
+
+        return e;
+    }
+
+    public ExprDesc DischargeVariables(ExprDesc e)
+    {
+        switch (e.Kind)
+        {
+            case Kind.Local:
+                e.Kind = Kind.NonRelocatable;
+                break;
+            case Kind.UpValue:
+                e.Kind = Kind.Relocatable;
+                e.Info = EncodeABC(OpCode.GetUpVal, 0, e.Info, 0);
+                break;
+            case Kind.Indexed:
+                FreeRegister(e.Index);
+            {
+                if (e.TableType == Kind.Local)
+                {
+                    FreeRegister(e.Table);
+                    e.Kind = Kind.Relocatable;
+                    e.Info = EncodeABC(OpCode.GetTable, 0, e.Table, e.Index);
+                }
+                else
+                {
+                    e.Kind = Kind.Relocatable;
+                    e.Info = EncodeABC(OpCode.GetTabUp, 0, e.Table, e.Index);
+                }
+            }
+                break;
+            case Kind.VarArg:
+            case Kind.Call:
+                e = SetReturn(e);
+                break;
+        }
+
+        return e;
+    }
+
+    public ExprDesc DischargeToRegister(ExprDesc e, int r)
+    {
+        e = DischargeVariables(e);
+        switch (e.Kind)
+        {
+            case Kind.Nil:
+                LoadNil(r, 1);
+                break;
+            case Kind.False:
+                EncodeABC(OpCode.LoadBool, r, 0, 0);
+                break;
+            case Kind.True:
+                EncodeABC(OpCode.LoadBool, r, 1, 0);
+                break;
+            case Kind.Constant:
+                EncodeConstant(r, e.Info);
+                break;
+            case Kind.Number:
+                EncodeConstant(r, NumberConstant(e.Value));
+                break;
+            case Kind.Relocatable:
+                Instruction(e).A = r;
+                break;
+            case Kind.NonRelocatable:
+                if (r != e.Info)
+                {
+                    EncodeABC(OpCode.Move, r, e.Info, 0);
+                }
+
+                break;
+            default:
+                Assert(e.Kind == Kind.Void || e.Kind == Kind.Jump);
+                return e;
+        }
+
+        e.Kind = Kind.NonRelocatable;
+        e.Info = r;
+        return e;
+    }
+
+
+    public ExprDesc DischargeToAnyRegister(ExprDesc e)
+    {
+        if (e.Kind != Kind.NonRelocatable)
+        {
+            ReserveRegisters(1);
+            e = DischargeToRegister(e, FreeRegisterCount - 1);
+        }
+
+        return e;
+    }
+
+
+    public int EncodeLabel(int a, int b, int jump)
+    {
+        Label();
+        return EncodeABC(OpCode.LoadBool, a, b, jump);
+    }
+
+
+    public ExprDesc ExpressionToRegister(ExprDesc e, int r)
+    {
+        e = DischargeToRegister(e, r);
+        if (e.Kind == Kind.Jump)
+        {
+            e.T = Concatenate(e.T, e.Info);
+        }
+
+        if (e.HasJumps())
+        {
+            int loadFalse = NoJump;
+            int loadTrue = NoJump;
+            if (NeedValue(e.T) || NeedValue(e.F))
+            {
+                int jump = NoJump;
+                if (e.Kind != Kind.Jump) jump = Jump();
+                loadFalse = EncodeLabel(r, 0, 1);
+                loadTrue = EncodeLabel(r, 1, 0);
+                PatchToHere(jump);
+            }
+
+            int end = Label();
+            PatchListHelper(e.F, end, r, loadFalse);
+            PatchListHelper(e.T, end, r, loadTrue);
+        }
+
+        e.F = e.T = NoJump;
+        e.Info = r;
+        e.Kind = Kind.NonRelocatable;
+        return e;
+    }
+
+
+    public ExprDesc ExpressionToNextRegister(ExprDesc e)
+    {
+        e = DischargeVariables(e);
+        FreeExpression(e);
+        ReserveRegisters(1);
+        return ExpressionToRegister(e, FreeRegisterCount - 1);
+    }
+
+
+    public ExprDesc ExpressionToAnyRegister(ExprDesc e)
+    {
+        e = DischargeVariables(e);
+        if (e.Kind == Kind.NonRelocatable)
+        {
+            if (!e.HasJumps())
+                return e;
+            if (e.Info >= ActiveVariableCount)
+            {
+                return ExpressionToRegister(e, e.Info);
+            }
+        }
+
+        return ExpressionToNextRegister(e);
+    }
+
+
+    public ExprDesc ExpressionToAnyRegisterOrUpValue(ExprDesc e)
+    {
+        if (e.Kind != Kind.UpValue || e.HasJumps())
+        {
+            e = ExpressionToAnyRegister(e);
+        }
+
+        return e;
+    }
+
+
+    public ExprDesc ExpressionToValue(ExprDesc e)
+    {
+        if (e.HasJumps()) return ExpressionToAnyRegister(e);
+        return DischargeVariables(e);
+    }
+
+
+    public (ExprDesc, int) ExpressionToRegisterOrConstant(ExprDesc e)
+    {
+        e = ExpressionToValue(e);
+        switch (e.Kind)
+        {
+            case Kind.True:
+            case Kind.False:
+                if (Proto.ConstantsList.Length <= MaxIndexRK)
+                {
+                    e.Info = BooleanConstant(e.Kind == Kind.True);
+                    e.Kind = Kind.Constant;
+                    return (e, AsConstant(e.Info));
+                }
+
+                break;
+            case Kind.Nil:
+                if (Proto.ConstantsList.Length <= MaxIndexRK)
+                {
+                    e.Info = NilConstant();
+                    e.Kind = Kind.Constant;
+                    return (e, AsConstant(e.Info));
+                }
+
+                break;
+            case Kind.Number:
+                e.Info = NumberConstant(e.Value);
+                e.Kind = Kind.Constant;
+                goto case Kind.Constant;
+            case Kind.Constant:
+                if (e.Info <= MaxIndexRK)
+                {
+                    return (e, AsConstant(e.Info));
+                }
+
+                break;
+        }
+
+        e = ExpressionToAnyRegister(e);
+        return (e, e.Info);
+    }
+
+
+    public void StoreVariable(ExprDesc v, ExprDesc e)
+    {
+        switch (v.Kind)
+        {
+            case Kind.Local:
+                FreeExpression(e);
+                ExpressionToRegister(e, v.Info);
+                return;
+            case Kind.UpValue:
+                e = ExpressionToAnyRegister(e);
+                EncodeABC(OpCode.SetUpVal, e.Info, v.Info, 0);
+                break;
+            case Kind.Indexed:
+                var r = 0;
+                (e, r) = ExpressionToRegisterOrConstant(e);
+                EncodeABC(v.TableType == Kind.Local ? OpCode.SetTable : OpCode.SetTabUp, v.Table, v.Index, r);
+
+                break;
+            default:
+                Unreachable();
+                break;
+        }
+
+        FreeExpression(e);
+    }
+
+
+    public ExprDesc Self(ExprDesc e, ExprDesc key)
+    {
+        e = ExpressionToAnyRegister(e);
+        var r = e.Info;
+        FreeExpression(e);
+        var result = new ExprDesc { Info = FreeRegisterCount, Kind = Kind.NonRelocatable }; // base register for opSelf
+        ReserveRegisters(2); // function and 'self' produced by opSelf
+        (key, var k) = ExpressionToRegisterOrConstant(key);
+        EncodeABC(OpCode.Self, result.Info, r, k);
+        FreeExpression(key);
+        return result;
+    }
+
+
+    public void InvertJump(int pc)
+    {
+        ref var i = ref JumpControl(pc);
+        Assert(TestTMode(i.OpCode) && i.OpCode is not (OpCode.TestSet or OpCode.Test));
+        i.A = Not(i.A);
+    }
+
+
+    public int JumpOnCondition(ExprDesc e, int cond)
+    {
+        if (e.Kind == Kind.Relocatable)
+        {
+            var i = Instruction(e);
+            if (i.OpCode == OpCode.Not)
+            {
+                DropLastInstruction(); // remove previous opNot
+                return ConditionalJump(OpCode.Test, i.B, 0, Not(cond));
+            }
+        }
+
+        e = DischargeToAnyRegister(e);
+        FreeExpression(e);
+        return ConditionalJump(OpCode.TestSet, NoRegister, e.Info, cond);
+    }
+
+
+    public ExprDesc GoIfTrue(ExprDesc e)
+    {
+        var pc = NoJump;
+        e = DischargeVariables(e);
+        switch (e.Kind)
+        {
+            case Kind.Jump:
+                InvertJump(e.Info);
+                pc = e.Info;
+                break;
+            case Kind.Constant:
+            case Kind.Number:
+            case Kind.True:
+                break;
+            default:
+                pc = JumpOnCondition(e, 0);
+                break;
+        }
+
+        e.F = Concatenate(e.F, pc);
+        PatchToHere(e.T);
+        e.T = NoJump;
+        return e;
+    }
+
+
+    public ExprDesc GoIfFalse(ExprDesc e)
+    {
+        var pc = NoJump;
+        e = DischargeVariables(e);
+        switch (e.Kind)
+        {
+            case Kind.Jump:
+                pc = e.Info;
+                break;
+            case Kind.Nil:
+            case Kind.False:
+                break;
+            default:
+                pc = JumpOnCondition(e, 1);
+                break;
+        }
+
+        e.T = Concatenate(e.T, pc);
+        PatchToHere(e.F);
+        e.F = NoJump;
+        return e;
+    }
+
+
+    public ExprDesc EncodeNot(ExprDesc e)
+    {
+        e = DischargeVariables(e);
+        switch (e.Kind)
+        {
+            case Kind.Nil:
+            case Kind.False:
+                e.Kind = Kind.True;
+                break;
+            case Kind.Constant:
+            case Kind.Number:
+            case Kind.True:
+                e.Kind = Kind.False;
+                break;
+            case Kind.Jump:
+                InvertJump(e.Info);
+                break;
+            case Kind.Relocatable:
+            case Kind.NonRelocatable:
+                e = DischargeToAnyRegister(e);
+                FreeExpression(e);
+                e.Info = EncodeABC(OpCode.Not, 0, e.Info, 0);
+                e.Kind = Kind.Relocatable;
+                break;
+            default:
+                Unreachable();
+                break;
+        }
+
+        (e.T, e.F) = (e.F, e.T);
+        RemoveValues(e.F);
+        RemoveValues(e.T);
+        return e;
+    }
+
+
+    public ExprDesc Indexed(ExprDesc t, ExprDesc k)
+    {
+        Assert(!t.HasJumps());
+        var r = MakeExpression(Kind.Indexed, 0);
+        r.Table = t.Info;
+        var (_, i) = ExpressionToRegisterOrConstant(k);
+        r.Index = i;
+        if (t.Kind == Kind.UpValue)
+            r.TableType = Kind.UpValue;
+        else
+        {
+            Assert(t.Kind == Kind.NonRelocatable || t.Kind == Kind.Local);
+            r.TableType = Kind.Local;
+        }
+
+        return r;
+    }
+
+
+    private static double Arith(OpCode op, double v1, double v2)
+    {
+        switch (op)
+        {
+            case OpCode.Add:
+                return v1 + v2;
+            case OpCode.Sub:
+                return v1 - v2;
+            case OpCode.Mul:
+                return v1 * v2;
+            case OpCode.Div:
+                return v1 / v2;
+            case OpCode.Mod:
+                return v1 - Math.Floor(v1 / v2) * v2;
+            case OpCode.Pow:
+                return Math.Pow(v1, v2);
+            case OpCode.Unm:
+                return -v1;
+        }
+
+        throw new("not an arithmetic op code (" + op + ")");
+    }
+
+    public static (ExprDesc, bool) FoldConstants(OpCode op, ExprDesc e1, ExprDesc e2)
+    {
+        if (!e1.IsNumeral() || !e2.IsNumeral())
+            return (e1, false);
+        if ((op == OpCode.Div || op == OpCode.Mod) && e2.Value == 0.0)
+            return (e1, false);
+        e1.Value = Arith(op, e1.Value, e2.Value);
+        return (e1, true);
+    }
+
+
+    public ExprDesc EncodeArithmetic(OpCode op, ExprDesc e1, ExprDesc e2, int line)
+    {
+        var (e, folded) = FoldConstants(op, e1, e2);
+        if (folded)
+            return e;
+        var o2 = 0;
+        if (op != OpCode.Unm && op != OpCode.Len)
+        {
+            (e2, o2) = ExpressionToRegisterOrConstant(e2);
+        }
+
+        (e1, var o1) = ExpressionToRegisterOrConstant(e1);
+        if (o1 > o2)
+        {
+            FreeExpression(e1);
+            FreeExpression(e2);
+        }
+        else
+        {
+            FreeExpression(e2);
+            FreeExpression(e1);
+        }
+
+        e1.Info = EncodeABC(op, 0, o1, o2);
+        e1.Kind = Kind.Relocatable;
+        FixLine(line);
+        return e1;
+    }
+
+
+    public ExprDesc Prefix(int op, ExprDesc e, int line)
+    {
+        switch (op)
+        {
+            case OprMinus:
+                if (e.IsNumeral())
+                {
+                    e.Value = -e.Value;
+                    return e;
+                }
+
+                return EncodeArithmetic(OpCode.Unm, ExpressionToAnyRegister(e), MakeExpression(Kind.Number, 0), line);
+            case OprNot:
+                return EncodeNot(e);
+            case OprLength:
+                return EncodeArithmetic(OpCode.Len, ExpressionToAnyRegister(e), MakeExpression(Kind.Number, 0), line);
+        }
+
+        throw new("unreachable");
+    }
+
+
+    public ExprDesc Infix(int op, ExprDesc e)
+    {
+        switch (op)
+        {
+            case OprAnd:
+                e = GoIfTrue(e);
+                break;
+            case OprOr:
+                e = GoIfFalse(e);
+                break;
+            case OprConcat:
+                e = ExpressionToNextRegister(e);
+                break;
+            case OprAdd:
+            case OprSub:
+            case OprMul:
+            case OprDiv:
+            case OprMod:
+            case OprPow:
+                if (!e.IsNumeral())
+                    (e, _) = ExpressionToRegisterOrConstant(e);
+                break;
+            default:
+                (e, _) = ExpressionToRegisterOrConstant(e);
+                break;
+        }
+
+        return e;
+    }
+
+
+    public ExprDesc EncodeComparison(OpCode op, int cond, ExprDesc e1, ExprDesc e2)
+    {
+        (e1, var o1) = ExpressionToRegisterOrConstant(e1);
+        (e2, var o2) = ExpressionToRegisterOrConstant(e2);
+        FreeExpression(e2);
+        FreeExpression(e1);
+        if (cond == 0 && op != OpCode.Eq)
+        {
+            (o1, o2, cond) = (o2, o1, 1);
+        }
+
+        return MakeExpression(Kind.Jump, ConditionalJump(op, cond, o1, o2));
+    }
+
+
+    public ExprDesc Postfix(int op, ExprDesc e1, ExprDesc e2, int line)
+    {
+        switch (op)
+        {
+            case OprAnd:
+                Assert(e1.T == NoJump);
+                e2 = DischargeVariables(e2);
+                e2.F = Concatenate(e2.F, e1.F);
+                return e2;
+            case OprOr:
+                Assert(e1.F == NoJump);
+                e2 = DischargeVariables(e2);
+                e2.T = Concatenate(e2.T, e1.T);
+                return e2;
+            case OprConcat:
+                e2 = ExpressionToValue(e2);
+                if (e2.Kind == Kind.Relocatable && Instruction(e2).OpCode == OpCode.Concat)
+                {
+                    Assert(e1.Info == Instruction(e2).B - 1);
+                    FreeExpression(e1);
+                    Instruction(e2).B = (e1.Info);
+                    return MakeExpression(Kind.Relocatable, e2.Info);
+                }
+
+                return EncodeArithmetic(OpCode.Concat, e1, ExpressionToNextRegister(e2), line);
+            case OprAdd:
+            case OprSub:
+            case OprMul:
+            case OprDiv:
+            case OprMod:
+            case OprPow:
+                return EncodeArithmetic((OpCode)(op - OprAdd + (byte)OpCode.Add), e1, e2, line);
+            case OprEq:
+            case OprLT:
+            case OprLE:
+                return EncodeComparison((OpCode)(op - OprEq + (byte)OpCode.Eq), 1, e1, e2);
+            case OprNE:
+            case OprGT:
+            case OprGE:
+                return EncodeComparison((OpCode)(op - OprNE + (byte)OpCode.Eq), 0, e1, e2);
+            default:
+                throw new("unreachable");
+        }
+    }
+
+
+    public void FixLine(int line) => Proto.LineInfoList[Proto.CodeList.Length - 1] = line;
+
+
+    public void SetList(int @base, int elementCount, int storeCount)
+    {
+        Assert(storeCount != 0);
+        if (storeCount == MultipleReturns)
+        {
+            storeCount = 0;
+        }
+
+        var c = (elementCount - 1) / ListItemsPerFlush + 1;
+        if (c <= MaxArgC)
+        {
+            EncodeABC(OpCode.SetList, @base, storeCount, c);
+        }
+        else if (c <= MaxArgAx)
+        {
+            EncodeABC(OpCode.SetList, @base, storeCount, 0);
+            EncodeExtraArg(c);
+        }
+        else
+        {
+            P.Scanner.SyntaxError("constructor too long");
+        }
+
+        FreeRegisterCount = @base + 1;
+    }
+
+
+    public unsafe void CheckConflict(AssignmentTarget tv, ExprDesc e)
+    {
+        var extra = FreeRegisterCount;
+        var conflict = false;
+        var t = &tv;
+        while (t != null)
+        {
+            ref var d = ref t->Description;
+            if (d.Kind == Kind.Indexed)
+            {
+                if (d.TableType == e.Kind && d.Table == e.Info)
+                {
+                    conflict = true;
+                    d.Table = extra;
+                    d.TableType = Kind.Local;
+                }
+
+                if (e.Kind == Kind.Local && d.Index == e.Info)
+                {
+                    conflict = true;
+                    d.Index = extra;
+                }
+            }
+
+            t = t->Previous;
+        }
+
+        if (conflict)
+        {
+            if (e.Kind == Kind.Local)
+            {
+                EncodeABC(OpCode.Move, extra, e.Info, 0);
+            }
+            else
+            {
+                EncodeABC(OpCode.GetUpVal, extra, e.Info, 0);
+            }
+
+            ReserveRegisters(1);
+        }
+    }
+
+
+    public void AdjustAssignment(int variableCount, int expressionCount, ExprDesc e)
+    {
+        var extra = variableCount - expressionCount;
+        if (e.HasMultipleReturns())
+        {
+            extra++;
+            if (extra < 0)
+            {
+                extra = 0;
+            }
+
+            SetReturns(e, extra);
+            if (extra > 1)
+            {
+                ReserveRegisters(extra - 1);
+            }
+        }
+        else
+        {
+            if (expressionCount > 0)
+            {
+                ExpressionToNextRegister(e);
+            }
+
+            if (extra > 0)
+            {
+                var r = FreeRegisterCount;
+                ReserveRegisters(extra);
+                LoadNil(r, extra);
+            }
+        }
+    }
+
+
+    public int MakeUpValue(string name, ExprDesc e)
+    {
+        P.CheckLimit(Proto.UpValuesList.Length + 1, MaxUpValue, "upvalues");
+        Proto.UpValuesList.Add(new() { Name = name, IsLocal = e.Kind == Kind.Local, Index = e.Info });
+        return Proto.UpValuesList.Length - 1;
+    }
+
+
+    public static (ExprDesc, bool) SingleVariableHelper(Function? f, string name, bool b)
+    {
+        static Block owningBlock(Block b1, int level)
+        {
+            while (b1.ActiveVariableCount > level)
+            {
+                b1 = b1.Previous!;
+            }
+
+            return b1;
+        }
+
+        ;
+
+        static (int, bool) find(Function f, string name)
+        {
+            for (var i = f.ActiveVariableCount - 1; i >= 0; i--)
+            {
+                if (name == f.LocalVariable(i).Name)
+                {
+                    return (i, true);
+                }
+            }
+
+            return (0, false);
+        }
+
+        ;
+
+        static (int, bool) findUpValue(Function f, string name)
+        {
+            for (var i = 0; i < f.Proto.UpValuesList.Length; i++)
+            {
+                if (f.Proto.UpValuesList[i].Name == name)
+                {
+                    return (i, true);
+                }
+            }
+
+            return (0, false);
+        }
+
+        ;
+
+        if (f == null)
+        {
+            return default;
+        }
+
+        var (v, found) = find(f, name);
+        if (found)
+        {
+            var e = MakeExpression(Kind.Local, v);
+            if (!b)
+            {
+                owningBlock(f.Block, v).HasUpValue = true;
+            }
+
+            return (e, true);
+        }
+
+        (v, found) = findUpValue(f, name);
+        if (found)
+        {
+            return (MakeExpression(Kind.UpValue, v), true);
+        }
+
+        {
+            (var e, found) = SingleVariableHelper(f.Previous, name, false);
+            if (!found)
+            {
+                return (e, found);
+            }
+
+            return (MakeExpression(Kind.UpValue, f.MakeUpValue(name, e)), true);
+        }
+    }
+
+
+    public ExprDesc SingleVariable(string name)
+    {
+        var (e, found) = SingleVariableHelper(this, name, true);
+        if (!found)
+        {
+            (e, found) = SingleVariableHelper(this, "_ENV", true);
+            Assert(found && (e.Kind == Kind.Local || e.Kind == Kind.UpValue));
+            e = Indexed(e, EncodeString(name));
+        }
+
+        return e;
+    }
+
+
+    public (int pc, ExprDesc t) OpenConstructor()
+    {
+        var pc = EncodeABC(OpCode.NewTable, 0, 0, 0);
+        var t = ExpressionToNextRegister(MakeExpression(Kind.Relocatable, pc));
+        return (pc, t);
+    }
+
+
+    public void FlushFieldToConstructor(int tableRegister, int freeRegisterCount, ExprDesc k, Func<ExprDesc> v)
+    {
+        (_, var rk) = ExpressionToRegisterOrConstant(k);
+        (_, var rv) = ExpressionToRegisterOrConstant(v());
+        EncodeABC(OpCode.SetTable, tableRegister, rk, rv);
+        FreeRegisterCount = freeRegisterCount;
+    }
+
+
+    public int FlushToConstructor(int tableRegister, int pending, int arrayCount, ExprDesc e)
+    {
+        ExpressionToNextRegister(e);
+        if (pending == ListItemsPerFlush)
+        {
+            SetList(tableRegister, arrayCount, ListItemsPerFlush);
+            pending = 0;
+        }
+
+        return pending;
+    }
+
+
+    public void CloseConstructor(int pc, int tableRegister, int pending, int arrayCount, int hashCount, ExprDesc e)
+    {
+        if (pending != 0)
+        {
+            if (e.HasMultipleReturns())
+            {
+                SetMultipleReturns(e);
+                SetList(tableRegister, arrayCount, MultipleReturns);
+                arrayCount--;
+            }
+            else
+            {
+                if (e.Kind != Kind.Void)
+                {
+                    ExpressionToNextRegister(e);
+                }
+
+                SetList(tableRegister, arrayCount, pending);
+            }
+        }
+
+        Proto.CodeList[pc].B = (((arrayCount)));
+        Proto.CodeList[pc].C = (((hashCount)));
+    }
+
+
+    public int OpenForBody(int @base, int n, bool isNumeric)
+    {
+        var prep = isNumeric ? EncodeAsBx(OpCode.ForPrep, @base, NoJump) : Jump();
+        EnterBlock(false);
+        AdjustLocalVariables(n);
+        ReserveRegisters(n);
+        return prep;
+    }
+
+
+    public void CloseForBody(int prep, int @base, int line, int n, bool isNumeric)
+    {
+        LeaveBlock();
+        PatchToHere(prep);
+        int end;
+        if (isNumeric)
+        {
+            end = EncodeAsBx(OpCode.ForLoop, @base, NoJump);
+        }
+        else
+        {
+            EncodeABC(OpCode.TForCall, @base, 0, n);
+            FixLine(line);
+            end = EncodeAsBx(OpCode.TForLoop, @base + 2, NoJump);
+        }
+
+        PatchList(end, prep + 1);
+        FixLine(line);
+    }
+
+
+    public void OpenMainFunction()
+    {
+        EnterBlock(false);
+        MakeUpValue("_ENV", MakeExpression(Kind.Local, 0));
+    }
+
+
+    public Function CloseMainFunction()
+    {
+        ReturnNone();
+        LeaveBlock();
+        Assert(Block == null);
+        return Previous!;
+    }
+}

+ 0 - 499
src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs

@@ -1,499 +0,0 @@
-using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using Lua.Runtime;
-using Lua.Internal;
-
-namespace Lua.CodeAnalysis.Compilation;
-
-public class FunctionCompilationContext : IDisposable
-{
-    static class Pool
-    {
-        static readonly ConcurrentStack<FunctionCompilationContext> stack = new();
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static FunctionCompilationContext Rent()
-        {
-            if (!stack.TryPop(out var context))
-            {
-                context = new();
-            }
-
-            return context;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Return(FunctionCompilationContext context)
-        {
-            context.Reset();
-            stack.Push(context);
-        }
-    }
-
-    internal static FunctionCompilationContext Create(ScopeCompilationContext? parentScope)
-    {
-        var context = Pool.Rent();
-        context.ParentScope = parentScope;
-        return context;
-    }
-
-    FunctionCompilationContext()
-    {
-        Scope = new()
-        {
-            Function = this
-        };
-    }
-
-    // instructions
-    FastListCore<Instruction> instructions;
-    FastListCore<SourcePosition> instructionPositions;
-
-    // constants
-    Dictionary<LuaValue, int> constantIndexMap = new(16);
-    FastListCore<LuaValue> constants;
-
-    // functions
-    Dictionary<ReadOnlyMemory<char>, int> functionMap = new(32, Utf16StringMemoryComparer.Default);
-    FastListCore<Chunk> functions;
-
-    // upvalues
-    FastListCore<UpValueInfo> upvalues;
-    FastListCore<LocalValueInfo> localVariables;
-
-    // loop
-    FastListCore<BreakDescription> breakQueue;
-    FastListCore<GotoDescription> gotoQueue;
-
-    /// <summary>
-    /// Maximum local stack size
-    /// </summary>
-    public byte MaxStackPosition { get; set; }
-
-    /// <summary>
-    /// Chunk name (for debug)
-    /// </summary>
-    public string? ChunkName { get; set; }
-
-    /// <summary>
-    /// Level of nesting of while, repeat, and for loops
-    /// </summary>
-    public int LoopLevel { get; set; }
-
-    /// <summary>
-    /// Number of parameters
-    /// </summary>
-    public int ParameterCount { get; set; }
-
-    /// <summary>
-    /// Weather the function has variable arguments
-    /// </summary>
-    public bool HasVariableArguments { get; set; }
-
-    /// <summary>
-    /// Line number where the function is defined
-    /// </summary>
-    public int LineDefined { get; set; }
-
-    /// <summary>
-    /// Last line number where the function is defined
-    /// </summary>
-    public int LastLineDefined { get; set; }
-
-    /// <summary>
-    /// Parent scope context
-    /// </summary>
-    public ScopeCompilationContext? ParentScope { get; private set; }
-
-    /// <summary>
-    /// Top-level scope context
-    /// </summary>
-    public ScopeCompilationContext Scope { get; }
-
-    /// <summary>
-    /// Instructions
-    /// </summary>
-    public Span<Instruction> Instructions => instructions.AsSpan();
-
-    /// <summary>
-    /// Push the new instruction.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void PushInstruction(in Instruction instruction, in SourcePosition position)
-    {
-        instructions.Add(instruction);
-        instructionPositions.Add(position);
-    }
-
-    /// <summary>
-    /// Push or merge the new instruction.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void PushOrMergeInstruction(in Instruction instruction, in SourcePosition position, ref bool incrementStackPosition)
-    {
-        if (instructions.Length == 0)
-        {
-            instructions.Add(instruction);
-            instructionPositions.Add(position);
-            return;
-        }
-
-        var activeLocals = Scope.ActiveLocalVariables;
-
-        ref var lastInstruction = ref instructions.AsSpan()[^1];
-        var opcode = instruction.OpCode;
-        switch (opcode)
-        {
-            case OpCode.Move:
-
-                if (
-                    // available to merge and  last A is not local variable
-                    lastInstruction.A == instruction.B && !activeLocals[lastInstruction.A])
-                {
-                    switch (lastInstruction.OpCode)
-                    {
-                        case OpCode.LoadK:
-                        case OpCode.LoadBool when lastInstruction.C == 0:
-                        case OpCode.LoadNil when lastInstruction.B == 0:
-                        case OpCode.GetUpVal:
-                        case OpCode.GetTabUp:
-                        case OpCode.GetTable  when !activeLocals[lastInstruction.B]:
-                        case OpCode.NewTable:
-                        case OpCode.Self:
-                        case OpCode.Add:
-                        case OpCode.Sub:
-                        case OpCode.Mul:
-                        case OpCode.Div:
-                        case OpCode.Mod:
-                        case OpCode.Pow:
-                        case OpCode.Unm:
-                        case OpCode.Not:
-                        case OpCode.Len:
-                        case OpCode.Concat:
-                            {
-                                lastInstruction.A = instruction.A;
-                                incrementStackPosition = false;
-                                return;
-                            }
-                    }
-                }
-
-                break;
-            case OpCode.GetTable:
-                {
-                    // Merge MOVE GetTable
-                    if (lastInstruction.OpCode == OpCode.Move && !activeLocals[lastInstruction.A])
-                    {
-                        if (lastInstruction.A == instruction.B)
-                        {
-                            lastInstruction = Instruction.GetTable(instruction.A, lastInstruction.B, instruction.C);
-                            instructionPositions[^1] = position;
-                            incrementStackPosition = false;
-                            return;
-                        }
-                    }
-
-                    break;
-                }
-            case OpCode.SetTable:
-                {
-                    // Merge MOVE SETTABLE
-                    if (lastInstruction.OpCode == OpCode.Move && !activeLocals[lastInstruction.A])
-                    {
-                        var lastB = lastInstruction.B;
-                        var lastA = lastInstruction.A;
-                        if (lastB < 255 && lastA == instruction.A)
-                        {
-                            // Merge MOVE MOVE SETTABLE
-                            if (instructions.Length > 2)
-                            {
-                                ref var last2Instruction = ref instructions.AsSpan()[^2];
-                                var last2A = last2Instruction.A;
-                                if (last2Instruction.OpCode == OpCode.Move && !activeLocals[last2A] && instruction.C == last2A)
-                                {
-                                    last2Instruction = Instruction.SetTable((byte)(lastB), instruction.B, last2Instruction.B);
-                                    instructions.RemoveAtSwapback(instructions.Length - 1);
-                                    instructionPositions.RemoveAtSwapback(instructionPositions.Length - 1);
-                                    instructionPositions[^1] = position;
-                                    incrementStackPosition = false;
-                                    return;
-                                }
-                            }
-
-                            lastInstruction = Instruction.SetTable((byte)(lastB), instruction.B, instruction.C);
-                            instructionPositions[^1] = position;
-                            incrementStackPosition = false;
-                            return;
-                        }
-
-                        if (lastA == instruction.C)
-                        {
-                            lastInstruction = Instruction.SetTable(instruction.A, instruction.B, lastB);
-                            instructionPositions[^1] = position;
-                            incrementStackPosition = false;
-                            return;
-                        }
-                    }
-                    else if (lastInstruction.OpCode == OpCode.GetTabUp && instructions.Length >= 2)
-                    {
-                        ref var last2Instruction = ref instructions[^2];
-                        var last2OpCode = last2Instruction.OpCode;
-                        if (last2OpCode is OpCode.LoadK or OpCode.Move)
-                        {
-                            var last2A = last2Instruction.A;
-                            if (!activeLocals[last2A] && instruction.C == last2A)
-                            {
-                                var c = last2OpCode == OpCode.LoadK ? last2Instruction.Bx + 256 : last2Instruction.B;
-                                last2Instruction = lastInstruction;
-                                lastInstruction = instruction with { C = (ushort)c };
-                                instructionPositions[^2] = instructionPositions[^1];
-                                instructionPositions[^1] = position;
-                                incrementStackPosition = false;
-                                return;
-                            }
-                        }
-                    }
-
-                    break;
-                }
-            case OpCode.Unm:
-            case OpCode.Not:
-            case OpCode.Len:
-                if (lastInstruction.OpCode == OpCode.Move && !activeLocals[lastInstruction.A] && lastInstruction.A == instruction.B)
-                {
-                    lastInstruction = instruction with { B = lastInstruction.B };
-                    instructionPositions[^1] = position;
-                    incrementStackPosition = false;
-                    return;
-                }
-
-                break;
-        }
-
-        instructions.Add(instruction);
-        instructionPositions.Add(position);
-    }
-
-    /// <summary>
-    /// Gets the index of the constant from the value, or if the constant is not registered it is added and its index is returned.
-    /// </summary>
-    public uint GetConstantIndex(in LuaValue value)
-    {
-        if (!constantIndexMap.TryGetValue(value, out var index))
-        {
-            index = constants.Length;
-
-            constants.Add(value);
-            constantIndexMap.Add(value, index);
-        }
-
-        return (uint)index;
-    }
-
-    public void AddOrSetFunctionProto(ReadOnlyMemory<char> name, Chunk chunk, out int index)
-    {
-        index = functions.Length;
-        functionMap[name] = functions.Length;
-        functions.Add(chunk);
-    }
-
-    public void AddFunctionProto(Chunk chunk, out int index)
-    {
-        index = functions.Length;
-        functions.Add(chunk);
-    }
-
-    public bool TryGetFunctionProto(ReadOnlyMemory<char> name, [NotNullWhen(true)] out Chunk? proto)
-    {
-        if (functionMap.TryGetValue(name, out var index))
-        {
-            proto = functions[index];
-            return true;
-        }
-        else
-        {
-            proto = null;
-            return false;
-        }
-    }
-
-    public void AddLocalVariable(ReadOnlyMemory<char> name, LocalVariableDescription description)
-    {
-        localVariables.Add(new LocalValueInfo()
-        {
-            Name = name,
-            Index = description.RegisterIndex,
-            StartPc = description.StartPc,
-            EndPc = Instructions.Length,
-        });
-    }
-
-    public void AddUpValue(UpValueInfo upValue)
-    {
-        upvalues.Add(upValue);
-    }
-
-    public bool TryGetUpValue(ReadOnlyMemory<char> name, out UpValueInfo description)
-    {
-        var span = upvalues.AsSpan();
-        for (int i = 0; i < span.Length; i++)
-        {
-            var info = span[i];
-            if (info.Name.Span.SequenceEqual(name.Span))
-            {
-                description = info;
-                return true;
-            }
-        }
-
-        if (ParentScope == null)
-        {
-            description = default;
-            return false;
-        }
-
-        if (ParentScope.TryGetLocalVariable(name, out var localVariable))
-        {
-            ParentScope.HasCapturedLocalVariables = true;
-
-            description = new()
-            {
-                Name = name,
-                Index = localVariable.RegisterIndex,
-                Id = upvalues.Length,
-                IsInRegister = true,
-            };
-            upvalues.Add(description);
-
-            return true;
-        }
-        else if (ParentScope.Function.TryGetUpValue(name, out var parentUpValue))
-        {
-            description = new()
-            {
-                Name = name,
-                Index = parentUpValue.Id,
-                Id = upvalues.Length,
-                IsInRegister = false,
-            };
-            upvalues.Add(description);
-
-            return true;
-        }
-
-        description = default;
-        return false;
-    }
-
-    public void AddUnresolvedBreak(BreakDescription description, SourcePosition sourcePosition)
-    {
-        if (LoopLevel == 0)
-        {
-            LuaParseException.BreakNotInsideALoop(ChunkName, sourcePosition);
-        }
-
-        breakQueue.Add(description);
-    }
-
-    public void ResolveAllBreaks(byte startPosition, int endPosition, ScopeCompilationContext loopScope)
-    {
-        foreach (var description in breakQueue.AsSpan())
-        {
-            ref var instruction = ref Instructions[description.Index];
-            if (loopScope.HasCapturedLocalVariables)
-            {
-                instruction.A = startPosition;
-            }
-
-            instruction.SBx = endPosition - description.Index;
-        }
-
-        breakQueue.Clear();
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void AddUnresolvedGoto(GotoDescription description)
-    {
-        gotoQueue.Add(description);
-    }
-
-    public void ResolveGoto(LabelDescription labelDescription)
-    {
-        for (int i = 0; i < gotoQueue.Length; i++)
-        {
-            var gotoDesc = gotoQueue[i];
-            if (gotoDesc.Name.Span.SequenceEqual(labelDescription.Name.Span))
-            {
-                instructions[gotoDesc.JumpInstructionIndex] = Instruction.Jmp(labelDescription.RegisterIndex, labelDescription.Index - gotoDesc.JumpInstructionIndex - 1);
-                gotoQueue.RemoveAtSwapback(i);
-                i--;
-            }
-        }
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public Chunk ToChunk()
-    {
-        // add return
-        instructions.Add(Instruction.Return(0, 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()
-        {
-            Name = ChunkName ?? "chunk",
-            Instructions = instructions.AsSpan().ToArray(),
-            SourcePositions = instructionPositions.AsSpan().ToArray(),
-            Constants = constants.AsSpan().ToArray(),
-            UpValues = upvalues.AsSpan().ToArray(),
-            Locals = locals,
-            Functions = functions.AsSpan().ToArray(),
-            ParameterCount = ParameterCount,
-            HasVariableArguments = HasVariableArguments,
-            MaxStackPosition = MaxStackPosition,
-            LineDefined = LineDefined,
-            LastLineDefined = LastLineDefined,
-        };
-
-        foreach (var function in functions.AsSpan())
-        {
-            function.Parent = chunk;
-        }
-
-        return chunk;
-    }
-
-    /// <summary>
-    /// Resets the values ​​held in the context.
-    /// </summary>
-    public void Reset()
-    {
-        Scope.Reset();
-        instructions.Clear();
-        instructionPositions.Clear();
-        constantIndexMap.Clear();
-        constants.Clear();
-        upvalues.Clear();
-        localVariables.Clear();
-        functionMap.Clear();
-        functions.Clear();
-        breakQueue.Clear();
-        gotoQueue.Clear();
-        ChunkName = null;
-        LoopLevel = 0;
-        ParameterCount = 0;
-        HasVariableArguments = false;
-        MaxStackPosition = 0;
-    }
-
-    /// <summary>
-    /// Returns the context object to the pool.
-    /// </summary>
-    public void Dispose()
-    {
-        ParentScope = null;
-        Pool.Return(this);
-    }
-}

+ 0 - 1287
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -1,1287 +0,0 @@
-using Lua.Internal;
-using Lua.CodeAnalysis.Syntax;
-using Lua.CodeAnalysis.Syntax.Nodes;
-using Lua.Runtime;
-
-namespace Lua.CodeAnalysis.Compilation;
-
-public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bool>
-{
-    public static readonly LuaCompiler Default = new();
-
-    public Chunk Compile(string source, string? chunkName = null)
-    {
-        return Compile(LuaSyntaxTree.Parse(source, chunkName), chunkName);
-    }
-
-    /// <summary>
-    /// Returns a compiled chunk of the syntax tree.
-    /// </summary>
-    public Chunk Compile(LuaSyntaxTree syntaxTree, string? chunkName = null)
-    {
-        using var context = FunctionCompilationContext.Create(null);
-        context.HasVariableArguments = true;
-        context.LineDefined = syntaxTree.Position.Line;
-        context.LastLineDefined = syntaxTree.Position.Line;
-        // set global enviroment upvalue
-        context.AddUpValue(new()
-        {
-            Name = "_ENV".AsMemory(), Id = 0, Index = -1, IsInRegister = false,
-        });
-
-        context.ChunkName = chunkName;
-
-        syntaxTree.Accept(this, context.Scope);
-        return context.ToChunk();
-    }
-
-    // Syntax Tree
-    public bool VisitSyntaxTree(LuaSyntaxTree node, ScopeCompilationContext context)
-    {
-        foreach (var childNode in node.Nodes)
-        {
-            childNode.Accept(this, context);
-        }
-
-        return true;
-    }
-
-    // Literals
-    public bool VisitNilLiteralNode(NilLiteralNode node, ScopeCompilationContext context)
-    {
-        context.PushInstruction(Instruction.LoadNil(context.StackPosition, 1), node.Position, true);
-        return true;
-    }
-
-    public bool VisitBooleanLiteralNode(BooleanLiteralNode node, ScopeCompilationContext context)
-    {
-        context.PushInstruction(Instruction.LoadBool(context.StackPosition, (ushort)(node.Value ? 1 : 0), 0), node.Position, true);
-        return true;
-    }
-
-    public bool VisitNumericLiteralNode(NumericLiteralNode node, ScopeCompilationContext context)
-    {
-        var index = context.Function.GetConstantIndex(node.Value);
-        context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.Position, true);
-        return true;
-    }
-
-    public bool VisitStringLiteralNode(StringLiteralNode node, ScopeCompilationContext context)
-    {
-        string? str;
-        if (node.IsShortLiteral)
-        {
-            if (!StringHelper.TryFromStringLiteral(node.Text.Span, out str))
-            {
-                throw new LuaParseException(context.Function.ChunkName, node.Position, $"invalid escape sequence near '{node.Text}'");
-            }
-        }
-        else
-        {
-            str = node.Text.ToString();
-        }
-
-        var index = context.Function.GetConstantIndex(str);
-        context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.Position, true);
-        return true;
-    }
-
-    // identifier
-    public bool VisitIdentifierNode(IdentifierNode node, ScopeCompilationContext context)
-    {
-        GetOrLoadIdentifier(node.Name, context, node.Position, false);
-        return true;
-    }
-
-    // vararg
-    public bool VisitVariableArgumentsExpressionNode(VariableArgumentsExpressionNode node, ScopeCompilationContext context)
-    {
-        CompileVariableArgumentsExpression(node, context, 1);
-        return true;
-    }
-
-    void CompileVariableArgumentsExpression(VariableArgumentsExpressionNode node, ScopeCompilationContext context, int resultCount)
-    {
-        context.PushInstruction(Instruction.VarArg(context.StackPosition, (ushort)(resultCount == -1 ? 0 : resultCount + 1)), node.Position, true);
-    }
-
-    // Unary/Binary expression
-    public bool VisitUnaryExpressionNode(UnaryExpressionNode node, ScopeCompilationContext context)
-    {
-        var b = context.StackPosition;
-        node.Node.Accept(this, context);
-
-        switch (node.Operator)
-        {
-            case UnaryOperator.Negate:
-                context.PushInstruction(Instruction.Unm(b, b), node.Position);
-                break;
-            case UnaryOperator.Not:
-                context.PushInstruction(Instruction.Not(b, b), node.Position);
-                break;
-            case UnaryOperator.Length:
-                context.PushInstruction(Instruction.Len(b, b), node.Position);
-                break;
-        }
-
-        return true;
-    }
-
-    public bool VisitBinaryExpressionNode(BinaryExpressionNode node, ScopeCompilationContext context)
-    {
-        var r = context.StackPosition;
-        if (node.OperatorType is BinaryOperator.And or BinaryOperator.Or)
-        {
-            byte a;
-            if (node.LeftNode is IdentifierNode leftIdentifier)
-            {
-                a = GetOrLoadIdentifier(leftIdentifier.Name, context, leftIdentifier.Position, true);
-            }
-            else
-            {
-                node.LeftNode.Accept(this, context);
-                a = context.StackTopPosition;
-            }
-
-            context.PushInstruction(Instruction.Test(a, 0), node.Position);
-            if (node.OperatorType is BinaryOperator.Or)
-            {
-                context.PushInstruction(Instruction.Jmp(0, 2), node.Position);
-                context.PushInstruction(Instruction.Move(r, a), node.Position);
-            }
-
-            var testJmpIndex = context.Function.Instructions.Length;
-            context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
-
-            context.StackPosition = r;
-            node.RightNode.Accept(this, context);
-
-            context.Function.Instructions[testJmpIndex].SBx = context.Function.Instructions.Length - testJmpIndex - 1;
-        }
-        else
-        {
-            var b = (ushort)GetRKIndex(node.LeftNode, context);
-            var c = (ushort)GetRKIndex(node.RightNode, context);
-
-            switch (node.OperatorType)
-            {
-                case BinaryOperator.Addition:
-                    context.PushInstruction(Instruction.Add(r, b, c), node.Position);
-                    break;
-                case BinaryOperator.Subtraction:
-                    context.PushInstruction(Instruction.Sub(r, b, c), node.Position);
-                    break;
-                case BinaryOperator.Multiplication:
-                    context.PushInstruction(Instruction.Mul(r, b, c), node.Position);
-                    break;
-                case BinaryOperator.Division:
-                    context.PushInstruction(Instruction.Div(r, b, c), node.Position);
-                    break;
-                case BinaryOperator.Modulo:
-                    context.PushInstruction(Instruction.Mod(r, b, c), node.Position);
-                    break;
-                case BinaryOperator.Exponentiation:
-                    context.PushInstruction(Instruction.Pow(r, b, c), node.Position);
-                    break;
-                case BinaryOperator.Equality:
-                    context.PushInstruction(Instruction.Eq(1, b, c), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
-                    break;
-                case BinaryOperator.Inequality:
-                    context.PushInstruction(Instruction.Eq(0, b, c), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
-                    break;
-                case BinaryOperator.GreaterThan:
-                    context.PushInstruction(Instruction.Lt(1, c, b), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
-                    break;
-                case BinaryOperator.GreaterThanOrEqual:
-                    context.PushInstruction(Instruction.Le(1, c, b), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
-                    break;
-                case BinaryOperator.LessThan:
-                    context.PushInstruction(Instruction.Lt(1, b, c), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
-                    break;
-                case BinaryOperator.LessThanOrEqual:
-                    context.PushInstruction(Instruction.Le(1, b, c), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
-                    context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
-                    break;
-                case BinaryOperator.Concat:
-                    context.PushInstruction(Instruction.Concat(r, b, c), node.Position);
-                    break;
-            }
-
-            context.StackPosition = (byte)(r + 1);
-        }
-
-        return true;
-    }
-
-    public bool VisitGroupedExpressionNode(GroupedExpressionNode node, ScopeCompilationContext context)
-    {
-        return node.Expression.Accept(this, context);
-    }
-
-    // table
-    public bool VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, ScopeCompilationContext context)
-    {
-        var tableRegisterIndex = context.StackPosition;
-        var newTableInstructionIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.NewTable(tableRegisterIndex, 0, 0), node.Position, true);
-
-        var currentArrayChunkSize = 0;
-        ushort hashMapSize = 0;
-        ushort arrayBlock = 1;
-
-        ListTableConstructorField? lastField = null;
-        if (node.Fields.LastOrDefault() is ListTableConstructorField t)
-        {
-            lastField = t;
-        }
-
-        foreach (var group in node.Fields.GroupConsecutiveBy(x => x.GetType()))
-        {
-            foreach (var field in group)
-            {
-                var p = context.StackPosition;
-
-                switch (field)
-                {
-                    case ListTableConstructorField listItem:
-                        context.StackPosition = (byte)(p + currentArrayChunkSize - 50 * (arrayBlock - 1));
-
-                        // For the last element, we need to take into account variable arguments and multiple return values.
-                        if (listItem == lastField)
-                        {
-                            bool isFixedItems = true;
-                            switch (listItem.Expression)
-                            {
-                                case CallFunctionExpressionNode call:
-                                    CompileCallFunctionExpression(call, context, false, -1);
-                                    isFixedItems = false;
-                                    break;
-                                case CallTableMethodExpressionNode method:
-                                    CompileTableMethod(method, context, false, -1);
-                                    break;
-                                case VariableArgumentsExpressionNode varArg:
-                                    CompileVariableArgumentsExpression(varArg, context, -1);
-                                    isFixedItems = false;
-                                    break;
-                                default:
-                                    listItem.Expression.Accept(this, context);
-                                    break;
-                            }
-
-                            context.PushInstruction(Instruction.SetList(tableRegisterIndex, (ushort)(isFixedItems ? context.StackTopPosition - tableRegisterIndex : 0), arrayBlock), listItem.Position);
-                            currentArrayChunkSize = 0;
-                        }
-                        else
-                        {
-                            listItem.Expression.Accept(this, context);
-
-                            currentArrayChunkSize++;
-
-                            if (currentArrayChunkSize == 50)
-                            {
-                                context.PushInstruction(Instruction.SetList(tableRegisterIndex, 50, arrayBlock), listItem.Position);
-                                currentArrayChunkSize = 0;
-                                arrayBlock++;
-                            }
-                        }
-
-                        break;
-                    case RecordTableConstructorField recordItem:
-                        recordItem.ValueExpression.Accept(this, context);
-                        var keyConstIndex = context.Function.GetConstantIndex(recordItem.Key) + 256;
-
-                        context.PushInstruction(Instruction.SetTable(tableRegisterIndex, (ushort)keyConstIndex, p), recordItem.Position);
-                        hashMapSize++;
-                        break;
-                    case GeneralTableConstructorField generalItem:
-                        var keyIndex = context.StackPosition;
-                        generalItem.KeyExpression.Accept(this, context);
-                        var valueIndex = context.StackPosition;
-                        generalItem.ValueExpression.Accept(this, context);
-
-                        context.PushInstruction(Instruction.SetTable(tableRegisterIndex, keyIndex, valueIndex), generalItem.Position);
-                        hashMapSize++;
-                        break;
-                    default:
-                        throw new NotSupportedException();
-                }
-
-                context.StackPosition = p;
-            }
-
-            if (currentArrayChunkSize > 0)
-            {
-                context.PushInstruction(Instruction.SetList(tableRegisterIndex, (ushort)currentArrayChunkSize, arrayBlock), node.Position);
-                currentArrayChunkSize = 0;
-                arrayBlock = 1;
-            }
-        }
-
-        context.Function.Instructions[newTableInstructionIndex].B = (ushort)(currentArrayChunkSize + (arrayBlock - 1) * 50);
-        context.Function.Instructions[newTableInstructionIndex].C = hashMapSize;
-
-        return true;
-    }
-
-    public bool VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, ScopeCompilationContext context)
-    {
-        // load table
-        var tablePosition = context.StackPosition;
-        node.TableNode.Accept(this, context);
-
-        // load key
-        var keyPosition = (ushort)GetRKIndex(node.KeyNode, context);
-
-        // push interuction
-        context.PushInstruction(Instruction.GetTable(tablePosition, tablePosition, keyPosition), node.Position);
-        context.StackPosition = (byte)(tablePosition + 1);
-
-        return true;
-    }
-
-    public bool VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, ScopeCompilationContext context)
-    {
-        // load table
-        var tablePosition = context.StackPosition;
-        node.TableNode.Accept(this, context);
-
-        // load key
-        var keyIndex = context.Function.GetConstantIndex(node.MemberName) + 256;
-
-        // push interuction
-        context.PushInstruction(Instruction.GetTable(tablePosition, tablePosition, (ushort)keyIndex), node.Position);
-        context.StackPosition = (byte)(tablePosition + 1);
-
-        return true;
-    }
-
-    public bool VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, ScopeCompilationContext context)
-    {
-        CompileTableMethod(node, context, false, 1);
-        return true;
-    }
-
-    public bool VisitCallTableMethodStatementNode(CallTableMethodStatementNode node, ScopeCompilationContext context)
-    {
-        CompileTableMethod(node.Expression, context, false, 0);
-        return true;
-    }
-
-    void CompileTableMethod(CallTableMethodExpressionNode node, ScopeCompilationContext context, bool isTailCall, int resultCount)
-    {
-        // load table
-        var tablePosition = context.StackPosition;
-        node.TableNode.Accept(this, context);
-
-        // load key
-        var keyIndex = context.Function.GetConstantIndex(node.MethodName) + 256;
-
-        // get closure
-        context.PushInstruction(Instruction.Self(tablePosition, tablePosition, (ushort)keyIndex), node.Position);
-        context.StackPosition = (byte)(tablePosition + 2);
-
-        // load arguments
-        var b = node.ArgumentNodes.Length + 2;
-        if (node.ArgumentNodes.Length > 0 && !IsFixedNumberOfReturnValues(node.ArgumentNodes[^1]))
-        {
-            b = 0;
-        }
-
-        CompileExpressionList(node, node.ArgumentNodes, b - 2, context);
-
-        // push call interuction
-        if (isTailCall)
-        {
-            context.PushInstruction(Instruction.TailCall(tablePosition, (ushort)b, 0), node.Position);
-            context.StackPosition = tablePosition;
-        }
-        else
-        {
-            context.PushInstruction(Instruction.Call(tablePosition, (ushort)b, (ushort)(resultCount < 0 ? 0 : resultCount + 1)), node.Position);
-            context.StackPosition = (byte)(tablePosition + resultCount);
-        }
-    }
-
-    // return
-    public bool VisitReturnStatementNode(ReturnStatementNode node, ScopeCompilationContext context)
-    {
-        ushort b;
-
-        // tail call
-        if (node.Nodes.Length == 1)
-        {
-            var lastNode = node.Nodes[^1];
-
-            if (lastNode is CallFunctionExpressionNode call)
-            {
-                CompileCallFunctionExpression(call, context, true, -1);
-                return true;
-            }
-            else if (lastNode is CallTableMethodExpressionNode callMethod)
-            {
-                CompileTableMethod(callMethod, context, true, -1);
-                return true;
-            }
-        }
-
-        b = node.Nodes.Length > 0 && !IsFixedNumberOfReturnValues(node.Nodes[^1])
-            ? (ushort)0
-            : (ushort)(node.Nodes.Length + 1);
-
-        var a = context.StackPosition;
-
-        CompileExpressionList(node, node.Nodes, b - 1, context);
-
-        context.PushInstruction(Instruction.Return(a, b), node.Position);
-
-        return true;
-    }
-
-    // assignment
-    public bool VisitLocalAssignmentStatementNode(LocalAssignmentStatementNode node, ScopeCompilationContext context)
-    {
-        var startPosition = context.StackPosition;
-        CompileExpressionList(node, node.RightNodes, node.LeftNodes.Length, context);
-
-        for (int i = 0; i < node.Identifiers.Length; i++)
-        {
-            context.StackPosition = (byte)(startPosition + i + 1);
-
-            var identifier = node.Identifiers[i];
-
-            if (context.TryGetLocalVariableInThisScope(identifier.Name, out var variable))
-            {
-                // assign local variable
-                context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
-            }
-            else
-            {
-                // register local variable
-                context.AddLocalVariable(identifier.Name, new() { RegisterIndex = (byte)(context.StackPosition - 1), StartPc = context.Function.Instructions.Length, });
-            }
-        }
-
-        return true;
-    }
-
-    public bool VisitAssignmentStatementNode(AssignmentStatementNode node, ScopeCompilationContext context)
-    {
-        var startPosition = context.StackPosition;
-
-        CompileExpressionList(node, node.RightNodes, node.LeftNodes.Length, context);
-
-        for (int i = 0; i < node.LeftNodes.Length; i++)
-        {
-            context.StackPosition = (byte)(startPosition + i + 1);
-            var leftNode = node.LeftNodes[i];
-
-            switch (leftNode)
-            {
-                case IdentifierNode identifier:
-                    {
-                        if (context.TryGetLocalVariable(identifier.Name, out var variable))
-                        {
-                            // assign local variable
-                            context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
-                        }
-                        else if (context.Function.TryGetUpValue(identifier.Name, out var upValue))
-                        {
-                            // assign upvalue
-                            context.PushInstruction(Instruction.SetUpVal((byte)(context.StackPosition - 1), (ushort)upValue.Id), node.Position);
-                        }
-                        else if (context.TryGetLocalVariable("_ENV".AsMemory(), out variable))
-                        {
-                            // assign env element
-                            var index = context.Function.GetConstantIndex(identifier.Name.ToString()) + 256;
-                            context.PushInstruction(Instruction.SetTable(variable.RegisterIndex, (ushort)index, (ushort)(context.StackPosition - 1)), node.Position);
-                        }
-                        else
-                        {
-                            // assign global variable
-                            var index = context.Function.GetConstantIndex(identifier.Name.ToString()) + 256;
-                            context.PushInstruction(Instruction.SetTabUp(0, (ushort)index, (ushort)(context.StackPosition - 1)), node.Position);
-                        }
-                    }
-                    break;
-                case TableIndexerAccessExpressionNode tableIndexer:
-                    {
-                        var valueIndex = context.StackPosition - 1;
-                        tableIndexer.TableNode.Accept(this, context);
-                        var tableIndex = context.StackPosition - 1;
-                        tableIndexer.KeyNode.Accept(this, context);
-                        var keyIndex = context.StackPosition - 1;
-                        context.PushInstruction(Instruction.SetTable((byte)tableIndex, (ushort)keyIndex, (ushort)valueIndex), node.Position);
-                    }
-                    break;
-                case TableMemberAccessExpressionNode tableMember:
-                    {
-                        var valueIndex = context.StackPosition - 1;
-                        tableMember.TableNode.Accept(this, context);
-                        var tableIndex = context.StackPosition - 1;
-                        var keyIndex = context.Function.GetConstantIndex(tableMember.MemberName) + 256;
-                        context.PushInstruction(Instruction.SetTable((byte)tableIndex, (ushort)keyIndex, (ushort)valueIndex), node.Position);
-                    }
-                    break;
-                default:
-                    throw new LuaParseException(default, default, "An error occurred while parsing the code"); // TODO: add message
-            }
-        }
-
-        context.StackPosition = startPosition;
-
-        return true;
-    }
-
-    // function call
-    public bool VisitCallFunctionStatementNode(CallFunctionStatementNode node, ScopeCompilationContext context)
-    {
-        CompileCallFunctionExpression(node.Expression, context, false, 0);
-        return true;
-    }
-
-    public bool VisitCallFunctionExpressionNode(CallFunctionExpressionNode node, ScopeCompilationContext context)
-    {
-        CompileCallFunctionExpression(node, context, false, 1);
-        return true;
-    }
-
-    void CompileCallFunctionExpression(CallFunctionExpressionNode node, ScopeCompilationContext context, bool isTailCall, int resultCount)
-    {
-        // get closure
-        var r = context.StackPosition;
-        node.FunctionNode.Accept(this, context);
-
-        // load arguments
-        var b = node.ArgumentNodes.Length + 1;
-        if (node.ArgumentNodes.Length > 0 && !IsFixedNumberOfReturnValues(node.ArgumentNodes[^1]))
-        {
-            b = 0;
-        }
-
-        CompileExpressionList(node, node.ArgumentNodes, b - 1, context);
-
-        // push call interuction
-        if (isTailCall)
-        {
-            context.PushInstruction(Instruction.TailCall(r, (ushort)b, 0), node.Position);
-            context.StackPosition = r;
-        }
-        else
-        {
-            context.PushInstruction(Instruction.Call(r, (ushort)b, (ushort)(resultCount == -1 ? 0 : resultCount + 1)), node.Position);
-            context.StackPosition = (byte)(r + resultCount);
-        }
-    }
-
-    // function declaration
-    public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context)
-    {
-        var funcIndex = CompileFunctionProto(ReadOnlyMemory<char>.Empty, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line);
-
-        // push closure instruction
-        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
-
-        return true;
-    }
-
-    public bool VisitLocalFunctionDeclarationStatementNode(LocalFunctionDeclarationStatementNode node, ScopeCompilationContext context)
-    {
-        // assign local variable
-        context.AddLocalVariable(node.Name, new() { RegisterIndex = context.StackPosition, StartPc = context.Function.Instructions.Length, });
-
-        // compile function
-        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line);
-
-        // push closure instruction
-        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
-
-        return true;
-    }
-
-    public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context)
-    {
-        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line);
-
-        // add closure
-        var index = context.Function.GetConstantIndex(node.Name.ToString());
-
-        // push closure instruction
-        context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
-
-        if (context.TryGetLocalVariableInThisScope(node.Name, out var variable))
-        {
-            // assign local variable
-            context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
-        }
-        else
-        {
-            // assign global variable
-            context.PushInstruction(Instruction.SetTabUp(0, (ushort)(index + 256), (ushort)(context.StackPosition - 1)), node.Position);
-        }
-
-        return true;
-    }
-
-    public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context)
-    {
-        var funcIdentifier = node.MemberPath[^1];
-        var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length + 1, node.HasVariableArguments, node.HasSelfParameter, node.LineDefined, node.EndPosition.Line);
-
-        // add closure
-        var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString());
-
-        var r = context.StackPosition;
-
-        // assign global variable
-        var first = node.MemberPath[0];
-        var tableIndex = GetOrLoadIdentifier(first.Name, context, first.Position, true);
-
-        for (int i = 1; i < node.MemberPath.Length - 1; i++)
-        {
-            var member = node.MemberPath[i];
-            var constant = context.Function.GetConstantIndex(member.Name.ToString());
-            context.PushInstruction(Instruction.GetTable(context.StackPosition, tableIndex, (ushort)(constant + 256)), member.Position, true);
-            tableIndex = context.StackTopPosition;
-        }
-
-        // push closure instruction
-        var closureIndex = context.StackPosition;
-        context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.EndPosition, true);
-
-        // set table
-        context.PushInstruction(Instruction.SetTable(tableIndex, (ushort)(index + 256), closureIndex), funcIdentifier.Position);
-
-        context.StackPosition = r;
-        return true;
-    }
-
-    int CompileFunctionProto(ReadOnlyMemory<char> functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, int parameterCount, bool hasVarArg, bool hasSelfParameter, int lineDefined, int lastLineDefined)
-    {
-        using var funcContext = context.CreateChildFunction();
-        funcContext.ChunkName = functionName.ToString();
-        funcContext.ParameterCount = parameterCount;
-        funcContext.HasVariableArguments = hasVarArg;
-        funcContext.LineDefined = lineDefined;
-        funcContext.LastLineDefined = lastLineDefined;
-
-        if (hasSelfParameter)
-        {
-            funcContext.Scope.AddLocalVariable("self".AsMemory(), new() { RegisterIndex = 0, StartPc = 0, });
-
-            funcContext.Scope.StackPosition++;
-        }
-
-        // add arguments
-        for (int i = 0; i < parameters.Length; i++)
-        {
-            var parameter = parameters[i];
-            funcContext.Scope.AddLocalVariable(parameter.Name, new() { RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)), StartPc = 0, });
-
-            funcContext.Scope.StackPosition++;
-        }
-
-        foreach (var statement in statements)
-        {
-            statement.Accept(this, funcContext.Scope);
-        }
-
-        // compile function
-        var chunk = funcContext.ToChunk();
-
-        int index;
-        if (functionName.Length == 0)
-        {
-            // anonymous function
-            context.Function.AddFunctionProto(chunk, out index);
-        }
-        else
-        {
-            context.Function.AddOrSetFunctionProto(functionName, chunk, out index);
-        }
-
-        return index;
-    }
-
-    // control statements
-    public bool VisitDoStatementNode(DoStatementNode node, ScopeCompilationContext context)
-    {
-        using var scopeContext = context.CreateChildScope();
-
-        foreach (var childNode in node.StatementNodes)
-        {
-            childNode.Accept(this, scopeContext);
-        }
-
-        scopeContext.TryPushCloseUpValue(scopeContext.StackTopPosition, node.Position);
-
-        return true;
-    }
-
-    public bool VisitBreakStatementNode(BreakStatementNode node, ScopeCompilationContext context)
-    {
-        context.Function.AddUnresolvedBreak(new() { Index = context.Function.Instructions.Length }, node.Position);
-        context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
-
-        return true;
-    }
-
-    public bool VisitIfStatementNode(IfStatementNode node, ScopeCompilationContext context)
-    {
-        using var endJumpIndexList = new PooledList<int>(8);
-        var hasElse = node.ElseNodes.Length > 0;
-        var stackPositionToClose = (byte)(context.StackPosition + 1);
-        // if
-        using (var scopeContext = context.CreateChildScope())
-        {
-            CompileConditionNode(node.IfNode.ConditionNode, scopeContext, true, node.IfNode.Position);
-
-            var ifPosition = scopeContext.Function.Instructions.Length;
-            scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.IfNode.Position);
-
-            foreach (var childNode in node.IfNode.ThenNodes)
-            {
-                childNode.Accept(this, scopeContext);
-            }
-
-            stackPositionToClose = scopeContext.HasCapturedLocalVariables ? stackPositionToClose : (byte)0;
-            if (hasElse)
-            {
-                endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
-                scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.IfNode.ThenNodes[^1].Position, true);
-            }
-            else
-            {
-                scopeContext.TryPushCloseUpValue(stackPositionToClose, node.Position);
-            }
-
-            scopeContext.Function.Instructions[ifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - ifPosition;
-        }
-
-        // elseif
-        foreach (var elseIf in node.ElseIfNodes)
-        {
-            using var scopeContext = context.CreateChildScope();
-
-            CompileConditionNode(elseIf.ConditionNode, scopeContext, true);
-
-            var elseifPosition = scopeContext.Function.Instructions.Length;
-            scopeContext.PushInstruction(Instruction.Jmp(0, 0), elseIf.Position);
-
-            foreach (var childNode in elseIf.ThenNodes)
-            {
-                childNode.Accept(this, scopeContext);
-            }
-
-            stackPositionToClose = scopeContext.HasCapturedLocalVariables ? stackPositionToClose : (byte)0;
-            // skip if node doesn't have else statements
-            if (hasElse)
-            {
-                endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
-                scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), elseIf.Position);
-            }
-            else
-            {
-                scopeContext.TryPushCloseUpValue(stackPositionToClose, elseIf.Position);
-            }
-
-            scopeContext.Function.Instructions[elseifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - elseifPosition;
-        }
-
-        // else nodes
-        using (var scopeContext = context.CreateChildScope())
-        {
-            foreach (var childNode in node.ElseNodes)
-            {
-                childNode.Accept(this, scopeContext);
-            }
-
-            scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
-        }
-
-        // set JMP sBx
-        foreach (var index in endJumpIndexList.AsSpan())
-        {
-            context.Function.Instructions[index].SBx = context.Function.Instructions.Length - 1 - index;
-        }
-
-        return true;
-    }
-
-    public bool VisitRepeatStatementNode(RepeatStatementNode node, ScopeCompilationContext context)
-    {
-        var startIndex = context.Function.Instructions.Length;
-
-        context.Function.LoopLevel++;
-
-        using var scopeContext = context.CreateChildScope();
-        var stackPosition = scopeContext.StackPosition;
-        foreach (var childNode in node.Nodes)
-        {
-            childNode.Accept(this, scopeContext);
-        }
-
-        CompileConditionNode(node.ConditionNode, scopeContext, true);
-        var a = scopeContext.HasCapturedLocalVariables ? (byte)(stackPosition + 1) : (byte)0;
-        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--;
-
-        // resolve break statements inside repeat block
-        context.Function.ResolveAllBreaks(a, context.Function.Instructions.Length - 1, scopeContext);
-
-        return true;
-    }
-
-    public bool VisitWhileStatementNode(WhileStatementNode node, ScopeCompilationContext context)
-    {
-        var conditionIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
-
-        context.Function.LoopLevel++;
-
-        using var scopeContext = context.CreateChildScope();
-        var stackPosition = scopeContext.StackPosition;
-
-        foreach (var childNode in node.Nodes)
-        {
-            childNode.Accept(this, scopeContext);
-        }
-
-        context.Function.LoopLevel--;
-
-        // set JMP sBx
-        scopeContext.Function.Instructions[conditionIndex].SBx = scopeContext.Function.Instructions.Length - 1 - conditionIndex;
-
-        CompileConditionNode(node.ConditionNode, scopeContext, false);
-        var a = scopeContext.HasCapturedLocalVariables ? (byte)(1 + stackPosition) : (byte)0;
-        scopeContext.PushInstruction(Instruction.Jmp(a, conditionIndex - context.Function.Instructions.Length), node.Position);
-        scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
-
-        // resolve break statements inside while block
-        context.Function.ResolveAllBreaks(scopeContext.StackPosition, context.Function.Instructions.Length - 1, scopeContext);
-
-        return true;
-    }
-
-    public bool VisitNumericForStatementNode(NumericForStatementNode node, ScopeCompilationContext context)
-    {
-        var startPosition = context.StackPosition;
-
-        node.InitNode.Accept(this, context);
-        node.LimitNode.Accept(this, context);
-        if (node.StepNode != null)
-        {
-            node.StepNode.Accept(this, context);
-        }
-        else
-        {
-            var index = context.Function.GetConstantIndex(1);
-            context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.DoPosition, true);
-        }
-
-        var prepIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.ForPrep(startPosition, 0), node.DoPosition, true);
-
-        // compile statements
-        context.Function.LoopLevel++;
-        using var scopeContext = context.CreateChildScope();
-        {
-            scopeContext.AddLocalVariable("(for index)".AsMemory(), new() { RegisterIndex = startPosition, StartPc = context.Function.Instructions.Length, });
-
-            scopeContext.AddLocalVariable("(for limit)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 1), StartPc = context.Function.Instructions.Length, });
-
-            scopeContext.AddLocalVariable("(for step)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 2), StartPc = context.Function.Instructions.Length, });
-
-            // add local variable
-            scopeContext.AddLocalVariable(node.VariableName, new() { RegisterIndex = (byte)(startPosition + 3), StartPc = context.Function.Instructions.Length, });
-
-            foreach (var childNode in node.StatementNodes)
-            {
-                childNode.Accept(this, scopeContext);
-            }
-
-            scopeContext.TryPushCloseUpValue((byte)(startPosition + 1), node.Position);
-        }
-        context.Function.LoopLevel--;
-
-        // set ForPrep
-        context.Function.Instructions[prepIndex].SBx = context.Function.Instructions.Length - prepIndex - 1;
-
-        // push ForLoop
-        context.PushInstruction(Instruction.ForLoop(startPosition, prepIndex - context.Function.Instructions.Length), node.Position);
-
-        context.Function.ResolveAllBreaks((byte)(startPosition + 1), context.Function.Instructions.Length - 1, scopeContext);
-
-        context.StackPosition = startPosition;
-
-        return true;
-    }
-
-    public bool VisitGenericForStatementNode(GenericForStatementNode node, ScopeCompilationContext context)
-    {
-        // get iterator
-        var startPosition = context.StackPosition;
-        CompileExpressionList(node, node.ExpressionNodes, 3, context);
-
-        // jump to TFORCALL
-        var startJumpIndex = context.Function.Instructions.Length;
-        context.PushInstruction(Instruction.Jmp(0, 0), node.DoPosition);
-
-        // compile statements
-        context.Function.LoopLevel++;
-        using var scopeContext = context.CreateChildScope();
-        {
-            scopeContext.StackPosition = (byte)(startPosition + 3 + node.Names.Length);
-
-            scopeContext.AddLocalVariable("(for generator)".AsMemory(), new() { RegisterIndex = (byte)(startPosition), StartPc = context.Function.Instructions.Length, });
-
-            scopeContext.AddLocalVariable("(for state)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 1), StartPc = context.Function.Instructions.Length, });
-
-            scopeContext.AddLocalVariable("(for control)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 2), StartPc = context.Function.Instructions.Length, });
-
-            // add local variables
-            for (int i = 0; i < node.Names.Length; i++)
-            {
-                var name = node.Names[i];
-                scopeContext.AddLocalVariable(name.Name, new() { RegisterIndex = (byte)(startPosition + 3 + i), StartPc = context.Function.Instructions.Length, });
-            }
-
-            foreach (var childNode in node.StatementNodes)
-            {
-                childNode.Accept(this, scopeContext);
-            }
-
-            scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
-        }
-        context.Function.LoopLevel--;
-
-        // set jump
-        context.Function.Instructions[startJumpIndex].SBx = context.Function.Instructions.Length - startJumpIndex - 1;
-
-        // push OP_TFORCALL and OP_TFORLOOP
-        context.PushInstruction(Instruction.TForCall(startPosition, (ushort)node.Names.Length), node.Position);
-        context.PushInstruction(Instruction.TForLoop((byte)(startPosition + 2), startJumpIndex - context.Function.Instructions.Length), node.Position);
-
-        context.Function.ResolveAllBreaks((byte)(startPosition + 1), context.Function.Instructions.Length - 1, scopeContext);
-        context.StackPosition = startPosition;
-
-        return true;
-    }
-
-    public bool VisitLabelStatementNode(LabelStatementNode node, ScopeCompilationContext context)
-    {
-        var desc = new LabelDescription() { Name = node.Name, Index = context.Function.Instructions.Length, RegisterIndex = context.StackPosition };
-
-        context.AddLabel(desc);
-        context.Function.ResolveGoto(desc);
-
-        return true;
-    }
-
-    public bool VisitGotoStatementNode(GotoStatementNode node, ScopeCompilationContext context)
-    {
-        if (context.TryGetLabel(node.Name, out var description))
-        {
-            context.PushInstruction(Instruction.Jmp(description.RegisterIndex, description.Index - context.Function.Instructions.Length - 1), node.Position);
-        }
-        else
-        {
-            context.Function.AddUnresolvedGoto(new() { Name = node.Name, JumpInstructionIndex = context.Function.Instructions.Length });
-
-            // add uninitialized jmp instruction
-            context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
-        }
-
-        return true;
-    }
-
-    static byte GetOrLoadIdentifier(ReadOnlyMemory<char> name, ScopeCompilationContext context, SourcePosition sourcePosition, bool dontLoadLocalVariable)
-    {
-        var p = context.StackPosition;
-
-        if (context.TryGetLocalVariable(name, out var variable))
-        {
-            if (dontLoadLocalVariable)
-            {
-                return variable.RegisterIndex;
-            }
-            else if (p == variable.RegisterIndex)
-            {
-                context.StackPosition++;
-                return p;
-            }
-            else
-            {
-                context.PushInstruction(Instruction.Move(p, variable.RegisterIndex), sourcePosition, true);
-                return p;
-            }
-        }
-        else if (context.Function.TryGetUpValue(name, out var upValue))
-        {
-            context.PushInstruction(Instruction.GetUpVal(p, (ushort)upValue.Id), sourcePosition, true);
-            return p;
-        }
-        else if (context.TryGetLocalVariable("_ENV".AsMemory(), out variable))
-        {
-            var keyStringIndex = context.Function.GetConstantIndex(name.ToString()) + 256;
-            context.PushInstruction(Instruction.GetTable(p, variable.RegisterIndex, (ushort)keyStringIndex), sourcePosition, true);
-            return p;
-        }
-        else
-        {
-            context.Function.TryGetUpValue("_ENV".AsMemory(), out upValue);
-            var index = context.Function.GetConstantIndex(name.ToString()) + 256;
-            context.PushInstruction(Instruction.GetTabUp(p, (ushort)upValue.Id, (ushort)index), sourcePosition, true);
-            return p;
-        }
-    }
-
-    uint GetRKIndex(ExpressionNode node, ScopeCompilationContext context)
-    {
-        if (node is IdentifierNode identifier)
-        {
-            return GetOrLoadIdentifier(identifier.Name, context, identifier.Position, true);
-        }
-        else if (TryGetConstant(node, context, out var constant))
-        {
-            return context.Function.GetConstantIndex(constant) + 256;
-        }
-        else
-        {
-            node.Accept(this, context);
-            return context.StackTopPosition;
-        }
-    }
-
-    static bool TryGetConstant(ExpressionNode node, ScopeCompilationContext context, out LuaValue value)
-    {
-        switch (node)
-        {
-            case NilLiteralNode:
-                value = LuaValue.Nil;
-                return true;
-            case BooleanLiteralNode booleanLiteral:
-                value = booleanLiteral.Value;
-                return true;
-            case NumericLiteralNode numericLiteral:
-                value = numericLiteral.Value;
-                return true;
-            case StringLiteralNode stringLiteral:
-                if (stringLiteral.IsShortLiteral)
-                {
-                    if (!StringHelper.TryFromStringLiteral(stringLiteral.Text.Span, out var str))
-                    {
-                        throw new LuaParseException(context.Function.ChunkName, stringLiteral.Position, $"invalid escape sequence near '{stringLiteral.Text}'");
-                    }
-
-                    value = str;
-                }
-                else
-                {
-                    value = stringLiteral.Text.ToString();
-                }
-
-                return true;
-            case UnaryExpressionNode unaryExpression:
-                if (TryGetConstant(unaryExpression.Node, context, out var unaryNodeValue))
-                {
-                    switch (unaryExpression.Operator)
-                    {
-                        case UnaryOperator.Negate:
-                            if (unaryNodeValue.TryRead<double>(out var d1))
-                            {
-                                value = -d1;
-                                return true;
-                            }
-
-                            break;
-                        case UnaryOperator.Not:
-                            if (unaryNodeValue.TryRead<bool>(out var b))
-                            {
-                                value = !b;
-                                return true;
-                            }
-
-                            break;
-                    }
-                }
-
-                break;
-            case BinaryExpressionNode binaryExpression:
-                if (TryGetConstant(binaryExpression.LeftNode, context, out var leftValue) &&
-                    TryGetConstant(binaryExpression.RightNode, context, out var rightValue))
-                {
-                    switch (binaryExpression.OperatorType)
-                    {
-                        case BinaryOperator.Addition:
-                            {
-                                if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2))
-                                {
-                                    value = d1 + d2;
-                                    return true;
-                                }
-                            }
-                            break;
-                        case BinaryOperator.Subtraction:
-                            {
-                                if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2))
-                                {
-                                    value = d1 - d2;
-                                    return true;
-                                }
-                            }
-                            break;
-                        case BinaryOperator.Multiplication:
-                            {
-                                if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2))
-                                {
-                                    value = d1 * d2;
-                                    return true;
-                                }
-                            }
-                            break;
-                        case BinaryOperator.Division:
-                            {
-                                if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2) && d2 != 0)
-                                {
-                                    value = d1 / d2;
-                                    return true;
-                                }
-                            }
-                            break;
-                    }
-                }
-
-                break;
-        }
-
-        value = default;
-        return false;
-    }
-
-    static bool IsFixedNumberOfReturnValues(ExpressionNode node)
-    {
-        return node is not (CallFunctionExpressionNode or CallTableMethodExpressionNode or VariableArgumentsExpressionNode);
-    }
-
-    /// <summary>
-    /// Compiles a conditional boolean branch: if true (or false), the next instruction added is skipped.
-    /// </summary>
-    /// <param name="node">Condition node</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="testPosition">Position of the test instruction</param>
-    void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, bool falseIsSkip, SourcePosition? testPosition = null)
-    {
-        if (node is BinaryExpressionNode binaryExpression)
-        {
-            switch (binaryExpression.OperatorType)
-            {
-                case BinaryOperator.Equality:
-                    {
-                        var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
-                        var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
-                        context.PushInstruction(Instruction.Eq(falseIsSkip ? (byte)0 : (byte)1, b, c), node.Position);
-                        return;
-                    }
-                case BinaryOperator.Inequality:
-                    {
-                        var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
-                        var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
-                        context.PushInstruction(Instruction.Eq(falseIsSkip ? (byte)1 : (byte)0, b, c), node.Position);
-                        return;
-                    }
-                case BinaryOperator.LessThan:
-                    {
-                        var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
-                        var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
-                        context.PushInstruction(Instruction.Lt(falseIsSkip ? (byte)0 : (byte)1, b, c), node.Position);
-                        return;
-                    }
-                case BinaryOperator.LessThanOrEqual:
-                    {
-                        var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
-                        var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
-                        context.PushInstruction(Instruction.Le(falseIsSkip ? (byte)0 : (byte)1, b, c), node.Position);
-                        return;
-                    }
-                case BinaryOperator.GreaterThan:
-                    {
-                        var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
-                        var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
-                        context.PushInstruction(Instruction.Lt(falseIsSkip ? (byte)0 : (byte)1, c, b), node.Position);
-                        return;
-                    }
-                case BinaryOperator.GreaterThanOrEqual:
-                    {
-                        var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
-                        var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
-                        context.PushInstruction(Instruction.Le(falseIsSkip ? (byte)0 : (byte)1, c, b), node.Position);
-                        return;
-                    }
-            }
-        }
-
-        node.Accept(this, context);
-        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)
-    {
-        var isLastFunction = false;
-        for (int i = 0; i < expressions.Length; i++)
-        {
-            var expression = expressions[i];
-            var isLast = i == expressions.Length - 1;
-            var resultCount = isLast ? (minimumCount == -1 ? -1 : minimumCount - i) : 1;
-
-            if (expression is CallFunctionExpressionNode call)
-            {
-                CompileCallFunctionExpression(call, context, false, resultCount);
-                isLastFunction = isLast;
-            }
-            else if (expression is CallTableMethodExpressionNode method)
-            {
-                CompileTableMethod(method, context, false, resultCount);
-                isLastFunction = isLast;
-            }
-            else if (expression is VariableArgumentsExpressionNode varArg)
-            {
-                CompileVariableArgumentsExpression(varArg, context, resultCount);
-                isLastFunction = isLast;
-            }
-            else if (TryGetConstant(expression, context, out var constant))
-            {
-                var index = context.Function.GetConstantIndex(constant);
-                context.PushInstruction(Instruction.LoadK(context.StackPosition, index), expression.Position, true);
-                isLastFunction = false;
-            }
-            else
-            {
-                expression.Accept(this, context);
-                isLastFunction = false;
-            }
-        }
-
-        // fill space with nil
-        var varCount = minimumCount - expressions.Length;
-        if (varCount > 0 && !isLastFunction)
-        {
-            context.PushInstruction(Instruction.LoadNil(context.StackPosition, (ushort)varCount), rootNode.Position);
-            context.StackPosition = (byte)(context.StackPosition + varCount);
-        }
-    }
-}

+ 1026 - 0
src/Lua/CodeAnalysis/Compilation/Parser.cs

@@ -0,0 +1,1026 @@
+using Lua.Internal;
+using Lua.Runtime;
+using System.Buffers;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+using System.Runtime.CompilerServices;
+using static Lua.Runtime.Instruction;
+using static System.Diagnostics.Debug;
+using static Function;
+using static Scanner;
+using static Constants;
+
+internal class Parser : IPoolNode<Parser>, IDisposable
+{
+    /// inline
+    internal Scanner Scanner;
+
+    internal int T => Scanner.Token.T;
+    internal bool TestNext(int token) => Scanner.TestNext(token);
+    internal void Next() => Scanner.Next();
+
+    internal Function Function = null!;
+    internal FastListCore<int> ActiveVariables;
+    internal FastListCore<Label> PendingGotos;
+    internal FastListCore<Label> ActiveLabels;
+
+    Parser()
+    {
+    }
+
+    Parser? nextNode = null;
+    ref Parser? IPoolNode<Parser>.NextNode => ref nextNode;
+
+    static LinkedPool<Parser> pool;
+
+    static Parser Get(Scanner scanner)
+    {
+        if (!pool.TryPop(out var parser))
+        {
+            parser = new Parser();
+        }
+
+        parser.Scanner = scanner;
+        return parser;
+    }
+
+    void IDisposable.Dispose() => Release();
+
+    public void Release()
+    {
+        ActiveVariables.Clear();
+        PendingGotos.Clear();
+        ActiveLabels.Clear();
+        pool.TryPush(this);
+    }
+
+
+    public void CheckCondition(bool c, string message)
+    {
+        if (!c)
+        {
+            Scanner.SyntaxError(message);
+        }
+    }
+
+
+    public string CheckName()
+    {
+        Scanner.Check(TkName);
+        var s = Scanner.Token.S;
+        Next();
+        return s;
+    }
+
+
+    public void CheckLimit(int val, int limit, string what)
+    {
+        if (val > limit)
+        {
+            string where = "main function";
+            var line = Function!.Proto.LineDefined;
+            if (line != 0)
+            {
+                where = $"function at line {line}";
+            }
+
+            Scanner.SyntaxError($"too many {what} (limit is {limit}) in {where}");
+        }
+    }
+
+
+    public void CheckNext(int t)
+    {
+        Scanner.Check(t);
+        Next();
+    }
+
+
+    public ExprDesc CheckNameAsExpression() => Function!.EncodeString(CheckName());
+
+
+    public ExprDesc SingleVariable() => Function!.SingleVariable(CheckName());
+
+
+    public void LeaveLevel() => Scanner.L.CallCount--;
+
+
+    public void EnterLevel()
+    {
+        Scanner.L.CallCount++;
+        CheckLimit(Scanner.L.CallCount, MaxCallCount, "Go levels");
+    }
+
+
+    public (ExprDesc e, int n) ExpressionList()
+    {
+        var n = 1;
+        var e = Expression();
+        for (; TestNext(','); n++, e = Expression())
+        {
+            Function!.ExpressionToNextRegister(e);
+        }
+
+        return (e, n);
+    }
+
+
+    public (int, int, int, ExprDesc) Field(int tableRegister, int a, int h, int pending, ExprDesc e)
+    {
+        var freeRegisterCount = Function!.FreeRegisterCount;
+
+        void hashField(ExprDesc k)
+        {
+            h++;
+            CheckNext('=');
+            Function!.FlushFieldToConstructor(tableRegister, freeRegisterCount, k, Expression);
+        }
+
+        if (T == TkName && Scanner.LookAhead() == '=')
+        {
+            CheckLimit(h, MaxInt, "items in a constructor");
+            hashField(CheckNameAsExpression());
+        }
+        else if (T == '[')
+        {
+            hashField(this.Index());
+        }
+        else
+        {
+            e = Expression();
+            CheckLimit(a, MaxInt, "items in a constructor");
+            a++;
+            pending++;
+        }
+
+        return (a, h, pending, e);
+    }
+
+
+    public ExprDesc Constructor()
+    {
+        var (pc, t) = Function!.OpenConstructor();
+        var (line, a, h, pending) = (Scanner.LineNumber, 0, 0, 0);
+        ExprDesc e = default;
+        CheckNext('{');
+        if (T != '}')
+        {
+            (a, h, pending, e) = Field(t.Info, a, h, pending, e);
+            while ((TestNext(',') || TestNext(';')) && T != '}')
+            {
+                if (e.Kind != Kind.Void)
+                {
+                    pending = Function!.FlushToConstructor(t.Info, pending, a, e);
+                    e.Kind = Kind.Void;
+                }
+
+                (a, h, pending, e) = Field(t.Info, a, h, pending, e);
+            }
+        }
+
+        Scanner.CheckMatch('}', '{', line);
+        Function!.CloseConstructor(pc, t.Info, pending, a, h, e);
+        return t;
+    }
+
+
+    public ExprDesc FunctionArguments(ExprDesc f, int line)
+    {
+        ExprDesc args = default;
+        switch (T)
+        {
+            case '(':
+                Next();
+                if (T == ')')
+                {
+                    args.Kind = Kind.Void;
+                }
+                else
+                {
+                    (args, _) = ExpressionList();
+                    Function!.SetMultipleReturns(args);
+                }
+
+                Scanner.CheckMatch(')', '(', line);
+                break;
+            case '{':
+                args = Constructor();
+                break;
+            case TkString:
+                args = Function!.EncodeString(Scanner.Token.S);
+                Next();
+                break;
+            default:
+                Scanner.SyntaxError("function arguments expected");
+                break;
+        }
+
+        var (@base, parameterCount) = (f.Info, MultipleReturns);
+        if (!args.HasMultipleReturns())
+        {
+            if (args.Kind != Kind.Void)
+            {
+                Function!.ExpressionToNextRegister(args);
+            }
+
+            parameterCount = Function!.FreeRegisterCount - (@base + 1);
+        }
+
+        var e = MakeExpression(Kind.Call, Function!.EncodeABC(OpCode.Call, @base, parameterCount + 1, 2));
+        Function.FixLine(line);
+        Function.FreeRegisterCount = @base + 1; // call removed function and args & leaves (unless changed) one result
+        return e;
+    }
+
+
+    public ExprDesc PrimaryExpression()
+    {
+        ExprDesc e;
+        switch (T)
+        {
+            case '(':
+                var line = Scanner.LineNumber;
+                Next();
+                e = Expression();
+                Scanner.CheckMatch(')', '(', line);
+                e = Function!.DischargeVariables(e);
+                return e;
+            case TkName:
+                return SingleVariable();
+            default:
+                Scanner.SyntaxError("unexpected symbol");
+                return default;
+        }
+
+        return e;
+    }
+
+
+    public ExprDesc SuffixedExpression()
+    {
+        var line = Scanner.LineNumber;
+        var e = PrimaryExpression();
+        while (true)
+        {
+            switch (T)
+            {
+                case '.':
+                    e = FieldSelector(e);
+                    break;
+                case '[':
+                    e = Function!.Indexed(Function!.ExpressionToAnyRegisterOrUpValue(e), this.Index());
+                    break;
+                case ':':
+                    Next();
+                    e = FunctionArguments(Function!.Self(e, CheckNameAsExpression()), line);
+                    break;
+                case '(':
+                case TkString:
+                case '{':
+                    e = FunctionArguments(Function!.ExpressionToNextRegister(e), line);
+                    break;
+                default:
+                    return e;
+            }
+        }
+    }
+
+
+    public ExprDesc SimpleExpression()
+    {
+        ExprDesc e;
+        switch (T)
+        {
+            case TkNumber:
+                e = MakeExpression(Kind.Number, 0);
+                e.Value = Scanner.Token.N;
+                break;
+            case TkString:
+                e = Function!.EncodeString(Scanner.Token.S);
+                break;
+            case TkNil:
+                e = MakeExpression(Kind.Nil, 0);
+                break;
+            case TkTrue:
+                e = MakeExpression(Kind.True, 0);
+                break;
+            case TkFalse:
+                e = MakeExpression(Kind.False, 0);
+                break;
+            case TkDots:
+                CheckCondition(Function!.Proto.IsVarArg, "cannot use '...' outside a vararg function");
+                e = MakeExpression(Kind.VarArg, Function!.EncodeABC(OpCode.VarArg, 0, 1, 0));
+                break;
+            case '{':
+                e = Constructor();
+                return e;
+            case TkFunction:
+                Next();
+                e = Body(false, Scanner.LineNumber);
+                return e;
+            default:
+                e = SuffixedExpression();
+                return e;
+        }
+
+        Next();
+        return e;
+    }
+
+
+    public static int UnaryOp(int op)
+    {
+        switch (op)
+        {
+            case TkNot:
+                return OprNot;
+            case '-':
+                return OprMinus;
+            case '#':
+                return OprLength;
+        }
+
+        return OprNoUnary;
+    }
+
+
+    public static int BinaryOp(int op)
+    {
+        switch (op)
+        {
+            case '+':
+                return OprAdd;
+            case '-':
+                return OprSub;
+            case '*':
+                return OprMul;
+            case '/':
+                return OprDiv;
+            case '%':
+                return OprMod;
+            case '^':
+                return OprPow;
+            case TkConcat:
+                return OprConcat;
+            case TkNE:
+                return OprNE;
+            case TkEq:
+                return OprEq;
+            case '<':
+                return OprLT;
+            case TkLE:
+                return OprLE;
+            case '>':
+                return OprGT;
+            case TkGE:
+                return OprGE;
+            case TkAnd:
+                return OprAnd;
+            case TkOr:
+                return OprOr;
+        }
+
+        return OprNoBinary;
+    }
+
+
+    static readonly (int Left, int Right)[] priority =
+    [
+        (6, 6), (6, 6), (7, 7), (7, 7), (7, 7),
+        (10, 9), (5, 4),
+        (3, 3), (3, 3), (3, 3),
+        (3, 3), (3, 3), (3, 3),
+        (2, 2), (1, 1)
+    ];
+
+    public static int UnaryPriority => 8;
+
+
+    public (ExprDesc, int ) SubExpression(int limit)
+    {
+        EnterLevel();
+        ExprDesc e = default;
+        int u = UnaryOp(T);
+        if (u != OprNoUnary)
+        {
+            int line = Scanner.LineNumber;
+            Next();
+            (e, _) = SubExpression(UnaryPriority);
+            e = Function.Prefix(u, e, line);
+        }
+        else
+        {
+            e = SimpleExpression();
+        }
+
+        int op = BinaryOp(T);
+        while (op != OprNoBinary && priority[op].Left > limit)
+        {
+            int line = Scanner.LineNumber;
+            Next();
+            e = Function.Infix(op, e);
+            (ExprDesc e2, int next) = SubExpression(priority[op].Right);
+            e = Function.Postfix(op, e, e2, line);
+            op = next;
+        }
+
+        LeaveLevel();
+        return (e, op);
+    }
+
+
+    public ExprDesc Expression()
+    {
+        (ExprDesc e, _) = SubExpression(0);
+        return e;
+    }
+
+
+    public bool BlockFollow(bool withUntil)
+    {
+        switch (T)
+        {
+            case TkElse:
+            case TkElseif:
+            case TkEnd:
+            case TkEOS:
+                return true;
+            case TkUntil:
+                return withUntil;
+        }
+
+        return false;
+    }
+
+
+    public void StatementList()
+    {
+        while (!BlockFollow(true))
+        {
+            if (T == TkReturn)
+            {
+                Statement();
+                return;
+            }
+
+            Statement();
+        }
+    }
+
+
+    public ExprDesc FieldSelector(ExprDesc e)
+    {
+        e = Function.ExpressionToAnyRegisterOrUpValue(e);
+        Next(); // skip dot or colon
+        return Function.Indexed(e, CheckNameAsExpression());
+    }
+
+
+    public ExprDesc Index()
+    {
+        Next(); // skip '['
+        ExprDesc e = Function.ExpressionToValue(Expression());
+        CheckNext(']');
+        return e;
+    }
+
+
+    public void Assignment(AssignmentTarget t, int variableCount)
+    {
+        CheckCondition(t.Description.IsVariable(), "syntax error");
+        if (TestNext(','))
+        {
+            ExprDesc e = SuffixedExpression();
+            if (e.Kind != Kind.Indexed)
+            {
+                Function.CheckConflict(t, e);
+            }
+
+            CheckLimit(variableCount + Scanner.L.CallCount, MaxCallCount, "Go levels");
+            Assignment(new(previous: ref t, exprDesc: e), variableCount + 1);
+        }
+        else
+        {
+            CheckNext('=');
+            var (e, n) = ExpressionList();
+            if (n != variableCount)
+            {
+                Function.AdjustAssignment(variableCount, n, e);
+                if (n > variableCount)
+                {
+                    Function.FreeRegisterCount -= n - variableCount; // remove extra values
+                }
+            }
+            else
+            {
+                Function.StoreVariable(t.Description, Function.SetReturn(e));
+                return; // avoid default
+            }
+        }
+
+        Function.StoreVariable(t.Description, MakeExpression(Kind.NonRelocatable, Function.FreeRegisterCount - 1));
+        //t.Release();
+    }
+
+
+    public void ForBody(int @base, int line, int n, bool isNumeric)
+    {
+        Function.AdjustLocalVariables(3);
+        CheckNext(TkDo);
+        var prep = Function.OpenForBody(@base, n, isNumeric);
+        Block();
+        Function.CloseForBody(prep, @base, line, n, isNumeric);
+    }
+
+
+    public void ForNumeric(string name, int line)
+    {
+        void expr()
+        {
+            var e = Function.ExpressionToNextRegister(Expression());
+            Assert(e.Kind == Kind.NonRelocatable);
+        }
+
+        var @base = Function.FreeRegisterCount;
+        Function.MakeLocalVariable("(for index)");
+        Function.MakeLocalVariable("(for limit)");
+        Function.MakeLocalVariable("(for step)");
+        Function.MakeLocalVariable(name);
+        CheckNext('=');
+        expr();
+        CheckNext(',');
+        expr();
+        if (TestNext(','))
+        {
+            expr();
+        }
+        else
+        {
+            Function.EncodeConstant(Function.FreeRegisterCount, Function.NumberConstant(1));
+            Function.ReserveRegisters(1);
+        }
+
+        ForBody(@base, line, 1, true);
+    }
+
+
+    public void ForList(string name)
+    {
+        var n = 4;
+        var @base = Function.FreeRegisterCount;
+        Function.MakeLocalVariable("(for generator)");
+        Function.MakeLocalVariable("(for state)");
+        Function.MakeLocalVariable("(for control)");
+        Function.MakeLocalVariable(name);
+        while (TestNext(','))
+        {
+            Function.MakeLocalVariable(CheckName());
+            n++;
+        }
+
+        CheckNext(TkIn);
+        var line = Scanner.LineNumber;
+        var (e, c) = ExpressionList();
+        Function.AdjustAssignment(3, c, e);
+        Function.CheckStack(3);
+        ForBody(@base, line, n - 3, false);
+    }
+
+
+    public void ForStatement(int line)
+    {
+        Function.EnterBlock(true);
+        Next();
+        var name = CheckName();
+        switch (T)
+        {
+            case '=':
+                ForNumeric(name, line);
+                break;
+            case ',':
+            case TkIn:
+                ForList(name);
+                break;
+            default:
+                Scanner.SyntaxError("'=' or 'in' expected");
+                break;
+        }
+
+        Scanner.CheckMatch(TkEnd, TkFor, line);
+        Function.LeaveBlock();
+    }
+
+
+    public int TestThenBlock(int escapes)
+    {
+        int jumpFalse;
+        Next();
+        var e = Expression();
+        CheckNext(TkThen);
+        if (T == TkGoto || T == TkBreak)
+        {
+            e = Function.GoIfFalse(e);
+            Function.EnterBlock(false);
+            GotoStatement(e.T);
+            SkipEmptyStatements();
+            if (BlockFollow(false))
+            {
+                Function.LeaveBlock();
+                return escapes;
+            }
+
+            jumpFalse = Function.Jump();
+        }
+        else
+        {
+            e = Function.GoIfTrue(e);
+            Function.EnterBlock(false);
+            jumpFalse = e.F;
+        }
+
+        StatementList();
+        Function.LeaveBlock();
+        if (T is TkElse or TkElseif)
+        {
+            escapes = Function.Concatenate(escapes, Function.Jump());
+        }
+
+        Function.PatchToHere(jumpFalse);
+        return escapes;
+    }
+
+
+    public void IfStatement(int line)
+    {
+        var escapes = TestThenBlock(NoJump);
+        while (T == TkElseif)
+        {
+            escapes = TestThenBlock(escapes);
+        }
+
+        if (TestNext(TkElse))
+        {
+            Block();
+        }
+
+        Scanner.CheckMatch(TkEnd, TkIf, line);
+        Function.PatchToHere(escapes);
+    }
+
+
+    public void Block()
+    {
+        Function.EnterBlock(false);
+        StatementList();
+        Function.LeaveBlock();
+    }
+
+
+    public void WhileStatement(int line)
+    {
+        Next();
+        var top = Function.Label();
+        var conditionExit = Condition();
+        Function.EnterBlock(true);
+        CheckNext(TkDo);
+        Block();
+        Function.JumpTo(top);
+        Scanner.CheckMatch(TkEnd, TkWhile, line);
+        Function.LeaveBlock();
+        Function.PatchToHere(conditionExit);
+    }
+
+
+    public void RepeatStatement(int line)
+    {
+        var top = Function.Label();
+        Function.EnterBlock(true); // loop block
+        Function.EnterBlock(false); // scope block
+        Next();
+        StatementList();
+        Scanner.CheckMatch(TkUntil, TkRepeat, line);
+        var conditionExit = Condition();
+        if (Function.Block.HasUpValue)
+        {
+            Function.PatchClose(conditionExit, Function.Block.ActiveVariableCount);
+        }
+
+        Function.LeaveBlock(); // finish scope
+        Function.PatchList(conditionExit, top); // close loop
+        Function.LeaveBlock(); // finish loop
+    }
+
+
+    public int Condition()
+    {
+        var e = Expression();
+        if (e.Kind == Kind.Nil)
+        {
+            e.Kind = Kind.False;
+        }
+
+        return Function.GoIfTrue(e).F;
+    }
+
+
+    public void GotoStatement(int pc)
+    {
+        var line = Scanner.LineNumber;
+        if (TestNext(TkGoto))
+        {
+            Function.MakeGoto(CheckName(), line, pc);
+        }
+        else
+        {
+            Next();
+            Function.MakeGoto("break", line, pc);
+        }
+    }
+
+
+    public void SkipEmptyStatements()
+    {
+        while (T == ';' || T == TkDoubleColon)
+        {
+            Statement();
+        }
+    }
+
+
+    public void LabelStatement(string label, int line)
+    {
+        Function.CheckRepeatedLabel(label);
+        CheckNext(TkDoubleColon);
+        var l = Function.MakeLabel(label, line);
+        SkipEmptyStatements();
+        if (BlockFollow(false))
+        {
+            ActiveLabels[l].ActiveVariableCount = Function.Block.ActiveVariableCount;
+        }
+
+        Function.FindGotos(l);
+    }
+
+
+    public void ParameterList()
+    {
+        var n = 0;
+        var isVarArg = false;
+        if (T != ')')
+        {
+            for (var first = true; first || (!isVarArg && TestNext(',')); first = false)
+            {
+                switch (T)
+                {
+                    case TkName:
+                        Function.MakeLocalVariable(CheckName());
+                        n++;
+                        break;
+                    case TkDots:
+                        Next();
+                        isVarArg = true;
+                        break;
+                    default:
+                        Scanner.SyntaxError("<name> or '...' expected");
+                        break;
+                }
+            }
+        }
+
+        // TODO the following lines belong in a *function method
+        Function.Proto.IsVarArg = isVarArg;
+        Function.AdjustLocalVariables(n);
+        Function.Proto.ParameterCount = Function.ActiveVariableCount;
+        Function.ReserveRegisters(Function.ActiveVariableCount);
+    }
+
+
+    public ExprDesc Body(bool isMethod, int line)
+    {
+        Function.OpenFunction(line);
+        CheckNext('(');
+        if (isMethod)
+        {
+            Function.MakeLocalVariable("self");
+            Function.AdjustLocalVariables(1);
+        }
+
+        ParameterList();
+        CheckNext(')');
+        StatementList();
+        Function.Proto.LastLineDefined = Scanner.LineNumber;
+        Scanner.CheckMatch(TkEnd, TkFunction, line);
+        return Function.CloseFunction();
+    }
+
+
+    public (ExprDesc, bool IsMethod) FunctionName()
+    {
+        var e = SingleVariable();
+        for (; T == '.'; e = FieldSelector(e)) ;
+        if (T == ':')
+        {
+            e = FieldSelector(e);
+            return (e, true);
+        }
+
+        return (e, false);
+    }
+
+
+    public void FunctionStatement(int line)
+    {
+        Next();
+        var (v, m) = FunctionName();
+        Function.StoreVariable(v, Body(m, line));
+        Function.FixLine(line);
+    }
+
+
+    public void LocalFunction()
+    {
+        Function.MakeLocalVariable(CheckName());
+        Function.AdjustLocalVariables(1);
+        Function.LocalVariable(Body(false, Scanner.LineNumber).Info).StartPc = (Function.Proto.CodeList.Length);
+    }
+
+
+    public void LocalStatement()
+    {
+        var v = 0;
+        for (var first = true; first || TestNext(','); first = false)
+        {
+            Function.MakeLocalVariable(CheckName());
+            v++;
+        }
+
+        if (TestNext('='))
+        {
+            var (e, n) = ExpressionList();
+            Function.AdjustAssignment(v, n, e);
+        }
+        else
+        {
+            var e = default(ExprDesc);
+            Function.AdjustAssignment(v, 0, e);
+        }
+
+        Function.AdjustLocalVariables(v);
+    }
+
+
+    public void ExpressionStatement()
+    {
+        var e = SuffixedExpression();
+        if (T == '=' || T == ',')
+        {
+            Assignment(new AssignmentTarget(ref Unsafe.NullRef<AssignmentTarget>(), exprDesc: e), 1);
+        }
+        else
+        {
+            CheckCondition(e.Kind == Kind.Call, "syntax error");
+            Function.Instruction(e).C = (1); // call statement uses no results
+        }
+    }
+
+
+    public void ReturnStatement()
+    {
+        var f = Function;
+        if (BlockFollow(true) || T == ';')
+        {
+            f.ReturnNone();
+        }
+        else
+        {
+            var (e, n) = ExpressionList();
+            f.Return(e, n);
+        }
+
+        TestNext(';');
+    }
+
+
+    public void Statement()
+    {
+        var line = Scanner.LineNumber;
+        EnterLevel();
+        switch (T)
+        {
+            case ';':
+                Next();
+                break;
+            case TkIf:
+                IfStatement(line);
+                break;
+            case TkWhile:
+                WhileStatement(line);
+                break;
+            case TkDo:
+                Next();
+                Block();
+                Scanner.CheckMatch(TkEnd, TkDo, line);
+                break;
+            case TkFor:
+                ForStatement(line);
+                break;
+            case TkRepeat:
+                RepeatStatement(line);
+                break;
+            case TkFunction:
+                FunctionStatement(line);
+                break;
+            case TkLocal:
+                Next();
+                if (TestNext(TkFunction))
+                {
+                    LocalFunction();
+                }
+                else
+                {
+                    LocalStatement();
+                }
+
+                break;
+            case TkDoubleColon:
+                Next();
+                LabelStatement(CheckName(), line);
+                break;
+            case TkReturn:
+                Next();
+                ReturnStatement();
+                break;
+            case TkBreak:
+            case TkGoto:
+                GotoStatement(Function.Jump());
+                break;
+            default:
+                ExpressionStatement();
+                break;
+        }
+
+        Assert(Function.Proto.MaxStackSize >= Function.FreeRegisterCount && Function.FreeRegisterCount >= Function.ActiveVariableCount);
+        Function.FreeRegisterCount = Function.ActiveVariableCount;
+        LeaveLevel();
+    }
+
+
+    internal void MainFunction()
+    {
+        Function.OpenMainFunction();
+        Next();
+        StatementList();
+        Scanner.Check(TkEOS);
+        Function = Function.CloseMainFunction();
+    }
+
+    public static Prototype Parse(LuaState l, TextReader r, string name)
+    {
+        using var p = Get(new()
+        {
+            R = r,
+            LineNumber = 1,
+            LastLine = 1,
+            LookAheadToken = new() { T = TkEOS },
+            L = l,
+            Buffer = new(),
+            Source = name
+        });
+        var f = Function.Get(p, PrototypeBuilder.Get(name));
+        p.Function = f;
+        p.MainFunction();
+        f.Proto.IsVarArg = true;
+        return f.Proto.CreatePrototypeAndRelease();
+    }
+
+
+    public static void Dump(Prototype prototype, IBufferWriter<byte> writer, bool useLittleEndian = true)
+    {
+        var state = new DumpState(writer, useLittleEndian ^ BitConverter.IsLittleEndian);
+        state.Dump(prototype);
+    }
+
+    public static byte[] Dump(Prototype prototype, bool useLittleEndian = true)
+    {
+        var writer = new ArrayBufferWriter<byte>();
+        Dump(prototype, writer, useLittleEndian);
+        return writer.WrittenSpan.ToArray();
+    }
+
+    public static Prototype UnDump(ReadOnlySpan<byte> span, ReadOnlySpan<char> name)
+    {
+        if (name.Length > 0)
+        {
+            name = name[0] switch
+            {
+                '@' or '=' => name[1..],
+                '\e' => "binary string",
+                _ => name
+            };
+        }
+
+        var state = new UnDumpState(span, name);
+        return state.UnDump();
+    }
+}

+ 77 - 0
src/Lua/CodeAnalysis/Compilation/PrototypeBuilder.cs

@@ -0,0 +1,77 @@
+using Lua.Internal;
+using System;
+using Lua.Runtime;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+internal class PrototypeBuilder : IPoolNode<PrototypeBuilder>
+{
+    internal FastListCore<LuaValue> ConstantsList;
+    public ReadOnlySpan<LuaValue> Constants => ConstantsList.AsSpan();
+    internal FastListCore<Instruction> CodeList;
+    public ReadOnlySpan<Instruction> Code => CodeList.AsSpan();
+    internal FastListCore<PrototypeBuilder> PrototypeList;
+    public ReadOnlySpan<PrototypeBuilder> Prototypes => PrototypeList.AsSpan();
+    internal FastListCore<int> LineInfoList;
+    public ReadOnlySpan<int> LineInfo => LineInfoList.AsSpan();
+    internal FastListCore<LocalVariable> LocalVariablesList;
+    public ReadOnlySpan<LocalVariable> LocalVariables => LocalVariablesList.AsSpan();
+
+    internal FastListCore<UpValueDesc> UpValuesList;
+
+    public ReadOnlySpan<UpValueDesc> UpValues => UpValuesList.AsSpan();
+
+    //public LuaClosure Cache;
+    public string Source;
+    public int LineDefined, LastLineDefined;
+    public int ParameterCount, MaxStackSize;
+    public bool IsVarArg;
+
+
+    internal PrototypeBuilder(string source)
+    {
+        Source = source;
+    }
+
+    static LinkedPool<PrototypeBuilder> pool;
+
+
+    PrototypeBuilder? nextNode;
+    ref PrototypeBuilder? IPoolNode<PrototypeBuilder>.NextNode => ref nextNode;
+
+    internal static PrototypeBuilder Get(string source)
+    {
+        if (!pool.TryPop(out var f))
+        {
+            f = new PrototypeBuilder(source);
+        }
+
+        f.Source = source;
+        return f;
+    }
+
+    internal void Release()
+    {
+        ConstantsList.Clear();
+        CodeList.Clear();
+        PrototypeList.Clear();
+        LineInfoList.Clear();
+        LocalVariablesList.Clear();
+        UpValuesList.Clear();
+        pool.TryPush(this);
+    }
+
+
+    public Prototype CreatePrototypeAndRelease()
+    {
+        var protoTypes = Prototypes.Length == 0 ? Array.Empty<Prototype>() : new Prototype[Prototypes.Length];
+        for (var i = 0; i < Prototypes.Length; i++)
+        {
+            protoTypes[i] = Prototypes[i].CreatePrototypeAndRelease(); //ref
+        }
+
+        var p = new Prototype(Source, LineDefined, LastLineDefined, ParameterCount, MaxStackSize, IsVarArg, Constants.ToArray(), Code.ToArray(), protoTypes, LineInfo.ToArray(), LocalVariables.ToArray(), UpValues.ToArray());
+        Release();
+        return p;
+    }
+}

+ 835 - 0
src/Lua/CodeAnalysis/Compilation/Scanner.cs

@@ -0,0 +1,835 @@
+using Lua.Internal;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using static System.Diagnostics.Debug;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+using static Constants;
+
+internal struct Scanner
+{
+    public LuaState L;
+    public StringBuilder Buffer;
+    public TextReader R;
+    public int Current;
+    public int LineNumber, LastLine;
+    public string Source;
+    public Token LookAheadToken;
+
+    ///inline
+    public Token Token;
+
+    public int T => Token.T;
+
+
+    static string ChunkID(string source)
+    {
+        const int IdSize = 60;
+        switch (source[0])
+        {
+            case '=': // "literal" source
+                if (source.Length <= IdSize)
+                {
+                    return source[1..];
+                }
+
+                return source[1..IdSize];
+            case '@': // file name
+                if (source.Length <= IdSize)
+                {
+                    return source[1..];
+                }
+
+                return "..." + source[1..(IdSize - 3)];
+        }
+
+        source = source.Split('\n')[0];
+        if (source.Length > IdSize - 12)
+        {
+            return "[string \"" + source + "...\"]";
+        }
+
+        return "[string \"" + source + "\"]";
+    }
+
+
+    public const int FirstReserved = ushort.MaxValue + 257;
+    public const int EndOfStream = -1;
+
+    public const int MaxInt = int.MaxValue >> 1 + 1; //9223372036854775807
+
+
+    public const int TkAnd = FirstReserved;
+    public const int TkBreak = TkAnd + 1;
+    public const int TkDo = TkBreak + 1;
+    public const int TkElse = TkDo + 1;
+    public const int TkElseif = TkElse + 1;
+    public const int TkEnd = TkElseif + 1;
+    public const int TkFalse = TkEnd + 1;
+    public const int TkFor = TkFalse + 1;
+    public const int TkFunction = TkFor + 1;
+    public const int TkGoto = TkFunction + 1;
+    public const int TkIf = TkGoto + 1;
+    public const int TkIn = TkIf + 1;
+    public const int TkLocal = TkIn + 1;
+    public const int TkNil = TkLocal + 1;
+    public const int TkNot = TkNil + 1;
+    public const int TkOr = TkNot + 1;
+    public const int TkRepeat = TkOr + 1;
+    public const int TkReturn = TkRepeat + 1;
+    public const int TkThen = TkReturn + 1;
+    public const int TkTrue = TkThen + 1;
+    public const int TkUntil = TkTrue + 1;
+    public const int TkWhile = TkUntil + 1;
+    public const int TkConcat = TkWhile + 1;
+    public const int TkDots = TkConcat + 1;
+    public const int TkEq = TkDots + 1;
+    public const int TkGE = TkEq + 1;
+    public const int TkLE = TkGE + 1;
+    public const int TkNE = TkLE + 1;
+    public const int TkDoubleColon = TkNE + 1;
+    public const int TkEOS = TkDoubleColon + 1;
+    public const int TkNumber = TkEOS + 1;
+    public const int TkName = TkNumber + 1;
+    public const int TkString = TkName + 1;
+
+    public const int ReservedCount = TkWhile - FirstReserved + 1;
+
+
+    static readonly string[] tokens =
+    [
+        "and", "break", "do", "else", "elseif",
+        "end", "false", "for", "function", "goto", "if",
+        "in", "local", "nil", "not", "or", "repeat",
+        "return", "then", "true", "until", "while",
+        "..", "...", "==", ">=", "<=", "~=", "::", "<eof>",
+        "<number>", "<name>", "<string>"
+    ];
+
+    public static ReadOnlySpan<string> Tokens => tokens;
+
+
+    public void SyntaxError(string message) => ScanError(message, Token.T);
+    public void ErrorExpected(char t) => SyntaxError(TokenToString(t) + " expected");
+    public void NumberError() => ScanError("malformed number", TkNumber);
+    public static bool IsNewLine(int c) => c == '\n' || c == '\r';
+
+    public static bool IsDecimal(int c) => '0' <= c && c <= '9';
+
+
+    public static string TokenToString(Token t) => t.T switch
+    {
+        TkName or TkString => t.S,
+        TkNumber => $"{t.N}",
+        < FirstReserved => $"{(char)t.T}", // TODO check for printable rune
+        < TkEOS => $"'{tokens[t.T - FirstReserved]}'",
+        _ => tokens[t.T - FirstReserved]
+    };
+
+    public string TokenToString(int t) => t switch
+    {
+        TkName or TkString => Token.S,
+        TkNumber => $"{Token.N}",
+        < FirstReserved => $"{(char)t}", // TODO check for printable rune
+        < TkEOS => $"'{tokens[t - FirstReserved]}'",
+        _ => tokens[t - FirstReserved]
+    };
+
+    public static string TokenRuteToString(int t) => t switch
+    {
+        < FirstReserved => $"{(char)t}", // TODO check for printable rune
+        <= TkString => $"'{tokens[t - FirstReserved]}'",
+        _ => tokens[t - FirstReserved]
+    };
+
+
+    public void ScanError(string message, int token)
+    {
+        var buff = ChunkID(Source);
+        if (token != 0) message = $"{buff}:{LineNumber}: {message} near {TokenToString(token)}";
+        else message = $"{buff}:{LineNumber}: {message}";
+        throw new(message);
+    }
+
+
+    public void IncrementLineNumber()
+    {
+        var old = Current;
+        Assert(IsNewLine(old));
+        Advance();
+        if (IsNewLine(Current) && Current != old) Advance();
+        if (++LineNumber >= MaxLine) SyntaxError("chunk has too many lines");
+    }
+
+
+    public void Advance()
+    {
+        Current = R.TryRead(out var c) ? c : EndOfStream;
+    }
+
+
+    public void SaveAndAdvance()
+    {
+        Save(Current);
+        Advance();
+    }
+
+
+    public void AdvanceAndSave(int c)
+    {
+        Advance();
+        Save(c);
+    }
+
+
+    public void Save(int c)
+    {
+        Buffer.Append((char)c);
+    }
+
+
+    public bool CheckNext(string str)
+    {
+        if (Current == 0 || !str.Contains((char)Current)) return false;
+        SaveAndAdvance();
+        return true;
+    }
+
+
+    public int SkipSeparator()
+    {
+        var (i, c) = (0, Current);
+        Debug.Assert(c == '[' || c == ']');
+        for (SaveAndAdvance(); Current == '='; i++) SaveAndAdvance();
+        if (Current == c) return i;
+        return -i - 1;
+    }
+
+
+    public string ReadMultiLine(bool comment, int sep)
+    {
+        SaveAndAdvance();
+        if (IsNewLine(Current))
+        {
+            IncrementLineNumber();
+        }
+
+        for (;;)
+        {
+            switch (Current)
+            {
+                case EndOfStream:
+                    if (comment)
+                    {
+                        ScanError("unfinished long comment", 0);
+                    }
+                    else
+                    {
+                        ScanError("unfinished long string", 0);
+                    }
+
+                    break;
+                case ']':
+                    if (SkipSeparator() == sep)
+                    {
+                        SaveAndAdvance();
+                        if (!comment)
+                        {
+                            var s = Buffer.ToString(2 + sep, Buffer.Length - (4 + sep));
+                            Buffer.Clear();
+                            return s;
+                        }
+
+                        Buffer.Clear();
+                        return "";
+                    }
+
+                    break;
+                case '\r':
+                    goto case '\n';
+                case '\n':
+                    Save('\n');
+                    IncrementLineNumber();
+                    break;
+                default:
+                    if (!comment)
+                    {
+                        Save(Current);
+                    }
+
+                    Advance();
+                    break;
+            }
+        }
+    }
+
+
+    public int ReadDigits()
+    {
+        var c = Current;
+        for (; IsDecimal(c); c = Current) SaveAndAdvance();
+        return c;
+    }
+
+
+    public static bool IsHexadecimal(int c) => c is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F';
+
+
+    public (double n, int c, int i) ReadHexNumber(double x)
+    {
+        var c = Current;
+        var n = x;
+        if (!IsHexadecimal(c))
+        {
+            return (n, c, 0);
+        }
+
+        var i = 0;
+        for (;;)
+        {
+            switch (c)
+            {
+                case >= '0' and <= '9':
+                    c = c - '0';
+                    break;
+                case >= 'a' and <= 'f':
+                    c = c - 'a' + 10;
+                    break;
+                case >= 'A' and <= 'F':
+                    c = c - 'A' + 10;
+                    break;
+                default:
+                    return (n, c, i);
+            }
+
+            Advance();
+            (c, n, i) = (Current, n * 16.0 + c, i + 1);
+        }
+    }
+
+
+    public Token ReadNumber()
+    {
+        const int bits64 = 64;
+        const int base10 = 10;
+        var c = Current;
+        Assert(IsDecimal(c));
+        SaveAndAdvance();
+        if (c == '0' && CheckNext("Xx")) // hexadecimal
+        {
+            var prefix = Buffer.ToString();
+            Assert(prefix is "0x" or "0X");
+            Buffer.Clear();
+            var exponent = 0;
+            (var fraction, c, var i) = ReadHexNumber(0);
+            if (c == '.')
+            {
+                Advance();
+                (fraction, c, exponent) = ReadHexNumber(fraction);
+            }
+
+            if (i == 0 && exponent == 0)
+            {
+                NumberError();
+            }
+
+            exponent *= -4;
+            if (c is 'p' or 'P')
+            {
+                Advance();
+                var negativeExponent = false;
+                c = Current;
+                if (c is '+' or '-')
+                {
+                    negativeExponent = c == '-';
+                    Advance();
+                }
+
+                if (!IsDecimal(Current))
+                {
+                    NumberError();
+                }
+
+                _ = ReadDigits();
+
+                if (!long.TryParse(Buffer.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out long e))
+                {
+                    NumberError();
+                }
+                else if (negativeExponent)
+                {
+                    exponent += (int)(-e);
+                }
+                else
+                {
+                    exponent += (int)e;
+                }
+
+                Buffer.Clear();
+            }
+
+            return new() { T = TkNumber, N = (fraction * Math.Pow(2, exponent)) };
+        }
+
+        c = ReadDigits();
+        if (c == '.')
+        {
+            SaveAndAdvance();
+            c = ReadDigits();
+        }
+
+        if (c is 'e' or 'E')
+        {
+            SaveAndAdvance();
+            c = Current;
+            if (c is '+' or '-')
+            {
+                SaveAndAdvance();
+            }
+
+            _ = ReadDigits();
+        }
+
+        var str = Buffer.ToString();
+        if (str.StartsWith("0"))
+        {
+            if (str.Length == 1)
+            {
+                Buffer.Clear();
+                return new() { T = TkNumber, N = 0 };
+            }
+
+            str = str.TrimStart('0');
+            if (!IsDecimal(str[0]))
+            {
+                str = "0" + str;
+            }
+        }
+
+        if (!double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out double f))
+        {
+            NumberError();
+        }
+
+        Buffer.Clear();
+        return new() { T = TkNumber, N = f };
+    }
+
+
+    static readonly Dictionary<int, char> escapes = new()
+    {
+        { 'a', '\a' },
+        { 'b', '\b' },
+        { 'f', '\f' },
+        { 'n', '\n' },
+        { 'r', '\r' },
+        { 't', '\t' },
+        { 'v', '\v' },
+        { '\\', '\\' },
+        { '"', '"' },
+        { '\'', '\'' },
+    };
+
+
+    public void EscapeError(ReadOnlySpan<int> c, string message)
+    {
+        Buffer.Clear();
+        Save('\\');
+        foreach (var r in c)
+        {
+            if (r == EndOfStream)
+            {
+                break;
+            }
+
+            Save(r);
+        }
+
+        ScanError(message, TkString);
+    }
+
+
+    public int ReadHexEscape()
+    {
+        Advance();
+        var r = 0;
+        var b = (stackalloc int[3] { 'x', 0, 0 });
+        var (i, c) = (1, Current);
+        for (; i < b.Length; (i, c, r) = (i + 1, Current, (r << 4) + c))
+        {
+            b[i] = c;
+            switch (c)
+            {
+                case >= '0' and <= '9':
+                    c -= '0';
+                    break;
+                case >= 'a' and <= 'f':
+                    c -= ('a' - 10);
+                    break;
+                case >= 'A' and <= 'F':
+                    c -= ('A' - 10);
+                    break;
+                default:
+                    EscapeError(b.Slice(0, i + 1), "hexadecimal digit expected");
+                    break;
+            }
+
+            Advance();
+        }
+
+        return r;
+    }
+
+
+    public int ReadDecimalEscape()
+    {
+        var b = (stackalloc int[3] { 0, 0, 0 });
+        var c = Current;
+        var r = 0;
+        for (int i = 0; i < b.Length && IsDecimal(c); i++, c = Current)
+        {
+            b[i] = c;
+            r = 10 * r + c - '0';
+            Advance();
+        }
+
+        if (r > 255)
+        {
+            EscapeError(b, "decimal escape too large");
+        }
+
+        return r;
+    }
+
+
+    public Token ReadString()
+    {
+        var delimiter = Current;
+        for (SaveAndAdvance(); Current != delimiter;)
+        {
+            switch (Current)
+            {
+                case EndOfStream:
+                    ScanError("unfinished string", TkEOS);
+                    break;
+                case '\n' or '\r':
+                    ScanError("unfinished string", TkString);
+                    break;
+                case '\\':
+                    Advance();
+                    var c = Current;
+                    if (escapes.TryGetValue(c, out var esc))
+                    {
+                        AdvanceAndSave(esc);
+                    }
+                    else if (IsNewLine(c))
+                    {
+                        IncrementLineNumber();
+                        Save('\n');
+                    }
+                    else if (c == EndOfStream) // do nothing
+                    {
+                    }
+                    else if (c == 'x')
+                    {
+                        Save(ReadHexEscape());
+                    }
+                    else if (c == 'z')
+                    {
+                        for (Advance(); IsWhiteSpace(Current);)
+                        {
+                            if (IsNewLine(Current))
+                            {
+                                IncrementLineNumber();
+                            }
+                            else
+                            {
+                                Advance();
+                            }
+                        }
+                    }
+                    else if (IsDecimal(c))
+                    {
+                        Save(ReadDecimalEscape());
+                    }
+                    else
+                    {
+                        EscapeError([c], "invalid escape sequence");
+                    }
+
+                    break;
+                default:
+                    SaveAndAdvance();
+                    break;
+            }
+        }
+
+        SaveAndAdvance();
+        var length = Buffer.Length - 2;
+        // if (0<length&&Buffer[^2] == '\0')
+        // {
+        //     length--;
+        // }
+        var str = Buffer.ToString(1, length);
+        Buffer.Clear();
+        return new() { T = TkString, S = str };
+    }
+
+
+    public static bool IsReserved(string s)
+    {
+        foreach (var reserved in Tokens)
+        {
+            if (s == reserved)
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    public Token ReservedOrName()
+    {
+        var str = Buffer.ToString();
+        Buffer.Clear();
+        for (var i = 0; i < Tokens.Length; i++)
+        {
+            if (str == Tokens[i])
+            {
+                return new() { T = (i + FirstReserved), S = str };
+            }
+        }
+
+        return new() { T = TkName, S = str };
+    }
+
+
+    public Token Scan()
+    {
+        const bool comment = true, str = false;
+        while (true)
+        {
+            var c = Current;
+            switch (c)
+            {
+                case '\n':
+                case '\r':
+                    IncrementLineNumber();
+                    break;
+                case ' ':
+                case '\f':
+                case '\t':
+                case '\v':
+                    Advance();
+                    break;
+                case '-':
+                    Advance();
+                    if (Current != '-')
+                    {
+                        return new() { T = '-' };
+                    }
+
+                    Advance();
+                    if (Current == '[')
+                    {
+                        var sep = SkipSeparator();
+                        if (sep >= 0)
+                        {
+                            _ = ReadMultiLine(comment, sep);
+                            break;
+                        }
+
+                        Buffer.Clear();
+                    }
+
+
+                    while (!IsNewLine(Current) && (Current != EndOfStream))
+                    {
+                        Advance();
+                    }
+
+                    break;
+                case '[':
+                    {
+                        var sep = SkipSeparator();
+                        if (sep >= 0)
+                        {
+                            return new() { T = TkString, S = ReadMultiLine(str, sep) };
+                        }
+
+                        Buffer.Clear();
+                        if (sep == -1) return new() { T = '[' };
+
+                        ScanError("invalid long string delimiter", TkString);
+                        break;
+                    }
+                case '=':
+                    Advance();
+                    if (Current != '=')
+                    {
+                        return new() { T = '=' };
+                    }
+
+                    Advance();
+                    return new() { T = TkEq };
+                case '<':
+                    Advance();
+                    if (Current != '=')
+                    {
+                        return new() { T = '<' };
+                    }
+
+                    Advance();
+                    return new() { T = TkLE };
+                case '>':
+                    Advance();
+                    if (Current != '=')
+                    {
+                        return new() { T = '>' };
+                    }
+
+                    Advance();
+                    return new() { T = TkGE };
+                case '~':
+                    Advance();
+                    if (Current != '=')
+                    {
+                        return new() { T = '~' };
+                    }
+
+                    Advance();
+                    return new() { T = TkNE };
+                case ':':
+                    Advance();
+                    if (Current != ':')
+                    {
+                        return new() { T = ':' };
+                    }
+
+                    Advance();
+                    return new() { T = TkDoubleColon };
+                case '"':
+                case '\'':
+                    return ReadString();
+                case EndOfStream:
+                    return new() { T = TkEOS };
+                case '.':
+                    SaveAndAdvance();
+                    if (CheckNext("."))
+                    {
+                        if (CheckNext("."))
+                        {
+                            Buffer.Clear();
+                            return new() { T = TkDots };
+                        }
+
+                        Buffer.Clear();
+                        return new() { T = TkConcat };
+                    }
+
+                    if (!IsDigit(Current))
+                    {
+                        Buffer.Clear();
+                        return new() { T = '.' };
+                    }
+
+                    return ReadNumber();
+                case 0:
+                    Advance();
+                    break;
+                default:
+                    {
+                        if (IsDigit(c))
+                        {
+                            return ReadNumber();
+                        }
+
+                        if (c == '_' || IsLetter(c))
+                        {
+                            for (; c == '_' || IsLetter(c) || IsDigit(c); c = Current)
+                            {
+                                SaveAndAdvance();
+                            }
+
+                            return ReservedOrName();
+                        }
+
+                        Advance();
+                        return new() { T = c };
+                    }
+            }
+        }
+    }
+
+
+    public void Next()
+    {
+        LastLine = LineNumber;
+        if (LookAheadToken.T != TkEOS)
+        {
+            Token = LookAheadToken;
+            LookAheadToken.T = TkEOS;
+        }
+        else
+        {
+            Token = Scan();
+        }
+    }
+
+
+    public int LookAhead()
+    {
+        Assert(LookAheadToken.T == TkEOS);
+        LookAheadToken = Scan();
+        return LookAheadToken.T;
+    }
+
+
+    public bool TestNext(int t)
+    {
+        var r = Token.T == t;
+        if (r)
+        {
+            Next();
+        }
+
+        return r;
+    }
+
+
+    public void Check(int t)
+    {
+        if (Token.T != t)
+        {
+            ErrorExpected((char)t);
+        }
+    }
+
+
+    public void CheckMatch(int what, int who, int where)
+    {
+        if (!TestNext(what))
+        {
+            if (where == LineNumber)
+            {
+                ErrorExpected((char)what);
+            }
+            else
+            {
+                SyntaxError($"{TokenToString(what)} expected (to close {TokenToString(who)} at line {where})");
+            }
+        }
+    }
+
+    static bool IsWhiteSpace(int c) => c == ' ' || c == '\t' || c == '\n' || c == '\r';
+    static bool IsDigit(int c) => c is >= '0' and <= '9';
+
+    static bool IsLetter(int c)
+    {
+        return c < ushort.MaxValue && (char.IsLetter((char)c));
+    }
+}

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

@@ -1,188 +0,0 @@
-using System.Collections.Concurrent;
-using System.Runtime.CompilerServices;
-using Lua.Internal;
-using Lua.Runtime;
-
-namespace Lua.CodeAnalysis.Compilation;
-
-public class ScopeCompilationContext : IDisposable
-{
-    static class Pool
-    {
-        static ConcurrentStack<ScopeCompilationContext> stack = new();
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static ScopeCompilationContext Rent()
-        {
-            if (!stack.TryPop(out var context))
-            {
-                context = new();
-            }
-
-            return context;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Return(ScopeCompilationContext context)
-        {
-            context.Reset();
-            stack.Push(context);
-        }
-    }
-
-    readonly Dictionary<ReadOnlyMemory<char>, LocalVariableDescription> localVariables = new(256, Utf16StringMemoryComparer.Default);
-    readonly Dictionary<ReadOnlyMemory<char>, LabelDescription> labels = new(32, Utf16StringMemoryComparer.Default);
-
-    internal BitFlags256 ActiveLocalVariables = default;
-
-    public byte StackStartPosition { get; private set; }
-    public byte StackPosition { get; set; }
-
-    public byte StackTopPosition
-    {
-        get => (byte)(StackPosition - 1);
-    }
-
-    public bool HasCapturedLocalVariables { get; internal set; }
-
-    /// <summary>
-    /// Function context
-    /// </summary>
-    public FunctionCompilationContext Function { get; internal set; } = default!;
-
-    /// <summary>
-    /// Parent scope context
-    /// </summary>
-    public ScopeCompilationContext? Parent { get; private set; }
-
-    public ScopeCompilationContext CreateChildScope()
-    {
-        var childScope = Pool.Rent();
-        childScope.Parent = this;
-        childScope.Function = Function;
-        childScope.StackStartPosition = StackPosition;
-        childScope.StackPosition = StackPosition;
-        return childScope;
-    }
-
-    public FunctionCompilationContext CreateChildFunction()
-    {
-        var context = FunctionCompilationContext.Create(this);
-        return context;
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void PushInstruction(in Instruction instruction, SourcePosition position, bool incrementStackPosition = false)
-    {
-        Function.PushOrMergeInstruction(instruction, position, ref incrementStackPosition);
-        if (incrementStackPosition)
-        {
-            StackPosition++;
-        }
-        Function.MaxStackPosition = Math.Max(Function.MaxStackPosition, StackPosition);
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void TryPushCloseUpValue(byte top, SourcePosition position)
-    {
-        if (HasCapturedLocalVariables && top != 0)
-        {
-            Function.PushInstruction(Instruction.Jmp(top, 0), position);
-        }
-    }
-
-    /// <summary>
-    /// Add new local variable.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void AddLocalVariable(ReadOnlyMemory<char> name, LocalVariableDescription description, bool markAsLastLocalVariable = true)
-    {
-        localVariables[name] = description;
-        ActiveLocalVariables.Set(description.RegisterIndex);
-    }
-
-
-    /// <summary>
-    /// Gets the local variable in scope.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public bool TryGetLocalVariable(ReadOnlyMemory<char> name, out LocalVariableDescription description)
-    {
-        if (localVariables.TryGetValue(name, out description)) return true;
-
-        // Find local variables defined in the same function
-        if (Parent != null)
-        {
-            return Parent.TryGetLocalVariable(name, out description);
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    /// Gets the local variable in this scope.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public bool TryGetLocalVariableInThisScope(ReadOnlyMemory<char> name, out LocalVariableDescription description)
-    {
-        return localVariables.TryGetValue(name, out description);
-    }
-
-    /// <summary>
-    /// Add new label.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void AddLabel(LabelDescription description)
-    {
-        labels.Add(description.Name, description);
-    }
-
-    /// <summary>
-    /// Gets the label in scope.
-    /// </summary>
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public bool TryGetLabel(ReadOnlyMemory<char> name, out LabelDescription description)
-    {
-        if (labels.TryGetValue(name, out description)) return true;
-
-        // Find labels defined in the same function
-        if (Parent != null)
-        {
-            return Parent.TryGetLabel(name, out description);
-        }
-
-        return false;
-    }
-    
-    public void RegisterLocalsToFunction()
-    {
-        foreach (var localVariable in localVariables)
-        {
-            Function.AddLocalVariable(localVariable.Key, localVariable.Value);
-        }
-    }
-
-    /// <summary>
-    /// Resets the values ​​held in the context.
-    /// </summary>
-    public void Reset()
-    {
-        Parent = null;
-        StackStartPosition = 0;
-        StackPosition = 0;
-        HasCapturedLocalVariables = false;
-        localVariables.Clear();
-        labels.Clear();
-        ActiveLocalVariables = default;
-    }
-
-    /// <summary>
-    /// Returns the context object to the pool.
-    /// </summary>
-    public void Dispose()
-    {
-        RegisterLocalsToFunction();
-        Function = null!;
-        Pool.Return(this);
-    }
-}

+ 17 - 0
src/Lua/CodeAnalysis/Compilation/Token.cs

@@ -0,0 +1,17 @@
+using System.Diagnostics;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+[DebuggerDisplay("{DebuggerDisplay}")]
+internal struct Token
+{
+    public int T;
+    public double N;
+    public string S;
+    string DebuggerDisplay => $"{Scanner.TokenToString(this)} {T} {N} {S}";
+
+    public static implicit operator Token(int token)
+    {
+        return new Token { T = token };
+    }
+}

+ 7 - 0
src/Lua/CodeAnalysis/LocalVariable.cs

@@ -0,0 +1,7 @@
+namespace Lua.CodeAnalysis;
+
+public record struct LocalVariable
+{
+    public string Name;
+    public int StartPc, EndPc;
+}

+ 0 - 14
src/Lua/CodeAnalysis/SourcePosition.cs

@@ -1,14 +0,0 @@
-namespace Lua.CodeAnalysis;
-
-public record struct SourcePosition
-{
-    public SourcePosition(int line, int column)
-    {
-        Line = line;
-        Column = column;
-    }
-
-    public int Line { get; set; }
-    public int Column { get; set; }
-    public override readonly string ToString() => $"({Line},{Column})";
-}

+ 0 - 555
src/Lua/CodeAnalysis/Syntax/DisplayStringSyntaxVisitor.cs

@@ -1,555 +0,0 @@
-using System.Text;
-using Lua.CodeAnalysis.Syntax.Nodes;
-
-namespace Lua.CodeAnalysis.Syntax;
-
-public sealed class DisplayStringSyntaxVisitor : ISyntaxNodeVisitor<DisplayStringSyntaxVisitor.Context, bool>
-{
-    public sealed class Context
-    {
-        public readonly ref struct IndentScope
-        {
-            readonly Context source;
-
-            public IndentScope(Context source)
-            {
-                this.source = source;
-                source.IncreaseIndent();
-            }
-
-            public void Dispose()
-            {
-                source.DecreaseIndent();
-            }
-        }
-
-        readonly StringBuilder buffer = new();
-        int indentLevel;
-        bool isNewLine = true;
-
-        public IndentScope BeginIndentScope() => new(this);
-
-        public void Append(string value)
-        {
-            if (isNewLine)
-            {
-                buffer.Append(' ', indentLevel * 4);
-                isNewLine = false;
-            }
-            buffer.Append(value);
-        }
-
-        public void AppendLine(string value)
-        {
-            if (isNewLine)
-            {
-                buffer.Append(' ', indentLevel * 4);
-                isNewLine = false;
-            }
-
-            buffer.AppendLine(value);
-            isNewLine = true;
-        }
-
-        public void AppendLine()
-        {
-            buffer.AppendLine();
-            isNewLine = true;
-        }
-
-        public override string ToString() => buffer.ToString();
-
-        public void IncreaseIndent()
-        {
-            indentLevel++;
-        }
-
-        public void DecreaseIndent()
-        {
-            if (indentLevel > 0)
-                indentLevel--;
-        }
-
-        public void Reset()
-        {
-            buffer.Clear();
-            indentLevel = 0;
-            isNewLine = true;
-        }
-    }
-
-    readonly Context context = new();
-
-    public string GetDisplayString(SyntaxNode node)
-    {
-        context.Reset();
-        node.Accept(this, context);
-        return context.ToString();
-    }
-
-    public bool VisitBinaryExpressionNode(BinaryExpressionNode node, Context context)
-    {
-        node.LeftNode.Accept(this, context);
-        context.Append($" {node.OperatorType.ToDisplayString()} ");
-        node.RightNode.Accept(this, context);
-        return true;
-    }
-
-    public bool VisitBooleanLiteralNode(BooleanLiteralNode node, Context context)
-    {
-        context.Append(node.Value ? Keywords.True : Keywords.False);
-        return true;
-    }
-
-    public bool VisitBreakStatementNode(BreakStatementNode node, Context context)
-    {
-        context.Append(Keywords.Break);
-        return true;
-    }
-
-    public bool VisitCallFunctionExpressionNode(CallFunctionExpressionNode node, Context context)
-    {
-        node.FunctionNode.Accept(this, context);
-        context.Append("(");
-        VisitSyntaxNodes(node.ArgumentNodes, context);
-        context.Append(")");
-        return true;
-    }
-
-    public bool VisitCallFunctionStatementNode(CallFunctionStatementNode node, Context context)
-    {
-        node.Expression.Accept(this, context);
-        return true;
-    }
-
-    public bool VisitDoStatementNode(DoStatementNode node, Context context)
-    {
-        context.AppendLine("do");
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.StatementNodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, Context context)
-    {
-        context.Append("function(");
-        VisitSyntaxNodes(node.ParameterNodes, context);
-        if (node.HasVariableArguments)
-        {
-            if (node.ParameterNodes.Length > 0) context.Append(", ");
-            context.Append("...");
-        }
-        context.AppendLine(")");
-
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.Nodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, Context context)
-    {
-        context.Append("function ");
-        context.Append(node.Name.ToString());
-        context.Append("(");
-        VisitSyntaxNodes(node.ParameterNodes, context);
-        if (node.HasVariableArguments)
-        {
-            if (node.ParameterNodes.Length > 0) context.Append(", ");
-            context.Append("...");
-        }
-        context.AppendLine(")");
-
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.Nodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, Context context)
-    {
-        context.Append("function ");
-
-        for (int i = 0; i < node.MemberPath.Length; i++)
-        {
-            context.Append(node.MemberPath[i].Name.ToString());
-
-            if (i == node.MemberPath.Length - 2 && node.HasSelfParameter)
-            {
-                context.Append(":");
-            }
-            else if (i != node.MemberPath.Length - 1)
-            {
-                context.Append(".");
-            }
-        }
-
-        context.Append("(");
-        VisitSyntaxNodes(node.ParameterNodes, context);
-        if (node.HasVariableArguments)
-        {
-            if (node.ParameterNodes.Length > 0) context.Append(", ");
-            context.Append("...");
-        }
-        context.AppendLine(")");
-
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.Nodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitGenericForStatementNode(GenericForStatementNode node, Context context)
-    {
-        context.Append($"for ");
-        VisitSyntaxNodes(node.Names, context);
-        context.Append(" in ");
-        VisitSyntaxNodes(node.ExpressionNodes, context);
-        context.AppendLine(" do");
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.StatementNodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitGotoStatementNode(GotoStatementNode node, Context context)
-    {
-        context.Append($"goto {node.Name}");
-        return true;
-    }
-
-    public bool VisitIdentifierNode(IdentifierNode node, Context context)
-    {
-        context.Append(node.Name.ToString());
-        return true;
-    }
-
-    public bool VisitIfStatementNode(IfStatementNode node, Context context)
-    {
-        context.Append("if ");
-        node.IfNode.ConditionNode.Accept(this, context);
-        context.AppendLine(" then");
-
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.IfNode.ThenNodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-
-        foreach (var elseif in node.ElseIfNodes)
-        {
-            context.Append("elseif ");
-            elseif.ConditionNode.Accept(this, context);
-            context.AppendLine(" then");
-
-            using (context.BeginIndentScope())
-            {
-                foreach (var childNode in elseif.ThenNodes)
-                {
-                    childNode.Accept(this, context);
-                    context.AppendLine();
-                }
-            }
-        }
-
-        if (node.ElseNodes.Length > 0)
-        {
-            context.AppendLine("else");
-
-            using (context.BeginIndentScope())
-            {
-                foreach (var childNode in node.ElseNodes)
-                {
-                    childNode.Accept(this, context);
-                    context.AppendLine();
-                }
-            }
-        }
-
-        context.Append("end");
-
-        return true;
-    }
-
-    public bool VisitLabelStatementNode(LabelStatementNode node, Context context)
-    {
-        context.Append($"::{node.Name}::");
-        return true;
-    }
-
-    public bool VisitAssignmentStatementNode(AssignmentStatementNode node, Context context)
-    {
-        VisitSyntaxNodes(node.LeftNodes, context);
-
-        if (node.RightNodes.Length > 0)
-        {
-            context.Append(" = ");
-            VisitSyntaxNodes(node.RightNodes, context);
-        }
-
-        return true;
-    }
-
-    public bool VisitLocalAssignmentStatementNode(LocalAssignmentStatementNode node, Context context)
-    {
-        context.Append("local ");
-        return VisitAssignmentStatementNode(node, context);
-    }
-
-    public bool VisitLocalFunctionDeclarationStatementNode(LocalFunctionDeclarationStatementNode node, Context context)
-    {
-        context.Append("local ");
-        return VisitFunctionDeclarationStatementNode(node, context);
-    }
-
-    public bool VisitNilLiteralNode(NilLiteralNode node, Context context)
-    {
-        context.Append(Keywords.Nil);
-        return true;
-    }
-
-    public bool VisitNumericForStatementNode(NumericForStatementNode node, Context context)
-    {
-        context.Append($"for {node.VariableName} = ");
-        node.InitNode.Accept(this, context);
-        context.Append(", ");
-        node.LimitNode.Accept(this, context);
-        if (node.StepNode != null)
-        {
-            context.Append(", ");
-            node.StepNode.Accept(this, context);
-        }
-
-        context.AppendLine(" do");
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.StatementNodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitNumericLiteralNode(NumericLiteralNode node, Context context)
-    {
-        context.Append(node.Value.ToString());
-        return true;
-    }
-
-    public bool VisitRepeatStatementNode(RepeatStatementNode node, Context context)
-    {
-        context.AppendLine("repeat");
-
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.Nodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-
-        context.Append("until ");
-        node.ConditionNode.Accept(this, context);
-        context.AppendLine();
-
-        return true;
-    }
-
-    public bool VisitReturnStatementNode(ReturnStatementNode node, Context context)
-    {
-        context.Append("return ");
-        VisitSyntaxNodes(node.Nodes, context);
-        return true;
-    }
-
-    public bool VisitStringLiteralNode(StringLiteralNode node, Context context)
-    {
-        if (node.IsShortLiteral)
-        {
-            context.Append("\"");
-            context.Append(node.Text.ToString());
-            context.Append("\"");
-        }
-        else
-        {
-            context.Append("[[");
-            context.Append(node.Text.ToString());
-            context.Append("]]");
-        }
-        return true;
-    }
-
-    public bool VisitSyntaxTree(LuaSyntaxTree node, Context context)
-    {
-        foreach (var statement in node.Nodes)
-        {
-            statement.Accept(this, context);
-            context.AppendLine();
-        }
-
-        return true;
-    }
-
-    public bool VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, Context context)
-    {
-        context.AppendLine("{");
-        using (context.BeginIndentScope())
-        {
-            for (int i = 0; i < node.Fields.Length; i++)
-            {
-                var field = node.Fields[i];
-
-                switch (field)
-                {
-                    case GeneralTableConstructorField general:
-                        context.Append("[");
-                        general.KeyExpression.Accept(this, context);
-                        context.Append("] = ");
-                        general.ValueExpression.Accept(this, context);
-                        break;
-                    case RecordTableConstructorField record:
-                        context.Append($"{record.Key} = ");
-                        record.ValueExpression.Accept(this, context);
-                        break;
-                    case ListTableConstructorField list:
-                        list.Expression.Accept(this, context);
-                        break;
-                }
-
-                context.AppendLine(i == node.Fields.Length - 1 ? "" : ",");
-            }
-        }
-        context.AppendLine("}");
-
-        return true;
-    }
-
-    public bool VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, Context context)
-    {
-        node.TableNode.Accept(this, context);
-        context.Append("[");
-        node.KeyNode.Accept(this, context);
-        context.Append("]");
-        return true;
-    }
-
-    public bool VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, Context context)
-    {
-        node.TableNode.Accept(this, context);
-        context.Append($".{node.MemberName}");
-        return true;
-    }
-
-    public bool VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, Context context)
-    {
-        node.TableNode.Accept(this, context);
-        context.Append($":{node.MethodName}(");
-        VisitSyntaxNodes(node.ArgumentNodes, context);
-        context.Append(")");
-        return true;
-    }
-
-    public bool VisitCallTableMethodStatementNode(CallTableMethodStatementNode node, Context context)
-    {
-        return node.Expression.Accept(this, context);
-    }
-
-    public bool VisitUnaryExpressionNode(UnaryExpressionNode node, Context context)
-    {
-        context.Append(node.Operator.ToDisplayString());
-        if (node.Operator is UnaryOperator.Not) context.Append(" ");
-        node.Node.Accept(this, context);
-
-        return true;
-    }
-
-    public bool VisitWhileStatementNode(WhileStatementNode node, Context context)
-    {
-        context.Append("while ");
-        node.ConditionNode.Accept(this, context);
-        context.AppendLine(" do");
-
-        using (context.BeginIndentScope())
-        {
-            foreach (var childNode in node.Nodes)
-            {
-                childNode.Accept(this, context);
-                context.AppendLine();
-            }
-        }
-
-        context.AppendLine("end");
-
-        return true;
-    }
-
-    public bool VisitVariableArgumentsExpressionNode(VariableArgumentsExpressionNode node, Context context)
-    {
-        context.Append("...");
-        return true;
-    }
-
-    void VisitSyntaxNodes(SyntaxNode[] nodes, Context context)
-    {
-        for (int i = 0; i < nodes.Length; i++)
-        {
-            nodes[i].Accept(this, context);
-            if (i != nodes.Length - 1) context.Append(", ");
-        }
-    }
-
-    public bool VisitGroupedExpressionNode(GroupedExpressionNode node, Context context)
-    {
-        context.Append("(");
-        node.Expression.Accept(this, context);
-        context.Append(")");
-        return true;
-    }
-}

+ 0 - 40
src/Lua/CodeAnalysis/Syntax/ISyntaxNodeVisitor.cs

@@ -1,40 +0,0 @@
-using Lua.CodeAnalysis.Syntax.Nodes;
-
-namespace Lua.CodeAnalysis.Syntax;
-
-public interface ISyntaxNodeVisitor<TContext, TResult>
-{
-    TResult VisitNumericLiteralNode(NumericLiteralNode node, TContext context);
-    TResult VisitBooleanLiteralNode(BooleanLiteralNode node, TContext context);
-    TResult VisitNilLiteralNode(NilLiteralNode node, TContext context);
-    TResult VisitStringLiteralNode(StringLiteralNode node, TContext context);
-    TResult VisitUnaryExpressionNode(UnaryExpressionNode node, TContext context);
-    TResult VisitBinaryExpressionNode(BinaryExpressionNode node, TContext context);
-    TResult VisitGroupedExpressionNode(GroupedExpressionNode node, TContext context);
-    TResult VisitIdentifierNode(IdentifierNode node, TContext context);
-    TResult VisitDoStatementNode(DoStatementNode node, TContext context);
-    TResult VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, TContext context);
-    TResult VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, TContext context);
-    TResult VisitLocalFunctionDeclarationStatementNode(LocalFunctionDeclarationStatementNode node, TContext context);
-    TResult VisitWhileStatementNode(WhileStatementNode node, TContext context);
-    TResult VisitRepeatStatementNode(RepeatStatementNode node, TContext context);
-    TResult VisitIfStatementNode(IfStatementNode node, TContext context);
-    TResult VisitLabelStatementNode(LabelStatementNode node, TContext context);
-    TResult VisitGotoStatementNode(GotoStatementNode node, TContext context);
-    TResult VisitBreakStatementNode(BreakStatementNode node, TContext context);
-    TResult VisitReturnStatementNode(ReturnStatementNode node, TContext context);
-    TResult VisitAssignmentStatementNode(AssignmentStatementNode node, TContext context);
-    TResult VisitLocalAssignmentStatementNode(LocalAssignmentStatementNode node, TContext context);
-    TResult VisitCallFunctionExpressionNode(CallFunctionExpressionNode node, TContext context);
-    TResult VisitCallFunctionStatementNode(CallFunctionStatementNode node, TContext context);
-    TResult VisitNumericForStatementNode(NumericForStatementNode node, TContext context);
-    TResult VisitGenericForStatementNode(GenericForStatementNode node, TContext context);
-    TResult VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, TContext context);
-    TResult VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, TContext context);
-    TResult VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, TContext context);
-    TResult VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, TContext context);
-    TResult VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, TContext context);
-    TResult VisitCallTableMethodStatementNode(CallTableMethodStatementNode node, TContext context);
-    TResult VisitVariableArgumentsExpressionNode(VariableArgumentsExpressionNode node, TContext context);
-    TResult VisitSyntaxTree(LuaSyntaxTree node, TContext context);
-}

+ 0 - 61
src/Lua/CodeAnalysis/Syntax/Keywords.cs

@@ -1,61 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax;
-
-internal static class Keywords
-{
-    public const string LF = "\n";
-
-    public const string LParen = "(";
-    public const string RParen = ")";
-    public const string LCurly = "{";
-    public const string RCurly = "}";
-    public const string LSquare = "[";
-    public const string RSquare = "]";
-
-    public const string Assignment = "=";
-
-    public const string Nil = "nil";
-    public const string True = "true";
-    public const string False = "false";
-
-    public const string Addition = "+";
-    public const string Subtraction = "-";
-    public const string Multiplication = "*";
-    public const string Division = "/";
-    public const string Modulo = "%";
-    public const string Exponentiation = "^";
-
-    public const string Length = "#";
-    public const string Concat = "..";
-
-    public const string Equality = "==";
-    public const string Inequality = "~=";
-    public const string GreaterThan = ">";
-    public const string GreaterThanOrEqual = ">=";
-    public const string LessThan = "<";
-    public const string LessThanOrEqual = "<=";
-
-    public const string And = "and";
-    public const string Or = "or";
-    public const string Not = "not";
-
-    public const string Do = "do";
-    public const string End = "end";
-    public const string Then = "then";
-
-    public const string If = "if";
-    public const string ElseIf = "elseif";
-    public const string Else = "else";
-
-    public const string Return = "return";
-    public const string Break = "break";
-    public const string Goto = "goto";
-
-    public const string For = "for";
-    public const string In = "in";
-    public const string While = "while";
-    public const string Repeat = "repeat";
-    public const string Until = "until";
-
-    public const string Function = "function";
-    public const string Local = "local";
-}

+ 0 - 543
src/Lua/CodeAnalysis/Syntax/Lexer.cs

@@ -1,543 +0,0 @@
-using System.Runtime.CompilerServices;
-using Lua.Internal;
-
-namespace Lua.CodeAnalysis.Syntax;
-
-public ref struct Lexer
-{
-    public required ReadOnlyMemory<char> Source { get; init; }
-    public string? ChunkName { get; init; }
-
-    SyntaxToken current;
-    SourcePosition position = new(1, 0);
-    int offset;
-
-    public Lexer()
-    {
-    }
-
-    public readonly SyntaxToken Current => current;
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void Advance(int count)
-    {
-        var span = Source.Span;
-        for (int i = 0; i < count; i++)
-        {
-            if (offset >= span.Length)
-            {
-                LuaParseException.SyntaxError(ChunkName, position, null);
-            }
-
-            var c = span[offset];
-            offset++;
-
-            var isLF = c is '\n';
-            var isCR = c is '\r' && (span.Length == offset || span[offset] is not '\n');
-
-            if (isLF || isCR)
-            {
-                position.Column = 0;
-                position.Line++;
-            }
-            else
-            {
-                position.Column++;
-            }
-        }
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    bool TryRead(int offset, out char value)
-    {
-        if (Source.Length <= offset)
-        {
-            value = default;
-            return false;
-        }
-
-        value = Source.Span[offset];
-        return true;
-    }
-
-    public bool MoveNext()
-    {
-        if (Source.Length <= offset) return false;
-
-        var span = Source.Span;
-        var startOffset = offset;
-        var position = this.position;
-
-        var c1 = span[offset];
-        Advance(1);
-        var c2 = span.Length == offset ? char.MinValue : span[offset];
-
-        switch (c1)
-        {
-            case ' ':
-            case '\t':
-                return MoveNext();
-            case '\n':
-                current = SyntaxToken.EndOfLine(position);
-                return true;
-            case '\r':
-                if (c2 == '\n') Advance(1);
-                current = SyntaxToken.EndOfLine(position);
-                return true;
-            case '(':
-                current = SyntaxToken.LParen(position);
-                return true;
-            case ')':
-                current = SyntaxToken.RParen(position);
-                return true;
-            case '{':
-                current = SyntaxToken.LCurly(position);
-                return true;
-            case '}':
-                current = SyntaxToken.RCurly(position);
-                return true;
-            case ']':
-                current = SyntaxToken.RSquare(position);
-                return true;
-            case '+':
-                current = SyntaxToken.Addition(position);
-                return true;
-            case '-':
-                // comment
-                if (c2 == '-')
-                {
-                    var pos = position;
-                    Advance(1);
-
-                    // block comment
-                    if (span.Length > offset + 1 && span[offset] is '[' && span[offset + 1] is '[' or '=')
-                    {
-                        Advance(1);
-                        (_, _, var isTerminated) = ReadUntilLongBracketEnd(ref span);
-                        if (!isTerminated) LuaParseException.UnfinishedLongComment(ChunkName, pos);
-                    }
-                    else // line comment
-                    {
-                        ReadUntilEOL(ref span, ref offset, out _);
-                    }
-
-                    return MoveNext();
-                }
-                else
-                {
-                    current = SyntaxToken.Subtraction(position);
-                    return true;
-                }
-            case '*':
-                current = SyntaxToken.Multiplication(position);
-                return true;
-            case '/':
-                current = SyntaxToken.Division(position);
-                return true;
-            case '%':
-                current = SyntaxToken.Modulo(position);
-                return true;
-            case '^':
-                current = SyntaxToken.Exponentiation(position);
-                return true;
-            case '=':
-                if (c2 == '=')
-                {
-                    current = SyntaxToken.Equality(position);
-                    Advance(1);
-                }
-                else
-                {
-                    current = SyntaxToken.Assignment(position);
-                }
-                return true;
-            case '~':
-                if (c2 == '=')
-                {
-                    current = SyntaxToken.Inequality(position);
-                    Advance(1);
-                }
-                else
-                {
-                    throw new LuaParseException(ChunkName, position, $"error: Invalid '~' token");
-                }
-                return true;
-            case '>':
-                if (c2 == '=')
-                {
-                    current = SyntaxToken.GreaterThanOrEqual(position);
-                    Advance(1);
-                }
-                else
-                {
-                    current = SyntaxToken.GreaterThan(position);
-                }
-                return true;
-            case '<':
-                if (c2 == '=')
-                {
-                    current = SyntaxToken.LessThanOrEqual(position);
-                    Advance(1);
-                }
-                else
-                {
-                    current = SyntaxToken.LessThan(position);
-                }
-                return true;
-            case '.':
-                if (c2 == '.')
-                {
-                    var c3 = span.Length == (offset + 1) ? char.MinValue : span[offset + 1];
-
-                    if (c3 == '.')
-                    {
-                        // vararg
-                        current = SyntaxToken.VarArg(position);
-                        Advance(2);
-                    }
-                    else
-                    {
-                        // concat
-                        current = SyntaxToken.Concat(position);
-                        Advance(1);
-                    }
-
-                    return true;
-                }
-
-                if (!StringHelper.IsNumber(c2))
-                {
-                    current = SyntaxToken.Dot(position);
-                    return true;
-                }
-
-                break;
-            case '#':
-                current = SyntaxToken.Length(position);
-                return true;
-            case ',':
-                current = SyntaxToken.Comma(position);
-                return true;
-            case ';':
-                current = SyntaxToken.SemiColon(position);
-                return true;
-        }
-
-        // numeric literal
-        if (c1 is '.' || StringHelper.IsNumber(c1))
-        {
-            if (c1 is '0' && c2 is 'x' or 'X') // hex 0x
-            {
-                Advance(1);
-                if (span[offset] is '.') Advance(1);
-
-                ReadDigit(ref span, ref offset, out var readCount);
-
-                if (span.Length > offset && span[offset] is '.')
-                {
-                    Advance(1);
-                    ReadDigit(ref span, ref offset, out _);
-                }
-
-                if (span.Length > offset && span[offset] is 'p' or 'P')
-                {
-                    Advance(1);
-                    if (span[offset] is '-' or '+') Advance(1);
-
-                    ReadDigit(ref span, ref offset, out _);
-                }
-
-                if (readCount == 0)
-                {
-                    throw new LuaParseException(ChunkName, this.position, $"error: Illegal hexadecimal number");
-                }
-            }
-            else
-            {
-                ReadNumber(ref span, ref offset, out _);
-
-                if (span.Length > offset && span[offset] is '.')
-                {
-                    Advance(1);
-                    ReadNumber(ref span, ref offset, out _);
-                }
-
-                if (span.Length > offset && span[offset] is 'e' or 'E')
-                {
-                    Advance(1);
-                    if (span[offset] is '-' or '+') Advance(1);
-
-                    ReadNumber(ref span, ref offset, out _);
-                }
-            }
-
-            current = new(SyntaxTokenType.Number, Source[startOffset..offset], position);
-            return true;
-        }
-
-        // label
-        if (c1 is ':')
-        {
-            if (c2 is ':')
-            {
-                var stringStartOffset = offset + 1;
-                Advance(2);
-
-                var prevC = char.MinValue;
-
-                while (span.Length > offset)
-                {
-                    var c = span[offset];
-                    if (prevC == ':' && c == ':') break;
-
-                    Advance(1);
-                    prevC = c;
-                }
-
-                current = SyntaxToken.Label(Source[stringStartOffset..(offset - 1)], position);
-                Advance(1);
-            }
-            else
-            {
-                current = SyntaxToken.Colon(position);
-            }
-
-            return true;
-        }
-
-        // short string literal
-        if (c1 is '"' or '\'')
-        {
-            var quote = c1;
-            var stringStartOffset = offset;
-            var isTerminated = false;
-
-            while (span.Length > offset)
-            {
-                var c = span[offset];
-
-                if (c is '\n' or '\r')
-                {
-                    break;
-                }
-
-                if (c is '\\')
-                {
-                    Advance(1);
-
-                    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)
-                {
-                    isTerminated = true;
-                    break;
-                }
-
-                Advance(1);
-            }
-
-            if (!isTerminated)
-            {
-                throw new LuaParseException(ChunkName, this.position, "error: Unterminated string");
-            }
-
-            current = SyntaxToken.String(Source[stringStartOffset..offset], position);
-            Advance(1);
-            return true;
-        }
-
-        // long string literal
-        if (c1 is '[')
-        {
-            if (c2 is '[' or '=')
-            {
-                (var start, var end, var isTerminated) = ReadUntilLongBracketEnd(ref span);
-
-                if (!isTerminated)
-                {
-                    throw new LuaParseException(ChunkName, this.position, "error: Unterminated string");
-                }
-
-                current = SyntaxToken.RawString(Source[start..end], position);
-                return true;
-            }
-            else
-            {
-                current = SyntaxToken.LSquare(position);
-                return true;
-            }
-        }
-
-        // identifier
-        if (IsIdentifier(c1))
-        {
-            while (span.Length > offset && IsIdentifier(span[offset]))
-            {
-                Advance(1);
-            }
-
-            var identifier = Source[startOffset..offset];
-
-            current = identifier.Span switch
-            {
-                Keywords.Nil => SyntaxToken.Nil(position),
-                Keywords.True => SyntaxToken.True(position),
-                Keywords.False => SyntaxToken.False(position),
-                Keywords.And => SyntaxToken.And(position),
-                Keywords.Or => SyntaxToken.Or(position),
-                Keywords.Not => SyntaxToken.Not(position),
-                Keywords.End => SyntaxToken.End(position),
-                Keywords.Then => SyntaxToken.Then(position),
-                Keywords.If => SyntaxToken.If(position),
-                Keywords.ElseIf => SyntaxToken.ElseIf(position),
-                Keywords.Else => SyntaxToken.Else(position),
-                Keywords.Local => SyntaxToken.Local(position),
-                Keywords.Return => SyntaxToken.Return(position),
-                Keywords.Goto => SyntaxToken.Goto(position),
-                Keywords.Do => SyntaxToken.Do(position),
-                Keywords.In => SyntaxToken.In(position),
-                Keywords.While => SyntaxToken.While(position),
-                Keywords.Repeat => SyntaxToken.Repeat(position),
-                Keywords.For => SyntaxToken.For(position),
-                Keywords.Until => SyntaxToken.Until(position),
-                Keywords.Break => SyntaxToken.Break(position),
-                Keywords.Function => SyntaxToken.Function(position),
-                _ => new(SyntaxTokenType.Identifier, identifier, position),
-            };
-
-            return true;
-        }
-
-        throw new LuaParseException(ChunkName, position, $"unexpected symbol near '{c1}'");
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void ReadUntilEOL(ref ReadOnlySpan<char> span, ref int offset, out int readCount)
-    {
-        readCount = 0;
-        var flag = true;
-        while (flag)
-        {
-            if (span.Length <= offset) return;
-
-            var c1 = span[offset];
-
-            if (c1 is '\n')
-            {
-                flag = false;
-            }
-            else if (c1 is '\r')
-            {
-                var c2 = span.Length == offset + 1 ? char.MinValue : span[offset + 1];
-                if (c2 is '\n')
-                {
-                    Advance(1);
-                    readCount++;
-                }
-                flag = false;
-            }
-
-            Advance(1);
-            readCount++;
-        }
-    }
-
-    (int Start, int End, bool IsTerminated) ReadUntilLongBracketEnd(ref ReadOnlySpan<char> span)
-    {
-        var c = span[offset];
-        var level = 0;
-        while (c is '=')
-        {
-            level++;
-            Advance(1);
-            c = span[offset];
-        }
-
-        Advance(1);
-
-        var startOffset = offset;
-        var endOffset = 0;
-        var isTerminated = false;
-        var prevC = char.MinValue;
-
-        while (span.Length > offset + level + 1)
-        {
-            var current = span[offset];
-
-            // skip first newline
-            if (offset == startOffset)
-            {
-                if (current == '\r')
-                {
-                    startOffset += 2;
-                    Advance(span[offset + 1] == '\n' ? 2 : 1);
-                    continue;
-                }
-                else if (current == '\n')
-                {
-                    startOffset++;
-                    Advance(1);
-                    continue;
-                }
-            }
-
-            if (current is ']' && prevC is not '\\')
-            {
-                endOffset = offset;
-
-                for (int i = 1; i <= level; i++)
-                {
-                    if (span[offset + i] is not '=') goto CONTINUE;
-                }
-
-                if (span[offset + level + 1] is not ']') goto CONTINUE;
-
-                Advance(level + 2);
-                isTerminated = true;
-                break;
-            }
-
-        CONTINUE:
-            prevC = current;
-            Advance(1);
-        }
-
-        return (startOffset, endOffset, isTerminated);
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void ReadDigit(ref ReadOnlySpan<char> span, ref int offset, out int readCount)
-    {
-        readCount = 0;
-        while (span.Length > offset && StringHelper.IsDigit(span[offset]))
-        {
-            Advance(1);
-            readCount++;
-        }
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void ReadNumber(ref ReadOnlySpan<char> span, ref int offset, out int readCount)
-    {
-        readCount = 0;
-        while (span.Length > offset && StringHelper.IsNumber(span[offset]))
-        {
-            Advance(1);
-            readCount++;
-        }
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    static bool IsIdentifier(char c)
-    {
-        return c == '_' ||
-               ('A' <= c && c <= 'Z') ||
-               ('a' <= c && c <= 'z') ||
-               StringHelper.IsNumber(c);
-    }
-}

+ 0 - 30
src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs

@@ -1,30 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax;
-
-public record LuaSyntaxTree(SyntaxNode[] Nodes,SourcePosition Position) : SyntaxNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitSyntaxTree(this, context);
-    }
-
-    public static LuaSyntaxTree Parse(string source, string? chunkName = null)
-    {
-        var lexer = new Lexer
-        {
-            Source = source.AsMemory(),
-            ChunkName = chunkName,
-        };
-
-        var parser = new Parser
-        {
-            ChunkName = chunkName
-        };
-
-        while (lexer.MoveNext())
-        {
-            parser.Add(lexer.Current);
-        }
-
-        return parser.Parse();
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/AssignmentStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record AssignmentStatementNode(SyntaxNode[] LeftNodes, ExpressionNode[] RightNodes, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitAssignmentStatementNode(this, context);
-    }
-}

+ 0 - 54
src/Lua/CodeAnalysis/Syntax/Nodes/BinaryExpressionNode.cs

@@ -1,54 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public enum BinaryOperator
-{
-    Addition,
-    Subtraction,
-    Multiplication,
-    Division,
-    Modulo,
-    Exponentiation,
-    Equality,
-    Inequality,
-    GreaterThan,
-    GreaterThanOrEqual,
-    LessThan,
-    LessThanOrEqual,
-    And,
-    Or,
-    Concat,
-}
-
-internal static class BinaryOperatorEx
-{
-    public static string ToDisplayString(this BinaryOperator @operator)
-    {
-        return @operator switch
-        {
-            BinaryOperator.Addition => Keywords.Addition,
-            BinaryOperator.Subtraction => Keywords.Subtraction,
-            BinaryOperator.Multiplication => Keywords.Multiplication,
-            BinaryOperator.Division => Keywords.Division,
-            BinaryOperator.Modulo => Keywords.Modulo,
-            BinaryOperator.Exponentiation => Keywords.Exponentiation,
-            BinaryOperator.Equality => Keywords.Equality,
-            BinaryOperator.Inequality => Keywords.Inequality,
-            BinaryOperator.GreaterThan => Keywords.GreaterThan,
-            BinaryOperator.GreaterThanOrEqual => Keywords.GreaterThanOrEqual,
-            BinaryOperator.LessThan => Keywords.LessThan,
-            BinaryOperator.LessThanOrEqual => Keywords.LessThanOrEqual,
-            BinaryOperator.And => Keywords.And,
-            BinaryOperator.Or => Keywords.Or,
-            BinaryOperator.Concat => Keywords.Concat,
-            _ => "",
-        };
-    }
-}
-
-public record BinaryExpressionNode(BinaryOperator OperatorType, ExpressionNode LeftNode, ExpressionNode RightNode, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitBinaryExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/BooleanLiteralNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record BooleanLiteralNode(bool Value, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitBooleanLiteralNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/BreakStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record BreakStatementNode(SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitBreakStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/CallFunctionExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record CallFunctionExpressionNode(ExpressionNode FunctionNode, ExpressionNode[] ArgumentNodes) : ExpressionNode(FunctionNode.Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitCallFunctionExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/CallFunctionStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record CallFunctionStatementNode(CallFunctionExpressionNode Expression) : StatementNode(Expression.Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitCallFunctionStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/CallTableMethodExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record CallTableMethodExpressionNode(ExpressionNode TableNode, string MethodName, ExpressionNode[] ArgumentNodes, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitCallTableMethodExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/CallTableMethodStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record CallTableMethodStatementNode(CallTableMethodExpressionNode Expression) : StatementNode(Expression.Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitCallTableMethodStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/DoStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record DoStatementNode(StatementNode[] StatementNodes, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitDoStatementNode(this, context);
-    }
-}

+ 0 - 3
src/Lua/CodeAnalysis/Syntax/Nodes/ExpressionNode.cs

@@ -1,3 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public abstract record ExpressionNode(SourcePosition Position) : SyntaxNode(Position);

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-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)
-    {
-        return visitor.VisitFunctionDeclarationExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-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)
-    {
-        return visitor.VisitFunctionDeclarationStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-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)
-    {
-        return visitor.VisitGenericForStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/GotoStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record GotoStatementNode(ReadOnlyMemory<char> Name, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitGotoStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/GroupedExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record GroupedExpressionNode(ExpressionNode Expression, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitGroupedExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/IdentifierNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record IdentifierNode(ReadOnlyMemory<char> Name, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitIdentifierNode(this, context);
-    }
-}

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

@@ -1,16 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record IfStatementNode(IfStatementNode.ConditionAndThenNodes IfNode, IfStatementNode.ConditionAndThenNodes[] ElseIfNodes, StatementNode[] ElseNodes, SourcePosition Position) : StatementNode(Position)
-{
-    public record ConditionAndThenNodes
-    {
-        public SourcePosition Position;
-        public required ExpressionNode ConditionNode;
-        public required StatementNode[] ThenNodes;
-    }
-
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitIfStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/LabelStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record LabelStatementNode(ReadOnlyMemory<char> Name, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitLabelStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/LocalAssignmentStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record LocalAssignmentStatementNode(IdentifierNode[] Identifiers, ExpressionNode[] RightNodes, SourcePosition Position) : AssignmentStatementNode(Identifiers, RightNodes, Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitLocalAssignmentStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-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)
-    {
-        return visitor.VisitLocalFunctionDeclarationStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/NilLiteralNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record NilLiteralNode(SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitNilLiteralNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-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)
-    {
-        return visitor.VisitNumericForStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/NumericLiteralNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record NumericLiteralNode(double Value, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitNumericLiteralNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/RepeatStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record RepeatStatementNode(ExpressionNode ConditionNode, SyntaxNode[] Nodes, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitRepeatStatementNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/ReturnStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record ReturnStatementNode(ExpressionNode[] Nodes, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitReturnStatementNode(this, context);
-    }
-}

+ 0 - 3
src/Lua/CodeAnalysis/Syntax/Nodes/StatementNode.cs

@@ -1,3 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public abstract record StatementNode(SourcePosition Position) : SyntaxNode(Position);

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/StringLiteralNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record StringLiteralNode(ReadOnlyMemory<char> Text, bool IsShortLiteral, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitStringLiteralNode(this, context);
-    }
-}

+ 0 - 14
src/Lua/CodeAnalysis/Syntax/Nodes/TableConstructorExpressionNode.cs

@@ -1,14 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record TableConstructorExpressionNode(TableConstructorField[] Fields, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitTableConstructorExpressionNode(this, context);
-    }
-}
-
-public abstract record TableConstructorField(SourcePosition Position);
-public record GeneralTableConstructorField(ExpressionNode KeyExpression, ExpressionNode ValueExpression, SourcePosition Position) : TableConstructorField(Position);
-public record RecordTableConstructorField(string Key, ExpressionNode ValueExpression, SourcePosition Position) : TableConstructorField(Position);
-public record ListTableConstructorField(ExpressionNode Expression, SourcePosition Position) : TableConstructorField(Position);

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/TableIndexerAccessExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record TableIndexerAccessExpressionNode(ExpressionNode TableNode, ExpressionNode KeyNode, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitTableIndexerAccessExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/TableMemberAccessExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record TableMemberAccessExpressionNode(ExpressionNode TableNode, string MemberName, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitTableMemberAccessExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-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)
-    {
-        return visitor.VisitTableMethodDeclarationStatementNode(this, context);
-    }
-}

+ 0 - 30
src/Lua/CodeAnalysis/Syntax/Nodes/UnaryExpressionNode.cs

@@ -1,30 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public enum UnaryOperator
-{
-    Negate,
-    Not,
-    Length,
-}
-
-public record UnaryExpressionNode(UnaryOperator Operator, ExpressionNode Node, SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitUnaryExpressionNode(this, context);
-    }
-}
-
-internal static class UnaryOperatorEx
-{
-    public static string ToDisplayString(this UnaryOperator @operator)
-    {
-        return @operator switch
-        {
-            UnaryOperator.Negate => Keywords.Subtraction,
-            UnaryOperator.Not => Keywords.Not,
-            UnaryOperator.Length => Keywords.Length,
-            _ => "",
-        };
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/VariableArgumentsExpressionNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record VariableArgumentsExpressionNode(SourcePosition Position) : ExpressionNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitVariableArgumentsExpressionNode(this, context);
-    }
-}

+ 0 - 9
src/Lua/CodeAnalysis/Syntax/Nodes/WhileStatementNode.cs

@@ -1,9 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax.Nodes;
-
-public record WhileStatementNode(ExpressionNode ConditionNode, SyntaxNode[] Nodes, SourcePosition Position) : StatementNode(Position)
-{
-    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
-    {
-        return visitor.VisitWhileStatementNode(this, context);
-    }
-}

+ 0 - 49
src/Lua/CodeAnalysis/Syntax/OperatorPrecedence.cs

@@ -1,49 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax;
-
-public enum OperatorPrecedence
-{
-    /// <summary>
-    /// Non-operator token precedence
-    /// </summary>
-    NonOperator,
-
-    /// <summary>
-    /// 'or' operator
-    /// </summary>
-    Or,
-
-    /// <summary>
-    /// 'and' operator
-    /// </summary>
-    And,
-
-    /// <summary>
-    /// Relational operators (&lt;, &lt;=, &gt;, &gt;=, ==, ~=)
-    /// </summary>
-    Relational,
-
-    /// <summary>
-    /// Concat operator (..)
-    /// </summary>
-    Concat,
-
-    /// <summary>
-    /// Addition and Subtraction (+, -)
-    /// </summary>
-    Addition,
-
-    /// <summary>
-    /// Multipilcation, Division and Modulo (*, /, %)
-    /// </summary>
-    Multiplication,
-
-    /// <summary>
-    /// Negate, Not, Length (-, 'not', #)
-    /// </summary>
-    Unary,
-
-    /// <summary>
-    /// Exponentiation (^)
-    /// </summary>
-    Exponentiation,
-}

+ 0 - 1037
src/Lua/CodeAnalysis/Syntax/Parser.cs

@@ -1,1037 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using Lua.Internal;
-using Lua.CodeAnalysis.Syntax.Nodes;
-using System.Globalization;
-
-namespace Lua.CodeAnalysis.Syntax;
-
-public ref struct Parser
-{
-    public string? ChunkName { get; init; }
-
-    PooledList<SyntaxToken> tokens;
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void Add(SyntaxToken token) => tokens.Add(token);
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void Dispose()
-    {
-        tokens.Dispose();
-    }
-
-    public LuaSyntaxTree Parse()
-    {
-        using var root = new PooledList<SyntaxNode>(64);
-
-        var enumerator = new SyntaxTokenEnumerator(tokens.AsSpan());
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
-
-            var node = ParseStatement(ref enumerator);
-            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(),lastToken.Position);
-        Dispose();
-
-        return tree;
-    }
-
-    StatementNode ParseStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        switch (enumerator.Current.Type)
-        {
-            case SyntaxTokenType.LParen:
-            case SyntaxTokenType.Identifier:
-                {
-                    var firstExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-
-                    switch (firstExpression)
-                    {
-                        case CallFunctionExpressionNode callFunctionExpression:
-                            return new CallFunctionStatementNode(callFunctionExpression);
-                        case CallTableMethodExpressionNode callTableMethodExpression:
-                            return new CallTableMethodStatementNode(callTableMethodExpression);
-                        default:
-                            if (enumerator.GetNext(true).Type is SyntaxTokenType.Comma or SyntaxTokenType.Assignment)
-                            {
-                                // skip ','
-                                MoveNextWithValidation(ref enumerator);
-                                enumerator.SkipEoL();
-
-                                return ParseAssignmentStatement(firstExpression, ref enumerator);
-                            }
-
-                            break;
-                    }
-                }
-                break;
-            case SyntaxTokenType.Return:
-                return ParseReturnStatement(ref enumerator);
-            case SyntaxTokenType.Do:
-                return ParseDoStatement(ref enumerator);
-            case SyntaxTokenType.Goto:
-                return ParseGotoStatement(ref enumerator);
-            case SyntaxTokenType.Label:
-                return new LabelStatementNode(enumerator.Current.Text, enumerator.Current.Position);
-            case SyntaxTokenType.If:
-                return ParseIfStatement(ref enumerator);
-            case SyntaxTokenType.While:
-                return ParseWhileStatement(ref enumerator);
-            case SyntaxTokenType.Repeat:
-                return ParseRepeatStatement(ref enumerator);
-            case SyntaxTokenType.For:
-                {
-                    // skip 'for' keyword
-                    var forToken = enumerator.Current;
-                    MoveNextWithValidation(ref enumerator);
-                    enumerator.SkipEoL();
-
-                    if (enumerator.GetNext(true).Type is SyntaxTokenType.Assignment)
-                    {
-                        return ParseNumericForStatement(ref enumerator, forToken);
-                    }
-                    else
-                    {
-                        return ParseGenericForStatement(ref enumerator, forToken);
-                    }
-                }
-            case SyntaxTokenType.Break:
-                return new BreakStatementNode(enumerator.Current.Position);
-            case SyntaxTokenType.Local:
-                {
-                    // skip 'local' keyword
-                    CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Local, out var localToken);
-
-                    // local function
-                    if (enumerator.Current.Type is SyntaxTokenType.Function)
-                    {
-                        // skip 'function' keyword
-                        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
-                        enumerator.SkipEoL();
-
-                        return ParseLocalFunctionDeclarationStatement(ref enumerator, functionToken);
-                    }
-
-                    CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-
-                    var nextType = enumerator.GetNext().Type;
-
-                    if (nextType is SyntaxTokenType.Comma or SyntaxTokenType.Assignment)
-                    {
-                        return ParseLocalAssignmentStatement(ref enumerator, localToken);
-                    }
-                    else if (nextType is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon)
-                    {
-                        return new LocalAssignmentStatementNode([new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position)], [], localToken.Position);
-                    }
-                }
-                break;
-            case SyntaxTokenType.Function:
-                {
-                    // skip 'function' keyword
-                    CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
-                    enumerator.SkipEoL();
-
-                    if (enumerator.GetNext(true).Type is SyntaxTokenType.Dot or SyntaxTokenType.Colon)
-                    {
-                        return ParseTableMethodDeclarationStatement(ref enumerator, functionToken);
-                    }
-                    else
-                    {
-                        return ParseFunctionDeclarationStatement(ref enumerator, functionToken);
-                    }
-                }
-        }
-
-        LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
-        return default!;
-    }
-
-    ReturnStatementNode ParseReturnStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip 'return' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Return, out var returnToken);
-
-        // parse parameters
-        var expressions = ParseExpressionList(ref enumerator);
-
-        return new ReturnStatementNode(expressions, returnToken.Position);
-    }
-
-    DoStatementNode ParseDoStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        // check 'do' keyword
-        CheckCurrent(ref enumerator, SyntaxTokenType.Do);
-        var doToken = enumerator.Current;
-
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
-
-        return new DoStatementNode(statements, doToken.Position);
-    }
-
-    GotoStatementNode ParseGotoStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip 'goto' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Goto, out var gotoToken);
-
-        CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-        return new GotoStatementNode(enumerator.Current.Text, gotoToken.Position);
-    }
-
-    AssignmentStatementNode ParseAssignmentStatement(ExpressionNode firstExpression, ref SyntaxTokenEnumerator enumerator)
-    {
-        // parse leftNodes
-        using var leftNodes = new PooledList<SyntaxNode>(8);
-        leftNodes.Add(firstExpression);
-
-        while (enumerator.Current.Type == SyntaxTokenType.Comma)
-        {
-            // skip ','
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // parse identifier
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            leftNodes.Add(ParseExpression(ref enumerator, OperatorPrecedence.NonOperator));
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-        }
-
-        // skip '='
-        if (enumerator.Current.Type is not SyntaxTokenType.Assignment)
-        {
-            enumerator.MovePrevious();
-            return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), [], firstExpression.Position);
-        }
-
-        MoveNextWithValidation(ref enumerator);
-
-        // parse expressions
-        var expressions = ParseExpressionList(ref enumerator);
-
-        return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), expressions, firstExpression.Position);
-    }
-
-    LocalAssignmentStatementNode ParseLocalAssignmentStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken localToken)
-    {
-        // parse identifiers
-        var identifiers = ParseIdentifierList(ref enumerator);
-
-        // skip '='
-        if (enumerator.Current.Type is not SyntaxTokenType.Assignment)
-        {
-            enumerator.MovePrevious();
-            return new LocalAssignmentStatementNode(identifiers, [], localToken.Position);
-        }
-
-        MoveNextWithValidation(ref enumerator);
-
-        // parse expressions
-        var expressions = ParseExpressionList(ref enumerator);
-
-        return new LocalAssignmentStatementNode(identifiers, expressions, localToken.Position);
-    }
-
-    IfStatementNode ParseIfStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip 'if' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.If, out var ifToken);
-        enumerator.SkipEoL();
-
-        // parse condition
-        var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        // skip 'then' keyword
-        CheckCurrent(ref enumerator, SyntaxTokenType.Then);
-        var thenToken = enumerator.Current;
-
-        using var builder = new PooledList<StatementNode>(64);
-        using var elseIfBuilder = new PooledList<IfStatementNode.ConditionAndThenNodes>(64);
-
-        IfStatementNode.ConditionAndThenNodes ifNodes = default!;
-        StatementNode[] elseNodes = [];
-
-        // if = 0, elseif = 1, else = 2
-        var state = 0;
-
-        // parse statements
-        while (true)
-        {
-            if (!enumerator.MoveNext())
-            {
-                LuaParseException.ExpectedToken(ChunkName, enumerator.Current.Position, SyntaxTokenType.End);
-            }
-
-            var tokenType = enumerator.Current.Type;
-
-            if (tokenType is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon)
-            {
-                continue;
-            }
-
-            if (tokenType is SyntaxTokenType.ElseIf or SyntaxTokenType.Else or SyntaxTokenType.End)
-            {
-                switch (state)
-                {
-                    case 0:
-                        ifNodes = new()
-                        {
-                            Position = thenToken.Position,
-                            ConditionNode = condition,
-                            ThenNodes = builder.AsSpan().ToArray(),
-                        };
-                        builder.Clear();
-                        break;
-                    case 1:
-                        elseIfBuilder.Add(new()
-                        {
-                            Position = thenToken.Position,
-                            ConditionNode = condition,
-                            ThenNodes = builder.AsSpan().ToArray(),
-                        });
-                        builder.Clear();
-                        break;
-                    case 2:
-                        elseNodes = builder.AsSpan().ToArray();
-                        break;
-                }
-
-                if (tokenType is SyntaxTokenType.ElseIf)
-                {
-                    // skip 'elseif' keywords
-                    MoveNextWithValidation(ref enumerator);
-                    enumerator.SkipEoL();
-
-                    // parse condition
-                    condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
-                    MoveNextWithValidation(ref enumerator);
-                    enumerator.SkipEoL();
-
-                    // check 'then' keyword
-                    CheckCurrent(ref enumerator, SyntaxTokenType.Then);
-                    thenToken = enumerator.Current;
-                    // set elseif state
-                    state = 1;
-
-                    continue;
-                }
-                else if (tokenType is SyntaxTokenType.Else)
-                {
-                    // set else state
-                    state = 2;
-
-                    continue;
-                }
-                else if (tokenType is SyntaxTokenType.End)
-                {
-                    goto RETURN;
-                }
-            }
-
-            var node = ParseStatement(ref enumerator);
-            builder.Add(node);
-        }
-
-    RETURN:
-        return new IfStatementNode(ifNodes, elseIfBuilder.AsSpan().ToArray(), elseNodes, ifToken.Position);
-    }
-
-    WhileStatementNode ParseWhileStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip 'while' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.While, out var whileToken);
-        enumerator.SkipEoL();
-
-        // parse condition
-        var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        // skip 'do' keyword
-        CheckCurrent(ref enumerator, SyntaxTokenType.Do);
-
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
-
-        return new WhileStatementNode(condition, statements, whileToken.Position);
-    }
-
-    RepeatStatementNode ParseRepeatStatement(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip 'repeat' keyword
-        CheckCurrent(ref enumerator, SyntaxTokenType.Repeat);
-        var repeatToken = enumerator.Current;
-
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until, out _);
-
-        // skip 'until keyword'
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
-        enumerator.SkipEoL();
-
-        // parse condition
-        var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
-
-        return new RepeatStatementNode(condition, statements, repeatToken.Position);
-    }
-
-    NumericForStatementNode ParseNumericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
-    {
-        // parse variable name
-        CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-        var varName = enumerator.Current.Text;
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        // skip '='
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Assignment, out _);
-        enumerator.SkipEoL();
-
-        // parse initial value
-        var initialValueNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        // skip ','
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Comma, out _);
-        enumerator.SkipEoL();
-
-        // parse limit
-        var limitNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        // parse stepNode
-        ExpressionNode? stepNode = null;
-        if (enumerator.Current.Type is SyntaxTokenType.Comma)
-        {
-            // skip ','
-            enumerator.MoveNext();
-
-            // parse step
-            stepNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-        }
-
-        // skip 'do' keyword
-        CheckCurrent(ref enumerator, SyntaxTokenType.Do);
-        var doToken = enumerator.Current;
-
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
-
-        return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position, doToken.Position);
-    }
-
-    GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
-    {
-        var identifiers = ParseIdentifierList(ref enumerator);
-        enumerator.SkipEoL();
-
-        // skip 'in' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _);
-        enumerator.SkipEoL();
-        var iteratorToken = enumerator.Current;
-        var expressions = ParseExpressionList(ref enumerator);
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        // skip 'do' keyword
-        CheckCurrent(ref enumerator, SyntaxTokenType.Do);
-        var doToken = enumerator.Current;
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
-
-        return new GenericForStatementNode(identifiers, expressions, statements, iteratorToken.Position, doToken.Position, endToken.Position);
-    }
-
-    FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
-    {
-        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)
-    {
-        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, int LineDefined, SourcePosition EndPosition) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
-    {
-        ReadOnlyMemory<char> name;
-
-        if (isAnonymous)
-        {
-            name = ReadOnlyMemory<char>.Empty;
-        }
-        else
-        {
-            // parse function name
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            name = enumerator.Current.Text;
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-        }
-
-        // skip '('
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var leftParenToken);
-        enumerator.SkipEoL();
-
-        // parse parameters
-        var identifiers = enumerator.Current.Type is SyntaxTokenType.Identifier
-            ? ParseIdentifierList(ref enumerator)
-            : [];
-
-        // check variable arguments
-        var hasVarArg = enumerator.Current.Type is SyntaxTokenType.VarArg;
-        if (hasVarArg) enumerator.MoveNext();
-
-        // skip ')'
-        CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
-
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
-
-        return (name, identifiers, statements, hasVarArg, leftParenToken.Position.Line, endToken.Position);
-    }
-
-    TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
-    {
-        using var names = new PooledList<IdentifierNode>(32);
-        var hasSelfParameter = false;
-        SyntaxToken leftParenToken;
-        while (true)
-        {
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            names.Add(new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position));
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            if (enumerator.Current.Type is SyntaxTokenType.Dot or SyntaxTokenType.Colon)
-            {
-                if (hasSelfParameter)
-                {
-                    LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
-                }
-
-                hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon;
-
-                MoveNextWithValidation(ref enumerator);
-                enumerator.SkipEoL();
-            }
-            else if (enumerator.Current.Type is SyntaxTokenType.LParen)
-            {
-                leftParenToken = enumerator.Current;
-                // skip '('
-                MoveNextWithValidation(ref enumerator);
-                enumerator.SkipEoL();
-                break;
-            }
-        }
-
-        // parse parameters
-        var identifiers = enumerator.Current.Type is SyntaxTokenType.Identifier
-            ? ParseIdentifierList(ref enumerator)
-            : [];
-
-        // check variable arguments
-        var hasVarArg = enumerator.Current.Type is SyntaxTokenType.VarArg;
-        if (hasVarArg) enumerator.MoveNext();
-
-        // skip ')'
-        CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
-
-        // parse statements
-        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
-
-        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)
-    {
-        result = enumerator.Current.Type switch
-        {
-            SyntaxTokenType.Identifier => enumerator.GetNext(true).Type switch
-            {
-                SyntaxTokenType.LParen or SyntaxTokenType.String or SyntaxTokenType.RawString => ParseCallFunctionExpression(ref enumerator, null),
-                SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon => ParseTableAccessExpression(ref enumerator, null),
-                _ => new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position),
-            },
-            SyntaxTokenType.Number => new NumericLiteralNode(ConvertTextToNumber(enumerator.Current.Text.Span), enumerator.Current.Position),
-            SyntaxTokenType.String => new StringLiteralNode(enumerator.Current.Text, true, enumerator.Current.Position),
-            SyntaxTokenType.RawString => new StringLiteralNode(enumerator.Current.Text, false, enumerator.Current.Position),
-            SyntaxTokenType.True => new BooleanLiteralNode(true, enumerator.Current.Position),
-            SyntaxTokenType.False => new BooleanLiteralNode(false, enumerator.Current.Position),
-            SyntaxTokenType.Nil => new NilLiteralNode(enumerator.Current.Position),
-            SyntaxTokenType.VarArg => new VariableArgumentsExpressionNode(enumerator.Current.Position),
-            SyntaxTokenType.Subtraction => ParseMinusNumber(ref enumerator),
-            SyntaxTokenType.Not or SyntaxTokenType.Length => ParseUnaryExpression(ref enumerator, enumerator.Current),
-            SyntaxTokenType.LParen => ParseGroupedExpression(ref enumerator),
-            SyntaxTokenType.LCurly => ParseTableConstructorExpression(ref enumerator),
-            SyntaxTokenType.Function => ParseFunctionDeclarationExpression(ref enumerator),
-            _ => null,
-        };
-
-        if (result == null) return false;
-
-        // nested table access & function call
-    RECURSIVE:
-        enumerator.SkipEoL();
-
-        var nextType = enumerator.GetNext().Type;
-        if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon)
-        {
-            MoveNextWithValidation(ref enumerator);
-            result = ParseTableAccessExpression(ref enumerator, result);
-            goto RECURSIVE;
-        }
-        else if (nextType is SyntaxTokenType.LParen or SyntaxTokenType.String or SyntaxTokenType.RawString or SyntaxTokenType.LCurly)
-        {
-            MoveNextWithValidation(ref enumerator);
-            result = ParseCallFunctionExpression(ref enumerator, result);
-            goto RECURSIVE;
-        }
-
-        // binary expression
-        while (true)
-        {
-            var opPrecedence = GetPrecedence(enumerator.GetNext(true).Type);
-            if (precedence >= opPrecedence) break;
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-            result = ParseBinaryExpression(ref enumerator, opPrecedence, result);
-
-            enumerator.SkipEoL();
-        }
-
-        return true;
-    }
-
-    ExpressionNode ParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence)
-    {
-        if (!TryParseExpression(ref enumerator, precedence, out var result))
-        {
-            throw new LuaParseException(ChunkName, enumerator.Current.Position, $"Unexpected token <{enumerator.Current.Type}>");
-        }
-
-        return result;
-    }
-
-    ExpressionNode ParseMinusNumber(ref SyntaxTokenEnumerator enumerator)
-    {
-        var token = enumerator.Current;
-        if (enumerator.GetNext(true).Type is SyntaxTokenType.Number)
-        {
-            enumerator.MoveNext();
-            enumerator.SkipEoL();
-
-            return new NumericLiteralNode(-ConvertTextToNumber(enumerator.Current.Text.Span), token.Position);
-        }
-        else
-        {
-            return ParseUnaryExpression(ref enumerator, token);
-        }
-    }
-
-    UnaryExpressionNode ParseUnaryExpression(ref SyntaxTokenEnumerator enumerator, SyntaxToken operatorToken)
-    {
-        var operatorType = enumerator.Current.Type switch
-        {
-            SyntaxTokenType.Subtraction => UnaryOperator.Negate,
-            SyntaxTokenType.Not => UnaryOperator.Not,
-            SyntaxTokenType.Length => UnaryOperator.Length,
-            _ => throw new LuaParseException(ChunkName, operatorToken.Position, $"unexpected symbol near '{enumerator.Current.Text}'"),
-        };
-
-        MoveNextWithValidation(ref enumerator);
-        var right = ParseExpression(ref enumerator, OperatorPrecedence.Unary);
-
-        return new UnaryExpressionNode(operatorType, right, operatorToken.Position);
-    }
-
-    BinaryExpressionNode ParseBinaryExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, ExpressionNode left)
-    {
-        var operatorToken = enumerator.Current;
-        var operatorType = operatorToken.Type switch
-        {
-            SyntaxTokenType.Addition => BinaryOperator.Addition,
-            SyntaxTokenType.Subtraction => BinaryOperator.Subtraction,
-            SyntaxTokenType.Multiplication => BinaryOperator.Multiplication,
-            SyntaxTokenType.Division => BinaryOperator.Division,
-            SyntaxTokenType.Modulo => BinaryOperator.Modulo,
-            SyntaxTokenType.Exponentiation => BinaryOperator.Exponentiation,
-            SyntaxTokenType.Equality => BinaryOperator.Equality,
-            SyntaxTokenType.Inequality => BinaryOperator.Inequality,
-            SyntaxTokenType.LessThan => BinaryOperator.LessThan,
-            SyntaxTokenType.LessThanOrEqual => BinaryOperator.LessThanOrEqual,
-            SyntaxTokenType.GreaterThan => BinaryOperator.GreaterThan,
-            SyntaxTokenType.GreaterThanOrEqual => BinaryOperator.GreaterThanOrEqual,
-            SyntaxTokenType.And => BinaryOperator.And,
-            SyntaxTokenType.Or => BinaryOperator.Or,
-            SyntaxTokenType.Concat => BinaryOperator.Concat,
-            _ => throw new LuaParseException(ChunkName, enumerator.Current.Position, $"unexpected symbol near '{enumerator.Current.Text}'"),
-        };
-
-        enumerator.SkipEoL();
-        MoveNextWithValidation(ref enumerator);
-        enumerator.SkipEoL();
-
-        var right = ParseExpression(ref enumerator, precedence);
-
-        return new BinaryExpressionNode(operatorType, left, right, operatorToken.Position);
-    }
-
-    TableConstructorExpressionNode ParseTableConstructorExpression(ref SyntaxTokenEnumerator enumerator)
-    {
-        CheckCurrent(ref enumerator, SyntaxTokenType.LCurly);
-        var startToken = enumerator.Current;
-
-        using var items = new PooledList<TableConstructorField>(16);
-
-        while (enumerator.MoveNext())
-        {
-            var currentToken = enumerator.Current;
-            switch (currentToken.Type)
-            {
-                case SyntaxTokenType.RCurly:
-                    goto RETURN;
-                case SyntaxTokenType.EndOfLine:
-                case SyntaxTokenType.SemiColon:
-                case SyntaxTokenType.Comma:
-                    continue;
-                case SyntaxTokenType.LSquare:
-                    // general style ([key] = value)
-                    enumerator.MoveNext();
-
-                    var keyExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-                    enumerator.MoveNext();
-
-                    // skip '] ='
-                    CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.RSquare, out _);
-                    CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Assignment, out _);
-
-                    var valueExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-
-                    items.Add(new GeneralTableConstructorField(keyExpression, valueExpression, currentToken.Position));
-
-                    break;
-                case SyntaxTokenType.Identifier when enumerator.GetNext(true).Type is SyntaxTokenType.Assignment:
-                    // record style (key = value)
-                    var name = enumerator.Current.Text;
-
-                    // skip key and '='
-                    enumerator.MoveNext();
-                    enumerator.MoveNext();
-
-                    var expression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-
-                    items.Add(new RecordTableConstructorField(name.ToString(), expression, currentToken.Position));
-                    break;
-                default:
-                    // list style
-                    items.Add(new ListTableConstructorField(ParseExpression(ref enumerator, OperatorPrecedence.NonOperator), currentToken.Position));
-                    break;
-            }
-        }
-
-    RETURN:
-        return new TableConstructorExpressionNode(items.AsSpan().ToArray(), startToken.Position);
-    }
-
-    ExpressionNode ParseTableAccessExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? parentTable)
-    {
-        IdentifierNode? identifier = null;
-        if (parentTable == null)
-        {
-            // parse identifier
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            identifier = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position);
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-        }
-
-        ExpressionNode result;
-        var current = enumerator.Current;
-        if (current.Type is SyntaxTokenType.LSquare)
-        {
-            // indexer access -- table[key]
-
-            // skip '['
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // parse key expression
-            var keyExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // check ']'
-            CheckCurrent(ref enumerator, SyntaxTokenType.RSquare);
-
-            result = new TableIndexerAccessExpressionNode(identifier ?? parentTable!, keyExpression, current.Position);
-        }
-        else if (current.Type is SyntaxTokenType.Dot)
-        {
-            // member access -- table.key
-
-            // skip '.'
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // parse identifier
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            var key = enumerator.Current.Text.ToString();
-
-            result = new TableMemberAccessExpressionNode(identifier ?? parentTable!, key, current.Position);
-        }
-        else if (current.Type is SyntaxTokenType.Colon)
-        {
-            // self method call -- table:method(arg0, arg1, ...)
-
-            // skip ':'
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // parse identifier
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            var methodName = enumerator.Current.Text;
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // parse arguments
-            var arguments = ParseCallFunctionArguments(ref enumerator);
-            result = new CallTableMethodExpressionNode(identifier ?? parentTable!, methodName.ToString(), arguments, current.Position);
-        }
-        else
-        {
-            LuaParseException.SyntaxError(ChunkName, current.Position, current);
-            return null!; // dummy
-        }
-
-        return result;
-    }
-
-    GroupedExpressionNode ParseGroupedExpression(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip '('
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var lParen);
-        enumerator.SkipEoL();
-
-        var expression = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
-        MoveNextWithValidation(ref enumerator);
-
-        // check ')'
-        CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
-
-        return new GroupedExpressionNode(expression, lParen.Position);
-    }
-
-    ExpressionNode ParseCallFunctionExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? function)
-    {
-        // parse name
-        if (function == null)
-        {
-            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
-            function = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position);
-            enumerator.MoveNext();
-            enumerator.SkipEoL();
-        }
-
-        // parse parameters
-        var parameters = ParseCallFunctionArguments(ref enumerator);
-
-        return new CallFunctionExpressionNode(function, parameters);
-    }
-
-    FunctionDeclarationExpressionNode ParseFunctionDeclarationExpression(ref SyntaxTokenEnumerator enumerator)
-    {
-        // skip 'function' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
-        enumerator.SkipEoL();
-
-        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)
-    {
-        if (enumerator.Current.Type is SyntaxTokenType.String)
-        {
-            return [new StringLiteralNode(enumerator.Current.Text, true, enumerator.Current.Position)];
-        }
-        else if (enumerator.Current.Type is SyntaxTokenType.RawString)
-        {
-            return [new StringLiteralNode(enumerator.Current.Text, false, enumerator.Current.Position)];
-        }
-        else if (enumerator.Current.Type is SyntaxTokenType.LCurly)
-        {
-            return [ParseTableConstructorExpression(ref enumerator)];
-        }
-
-        // check and skip '('
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _);
-
-        ExpressionNode[] arguments;
-        if (enumerator.Current.Type is SyntaxTokenType.RParen)
-        {
-            // parameterless
-            arguments = [];
-        }
-        else
-        {
-            // parse arguments
-            arguments = ParseExpressionList(ref enumerator);
-            enumerator.SkipEoL();
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            // check ')'
-            CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
-        }
-
-        return arguments;
-    }
-
-    ExpressionNode[] ParseExpressionList(ref SyntaxTokenEnumerator enumerator)
-    {
-        using var builder = new PooledList<ExpressionNode>(8);
-
-        while (true)
-        {
-            enumerator.SkipEoL();
-
-            if (!TryParseExpression(ref enumerator, OperatorPrecedence.NonOperator, out var expression))
-            {
-                enumerator.MovePrevious();
-                break;
-            }
-
-            builder.Add(expression);
-
-            enumerator.SkipEoL();
-            if (enumerator.GetNext().Type != SyntaxTokenType.Comma) break;
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            if (!enumerator.MoveNext()) break;
-        }
-
-        return builder.AsSpan().ToArray();
-    }
-
-    IdentifierNode[] ParseIdentifierList(ref SyntaxTokenEnumerator enumerator)
-    {
-        using var buffer = new PooledList<IdentifierNode>(8);
-
-        while (true)
-        {
-            if (enumerator.Current.Type != SyntaxTokenType.Identifier) break;
-            var identifier = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position);
-            buffer.Add(identifier);
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-
-            if (enumerator.Current.Type != SyntaxTokenType.Comma) break;
-
-            MoveNextWithValidation(ref enumerator);
-            enumerator.SkipEoL();
-        }
-
-        return buffer.AsSpan().ToArray();
-    }
-
-    StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endTokenType, out SyntaxToken endToken)
-    {
-        using var statements = new PooledList<StatementNode>(64);
-
-        // parse statements
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type == endTokenType) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
-
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
-        }
-
-        endToken = enumerator.Current;
-
-        return statements.AsSpan().ToArray();
-    }
-
-    void CheckCurrentAndSkip(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken, out SyntaxToken token)
-    {
-        CheckCurrent(ref enumerator, expectedToken);
-        token = enumerator.Current;
-        MoveNextWithValidation(ref enumerator);
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void CheckCurrent(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken)
-    {
-        if (enumerator.Current.Type != expectedToken)
-        {
-            LuaParseException.ExpectedToken(ChunkName, enumerator.Current.Position, expectedToken);
-        }
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void MoveNextWithValidation(ref SyntaxTokenEnumerator enumerator)
-    {
-        if (!enumerator.MoveNext()) LuaParseException.SyntaxError(ChunkName, enumerator.Current.Position, enumerator.Current);
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    static OperatorPrecedence GetPrecedence(SyntaxTokenType type)
-    {
-        return type switch
-        {
-            SyntaxTokenType.Addition or SyntaxTokenType.Subtraction => OperatorPrecedence.Addition,
-            SyntaxTokenType.Multiplication or SyntaxTokenType.Division or SyntaxTokenType.Modulo => OperatorPrecedence.Multiplication,
-            SyntaxTokenType.Equality or SyntaxTokenType.Inequality or SyntaxTokenType.LessThan or SyntaxTokenType.LessThanOrEqual or SyntaxTokenType.GreaterThan or SyntaxTokenType.GreaterThanOrEqual => OperatorPrecedence.Relational,
-            SyntaxTokenType.Concat => OperatorPrecedence.Concat,
-            SyntaxTokenType.Exponentiation => OperatorPrecedence.Exponentiation,
-            SyntaxTokenType.And => OperatorPrecedence.And,
-            SyntaxTokenType.Or => OperatorPrecedence.Or,
-            _ => OperatorPrecedence.NonOperator,
-        };
-    }
-
-    static double ConvertTextToNumber(ReadOnlySpan<char> text)
-    {
-        if (text.Length > 2 && text[0] is '0' && text[1] is 'x' or 'X')
-        {
-            return HexConverter.ToDouble(text);
-        }
-        else
-        {
-            return double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
-        }
-    }
-}

+ 0 - 6
src/Lua/CodeAnalysis/Syntax/SyntaxNode.cs

@@ -1,6 +0,0 @@
-namespace Lua.CodeAnalysis.Syntax;
-
-public abstract record SyntaxNode(SourcePosition Position)
-{
-    public abstract TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context);
-}

+ 0 - 364
src/Lua/CodeAnalysis/Syntax/SyntaxToken.cs

@@ -1,364 +0,0 @@
-using Lua.Internal;
-
-namespace Lua.CodeAnalysis.Syntax;
-
-public readonly struct SyntaxToken(SyntaxTokenType type, ReadOnlyMemory<char> text, SourcePosition position) : IEquatable<SyntaxToken>
-{
-    public static SyntaxToken EndOfLine(SourcePosition position) => new(SyntaxTokenType.EndOfLine, Keywords.LF.AsMemory(), position);
-
-    public static SyntaxToken LParen(SourcePosition position) => new(SyntaxTokenType.LParen, Keywords.LParen.AsMemory(), position);
-    public static SyntaxToken RParen(SourcePosition position) => new(SyntaxTokenType.RParen, Keywords.RParen.AsMemory(), position);
-    public static SyntaxToken LCurly(SourcePosition position) => new(SyntaxTokenType.LCurly, Keywords.LCurly.AsMemory(), position);
-    public static SyntaxToken RCurly(SourcePosition position) => new(SyntaxTokenType.RCurly, Keywords.RCurly.AsMemory(), position);
-    public static SyntaxToken LSquare(SourcePosition position) => new(SyntaxTokenType.LSquare, Keywords.LSquare.AsMemory(), position);
-    public static SyntaxToken RSquare(SourcePosition position) => new(SyntaxTokenType.RSquare, Keywords.RSquare.AsMemory(), position);
-
-    public static SyntaxToken Nil(SourcePosition position) => new(SyntaxTokenType.Nil, Keywords.Nil.AsMemory(), position);
-    public static SyntaxToken True(SourcePosition position) => new(SyntaxTokenType.True, Keywords.True.AsMemory(), position);
-    public static SyntaxToken False(SourcePosition position) => new(SyntaxTokenType.False, Keywords.False.AsMemory(), position);
-
-    public static SyntaxToken Addition(SourcePosition position) => new(SyntaxTokenType.Addition, Keywords.Addition.AsMemory(), position);
-    public static SyntaxToken Subtraction(SourcePosition position) => new(SyntaxTokenType.Subtraction, Keywords.Subtraction.AsMemory(), position);
-    public static SyntaxToken Multiplication(SourcePosition position) => new(SyntaxTokenType.Multiplication, Keywords.Multiplication.AsMemory(), position);
-    public static SyntaxToken Division(SourcePosition position) => new(SyntaxTokenType.Division, Keywords.Division.AsMemory(), position);
-    public static SyntaxToken Modulo(SourcePosition position) => new(SyntaxTokenType.Modulo, Keywords.Modulo.AsMemory(), position);
-    public static SyntaxToken Exponentiation(SourcePosition position) => new(SyntaxTokenType.Exponentiation, Keywords.Exponentiation.AsMemory(), position);
-
-    public static SyntaxToken Equality(SourcePosition position) => new(SyntaxTokenType.Equality, Keywords.Equality.AsMemory(), position);
-    public static SyntaxToken Inequality(SourcePosition position) => new(SyntaxTokenType.Inequality, Keywords.Inequality.AsMemory(), position);
-    public static SyntaxToken GreaterThan(SourcePosition position) => new(SyntaxTokenType.GreaterThan, Keywords.GreaterThan.AsMemory(), position);
-    public static SyntaxToken GreaterThanOrEqual(SourcePosition position) => new(SyntaxTokenType.GreaterThanOrEqual, Keywords.GreaterThanOrEqual.AsMemory(), position);
-    public static SyntaxToken LessThan(SourcePosition position) => new(SyntaxTokenType.LessThan, Keywords.LessThan.AsMemory(), position);
-    public static SyntaxToken LessThanOrEqual(SourcePosition position) => new(SyntaxTokenType.LessThanOrEqual, Keywords.LessThanOrEqual.AsMemory(), position);
-
-    public static SyntaxToken Length(SourcePosition position) => new(SyntaxTokenType.Length, Keywords.Length.AsMemory(), position);
-    public static SyntaxToken Concat(SourcePosition position) => new(SyntaxTokenType.Concat, Keywords.Concat.AsMemory(), position);
-    public static SyntaxToken VarArg(SourcePosition position) => new(SyntaxTokenType.VarArg, "...".AsMemory(), position);
-
-    public static SyntaxToken Assignment(SourcePosition position) => new(SyntaxTokenType.Assignment, Keywords.Assignment.AsMemory(), position);
-
-    public static SyntaxToken And(SourcePosition position) => new(SyntaxTokenType.And, Keywords.And.AsMemory(), position);
-    public static SyntaxToken Or(SourcePosition position) => new(SyntaxTokenType.Or, Keywords.Or.AsMemory(), position);
-    public static SyntaxToken Not(SourcePosition position) => new(SyntaxTokenType.Not, Keywords.Not.AsMemory(), position);
-
-    public static SyntaxToken End(SourcePosition position) => new(SyntaxTokenType.End, Keywords.End.AsMemory(), position);
-    public static SyntaxToken Then(SourcePosition position) => new(SyntaxTokenType.Then, Keywords.Then.AsMemory(), position);
-
-    public static SyntaxToken If(SourcePosition position) => new(SyntaxTokenType.If, Keywords.If.AsMemory(), position);
-    public static SyntaxToken ElseIf(SourcePosition position) => new(SyntaxTokenType.ElseIf, Keywords.ElseIf.AsMemory(), position);
-    public static SyntaxToken Else(SourcePosition position) => new(SyntaxTokenType.Else, Keywords.Else.AsMemory(), position);
-
-    public static SyntaxToken Local(SourcePosition position) => new(SyntaxTokenType.Local, Keywords.Local.AsMemory(), position);
-
-    public static SyntaxToken Return(SourcePosition position) => new(SyntaxTokenType.Return, Keywords.Return.AsMemory(), position);
-    public static SyntaxToken Goto(SourcePosition position) => new(SyntaxTokenType.Goto, Keywords.Goto.AsMemory(), position);
-
-    public static SyntaxToken Comma(SourcePosition position) => new(SyntaxTokenType.Comma, ",".AsMemory(), position);
-    public static SyntaxToken Dot(SourcePosition position) => new(SyntaxTokenType.Dot, ".".AsMemory(), position);
-    public static SyntaxToken SemiColon(SourcePosition position) => new(SyntaxTokenType.SemiColon, ";".AsMemory(), position);
-    public static SyntaxToken Colon(SourcePosition position) => new(SyntaxTokenType.Colon, ":".AsMemory(), position);
-
-    public static SyntaxToken Do(SourcePosition position) => new(SyntaxTokenType.Do, Keywords.Do.AsMemory(), position);
-    public static SyntaxToken While(SourcePosition position) => new(SyntaxTokenType.While, Keywords.While.AsMemory(), position);
-    public static SyntaxToken Repeat(SourcePosition position) => new(SyntaxTokenType.Repeat, Keywords.Repeat.AsMemory(), position);
-    public static SyntaxToken Until(SourcePosition position) => new(SyntaxTokenType.Until, Keywords.Until.AsMemory(), position);
-    public static SyntaxToken Break(SourcePosition position) => new(SyntaxTokenType.Break, Keywords.Break.AsMemory(), position);
-    public static SyntaxToken Function(SourcePosition position) => new(SyntaxTokenType.Function, Keywords.Function.AsMemory(), position);
-    public static SyntaxToken For(SourcePosition position) => new(SyntaxTokenType.For, Keywords.For.AsMemory(), position);
-    public static SyntaxToken In(SourcePosition position) => new(SyntaxTokenType.In, Keywords.In.AsMemory(), position);
-
-    public SyntaxTokenType Type { get; } = type;
-    public ReadOnlyMemory<char> Text { get; } = text;
-    public SourcePosition Position { get; } = position;
-
-    public static SyntaxToken Number(string text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.Number, text.AsMemory(), position);
-    }
-
-    public static SyntaxToken Number(ReadOnlyMemory<char> text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.Number, text, position);
-    }
-
-    public static SyntaxToken Identifier(string text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.Identifier, text.AsMemory(), position);
-    }
-
-    public static SyntaxToken Identifier(ReadOnlyMemory<char> text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.Identifier, text, position);
-    }
-
-    public static SyntaxToken String(ReadOnlyMemory<char> text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.String, text, position);
-    }
-
-    public static SyntaxToken RawString(ReadOnlyMemory<char> text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.RawString, text, position);
-    }
-
-    public static SyntaxToken Label(ReadOnlyMemory<char> text, SourcePosition position)
-    {
-        return new(SyntaxTokenType.Label, text, position);
-    }
-
-    public override string ToString()
-    {
-        return $"{Position} {Type}:{Text}";
-    }
-
-    public string ToDisplayString()
-    {
-        return Type switch
-        {
-            SyntaxTokenType.EndOfLine => Keywords.LF,
-            SyntaxTokenType.LParen => Keywords.LParen,
-            SyntaxTokenType.RParen => Keywords.RParen,
-            SyntaxTokenType.LCurly => Keywords.LCurly,
-            SyntaxTokenType.RCurly => Keywords.RCurly,
-            SyntaxTokenType.LSquare => Keywords.LSquare,
-            SyntaxTokenType.RSquare => Keywords.RSquare,
-            SyntaxTokenType.SemiColon => ";",
-            SyntaxTokenType.Comma => ",",
-            SyntaxTokenType.Number => Text.ToString(),
-            SyntaxTokenType.String => $"\"{Text}\"",
-            SyntaxTokenType.RawString => $"[[{Text}]]",
-            SyntaxTokenType.Nil => Keywords.Nil,
-            SyntaxTokenType.True => Keywords.True,
-            SyntaxTokenType.False => Keywords.False,
-            SyntaxTokenType.Identifier => Text.ToString(),
-            SyntaxTokenType.Addition => Keywords.Addition,
-            SyntaxTokenType.Subtraction => Keywords.Subtraction,
-            SyntaxTokenType.Multiplication => Keywords.Multiplication,
-            SyntaxTokenType.Division => Keywords.Division,
-            SyntaxTokenType.Modulo => Keywords.Modulo,
-            SyntaxTokenType.Exponentiation => Keywords.Exponentiation,
-            SyntaxTokenType.Equality => Keywords.Equality,
-            SyntaxTokenType.Inequality => Keywords.Inequality,
-            SyntaxTokenType.GreaterThan => Keywords.GreaterThan,
-            SyntaxTokenType.LessThan => Keywords.LessThan,
-            SyntaxTokenType.GreaterThanOrEqual => Keywords.GreaterThanOrEqual,
-            SyntaxTokenType.LessThanOrEqual => Keywords.LessThanOrEqual,
-            SyntaxTokenType.And => Keywords.And,
-            SyntaxTokenType.Not => Keywords.Not,
-            SyntaxTokenType.Or => Keywords.Or,
-            SyntaxTokenType.Assignment => Keywords.Assignment,
-            SyntaxTokenType.Concat => Keywords.Concat,
-            SyntaxTokenType.Length => Keywords.Length,
-            SyntaxTokenType.Break => Keywords.Break,
-            SyntaxTokenType.Do => Keywords.Do,
-            SyntaxTokenType.For => Keywords.For,
-            SyntaxTokenType.Goto => Keywords.Goto,
-            SyntaxTokenType.If => Keywords.If,
-            SyntaxTokenType.ElseIf => Keywords.ElseIf,
-            SyntaxTokenType.Else => Keywords.Else,
-            SyntaxTokenType.Function => Keywords.Function,
-            SyntaxTokenType.End => Keywords.End,
-            SyntaxTokenType.Then => Keywords.Then,
-            SyntaxTokenType.In => Keywords.In,
-            SyntaxTokenType.Local => Keywords.Local,
-            SyntaxTokenType.Repeat => Keywords.Repeat,
-            SyntaxTokenType.Return => Keywords.Return,
-            SyntaxTokenType.Until => Keywords.Until,
-            SyntaxTokenType.While => Keywords.While,
-            _ => "",
-        };
-    }
-
-    public bool Equals(SyntaxToken other)
-    {
-        return other.Type == Type &&
-            other.Text.Span.SequenceEqual(Text.Span) &&
-            other.Position == Position;
-    }
-
-    public override bool Equals(object? obj)
-    {
-        if (obj is SyntaxToken token) return Equals(token);
-        return false;
-    }
-
-    public override int GetHashCode()
-    {
-        return HashCode.Combine(Type, Utf16StringMemoryComparer.Default.GetHashCode(Text), Position);
-    }
-
-    public static bool operator ==(SyntaxToken left, SyntaxToken right)
-    {
-        return left.Equals(right);
-    }
-
-    public static bool operator !=(SyntaxToken left, SyntaxToken right)
-    {
-        return !(left == right);
-    }
-}
-
-public enum SyntaxTokenType
-{
-    /// <summary>
-    /// Invalid token
-    /// </summary>
-    Invalid,
-
-    /// <summary>
-    /// End of line
-    /// </summary>
-    EndOfLine,
-
-    /// <summary>
-    /// Left parenthesis '('
-    /// </summary>
-    LParen,
-    /// <summary>
-    /// Right parenthesis ')'
-    /// </summary>
-    RParen,
-
-    /// <summary>
-    /// Left curly bracket '{'
-    /// </summary>
-    LCurly,
-    /// <summary>
-    /// Right curly bracket '}'
-    /// </summary>
-    RCurly,
-
-    /// <summary>
-    /// Left square bracket '['
-    /// </summary>
-    LSquare,
-    /// <summary>
-    /// Right square bracket ']'
-    /// </summary>
-    RSquare,
-
-    /// <summary>
-    /// Semi colon (;)
-    /// </summary>
-    SemiColon,
-
-    /// <summary>
-    /// Colon (:)
-    /// </summary>
-    Colon,
-
-    /// <summary>
-    /// Comma (,)
-    /// </summary>
-    Comma,
-
-    /// <summary>
-    /// Dot (.)
-    /// </summary>
-    Dot,
-
-    /// <summary>
-    /// Numeric literal (e.g. 1, 2, 1.0, 2.0, ...)
-    /// </summary>
-    Number,
-
-    /// <summary>
-    /// String literal (e.g. "foo", "bar", ...)
-    /// </summary>
-    String,
-
-    /// <summary>
-    /// Raw string literal (e.g. [[Hello, World!]])
-    /// </summary>
-    RawString,
-
-    /// <summary>
-    /// Nil literal (nil)
-    /// </summary>
-    Nil,
-
-    /// <summary>
-    /// Boolean literal (true)
-    /// </summary>
-    True,
-    /// <summary>
-    /// Boolean literal (false)
-    /// </summary>
-    False,
-
-    /// <summary>
-    /// Identifier
-    /// </summary>
-    Identifier,
-
-    /// <summary>
-    /// Label
-    /// </summary>
-    Label,
-
-    /// <summary>
-    /// Addition operator (+)
-    /// </summary>
-    Addition,
-    /// <summary>
-    /// Subtraction operator (-)
-    /// </summary>
-    Subtraction,
-    /// <summary>
-    /// Multiplication operator (*)
-    /// </summary>
-    Multiplication,
-    /// <summary>
-    /// Division operator (/)
-    /// </summary>
-    Division,
-    /// <summary>
-    /// Modulo operator (%)
-    /// </summary>
-    Modulo,
-    /// <summary>
-    /// Exponentiation operator (^)
-    /// </summary>
-    Exponentiation,
-
-    Equality,          // ==
-    Inequality,       // ~=
-    GreaterThan,        // >
-    LessThan,           // <
-    GreaterThanOrEqual, // >=
-    LessThanOrEqual,    // <=
-
-    And,            // and
-    Not,            // not
-    Or,             // or
-
-    /// <summary>
-    /// Assignment operator (=)
-    /// </summary>
-    Assignment,
-
-    Concat,         // ..
-    Length,         // #
-
-    VarArg,         // ...
-
-    Break,          // break
-    Do,             // do
-    For,            // for
-    Goto,           // goto
-
-    If,             // if
-    ElseIf,         // elseif
-    Else,           // else
-    Function,       // function
-
-    End,            // end
-    Then,           // then
-
-    In,             // in
-    Local,          // local
-    Repeat,         // repeat
-    Return,         // return
-    Until,          // until
-    While,          // while
-}

+ 0 - 60
src/Lua/CodeAnalysis/Syntax/SyntaxTokenEnumerator.cs

@@ -1,60 +0,0 @@
-using System.Runtime.CompilerServices;
-
-namespace Lua.CodeAnalysis.Syntax;
-
-public ref struct SyntaxTokenEnumerator(ReadOnlySpan<SyntaxToken> source)
-{
-    ReadOnlySpan<SyntaxToken> source = source;
-    SyntaxToken current;
-    int offset;
-
-    public SyntaxToken Current => current;
-    public int Position => offset;
-    public bool IsCompleted => source.Length == offset;
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public bool MoveNext()
-    {
-        if (IsCompleted) return false;
-        current = source[offset];
-        offset++;
-        return true;
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public bool MovePrevious()
-    {
-        if (offset == 0) return false;
-        offset--;
-        current = source[offset - 1];
-        return true;
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void SkipEoL()
-    {
-        while (true)
-        {
-            if (current.Type != SyntaxTokenType.EndOfLine) return;
-            if (!MoveNext()) return;
-        }
-    }
-
-    public SyntaxToken GetNext(bool skipEoL = false)
-    {
-        if (!skipEoL)
-        {
-            return IsCompleted ? default : source[offset];
-        }
-
-        var i = offset;
-        while (i < source.Length)
-        {
-            var c = source[i];
-            if (source[i].Type is not SyntaxTokenType.EndOfLine) return c;
-            i++;
-        }
-
-        return default;
-    }
-}

+ 8 - 0
src/Lua/CodeAnalysis/UpValueDesc.cs

@@ -0,0 +1,8 @@
+namespace Lua.CodeAnalysis;
+
+public record struct UpValueDesc
+{
+    public string Name;
+    public bool IsLocal;
+    public int Index;
+}

+ 44 - 51
src/Lua/Exceptions.cs

@@ -1,5 +1,4 @@
-using Lua.CodeAnalysis;
-using Lua.CodeAnalysis.Syntax;
+using Lua.Internal;
 using Lua.Runtime;
 using Lua.Runtime;
 
 
 namespace Lua;
 namespace Lua;
@@ -13,46 +12,14 @@ public class LuaException : Exception
     public LuaException(string message) : base(message)
     public LuaException(string message) : base(message)
     {
     {
     }
     }
-}
-
-public class LuaParseException(string? chunkName, SourcePosition position, string message) : LuaException(message)
-{
-    public string? ChunkName { get; } = chunkName;
-    public SourcePosition? Position { get; } = position;
-
-    public static void UnexpectedToken(string? chunkName, SourcePosition position, SyntaxToken token)
-    {
-        throw new LuaParseException(chunkName, position, $"unexpected symbol <{token.Type}> near '{token.Text}'");
-    }
-
-    public static void ExpectedToken(string? chunkName, SourcePosition position, SyntaxTokenType token)
-    {
-        throw new LuaParseException(chunkName, position, $"'{token}' expected");
-    }
-
-    public static void UnfinishedLongComment(string? chunkName, SourcePosition position)
-    {
-        throw new LuaParseException(chunkName, position, $"unfinished long comment (starting at line {position.Line})");
-    }
-
-    public static void SyntaxError(string? chunkName, SourcePosition position, SyntaxToken? token)
-    {
-        throw new LuaParseException(chunkName, position, $"syntax error {(token == null ? "" : $"near '{token.Value.Text}'")}");
-    }
 
 
-    public static void NoVisibleLabel(string label, string? chunkName, SourcePosition position)
+    protected LuaException()
     {
     {
-        throw new LuaParseException(chunkName, position, $"no visible label '{label}' for <goto>");
     }
     }
-
-    public static void BreakNotInsideALoop(string? chunkName, SourcePosition position)
-    {
-        throw new LuaParseException(chunkName, position, "<break> not inside a loop");
-    }
-
-    public override string Message => $"{ChunkName}:{(Position == null ? "" : $"{Position.Value}:")} {base.Message}";
 }
 }
 
 
+public class LuaParseException(string message) : LuaException(message);
+
 public class LuaRuntimeException : LuaException
 public class LuaRuntimeException : LuaException
 {
 {
     public LuaRuntimeException(Traceback traceback, Exception innerException) : base(innerException)
     public LuaRuntimeException(Traceback traceback, Exception innerException) : base(innerException)
@@ -60,20 +27,14 @@ public class LuaRuntimeException : LuaException
         LuaTraceback = traceback;
         LuaTraceback = traceback;
     }
     }
 
 
-    public LuaRuntimeException(Traceback traceback, string message) : base(message)
-    {
-        LuaTraceback = traceback;
-    }
-
-    public LuaRuntimeException(Traceback traceback, LuaValue errorObject): base(errorObject.ToString())
+    public LuaRuntimeException(Traceback traceback, LuaValue errorObject) : base(CreateMessage(traceback, errorObject))
     {
     {
         LuaTraceback = traceback;
         LuaTraceback = traceback;
         ErrorObject = errorObject;
         ErrorObject = errorObject;
     }
     }
 
 
     public Traceback LuaTraceback { get; }
     public Traceback LuaTraceback { get; }
-
-    public LuaValue? ErrorObject { get; }
+    public LuaValue ErrorObject { get; }
 
 
     public static void AttemptInvalidOperation(Traceback traceback, string op, LuaValue a, LuaValue b)
     public static void AttemptInvalidOperation(Traceback traceback, string op, LuaValue a, LuaValue b)
     {
     {
@@ -118,20 +79,52 @@ public class LuaRuntimeException : LuaException
         }
         }
     }
     }
 
 
-    public override string Message => $"{LuaTraceback.RootChunkName}:{LuaTraceback.LastPosition.Line}: {base.Message}";
+    static string CreateMessage(Traceback traceback, LuaValue errorObject)
+    {
+        var pooledList = new PooledList<char>(64);
+        pooledList.Clear();
+        try
+        {
+            pooledList.AddRange("Lua-CSharp: ");
+            traceback.WriteLastLuaTrace(ref pooledList);
+            pooledList.AddRange(": ");
+            pooledList.AddRange($"{errorObject}");
+            return pooledList.AsSpan().ToString();
+        }
+        finally
+        {
+            pooledList.Dispose();
+        }
+    }
+
 
 
     public override string ToString()
     public override string ToString()
     {
     {
-        return $"{Message}\n{(LuaTraceback.StackFrames.Length > 0 ? $"{LuaTraceback}\n" : "")}{StackTrace}";
+        var pooledList = new PooledList<char>(64);
+        pooledList.Clear();
+        try
+        {
+            pooledList.AddRange(base.Message);
+            pooledList.Add('\n');
+            pooledList.AddRange(LuaTraceback.ToString());
+            pooledList.Add('\n');
+            pooledList.AddRange(StackTrace);
+            return pooledList.AsSpan().ToString();
+        }
+        finally
+        {
+            pooledList.Dispose();
+        }
+        //return $"{Message} {StackTrace}";
     }
     }
 }
 }
 
 
 public class LuaAssertionException(Traceback traceback, string message) : LuaRuntimeException(traceback, message)
 public class LuaAssertionException(Traceback traceback, string message) : LuaRuntimeException(traceback, message)
 {
 {
-    public override string ToString()
-    {
-        return $"{Message}\n{StackTrace}";
-    }
+    // public override string ToString()
+    // {
+    //     return $"{Message}\n{StackTrace}";
+    // }
 }
 }
 
 
 public class LuaModuleNotFoundException(string moduleName) : LuaException($"module '{moduleName}' not found");
 public class LuaModuleNotFoundException(string moduleName) : LuaException($"module '{moduleName}' not found");

+ 0 - 24
src/Lua/Internal/BitFlags256.cs

@@ -1,24 +0,0 @@
-namespace Lua.Internal;
-
-internal unsafe struct BitFlags256
-{
-    internal fixed long Data[4];
-
-    public bool this[int index]
-    {
-        get => (Data[index >> 6] & (1L << (index & 63))) != 0;
-        set
-        {
-            if (value)
-            {
-                Data[index >> 6] |= 1L << (index & 63);
-            }
-            else
-            {
-                Data[index >> 6] &= ~(1L << (index & 63));
-            }
-        }
-    }
-
-    public void Set(int index) => Data[index >> 6] |= 1L << (index & 63);
-}

+ 0 - 1
src/Lua/Internal/CancellationExtensions.cs

@@ -1,5 +1,4 @@
 #if NETSTANDARD2_0 || NETSTANDARD2_1
 #if NETSTANDARD2_0 || NETSTANDARD2_1
-
 namespace System.Threading;
 namespace System.Threading;
 
 
 internal static class CancellationTokenExtensions
 internal static class CancellationTokenExtensions

+ 12 - 0
src/Lua/Internal/Constants.cs

@@ -0,0 +1,12 @@
+namespace Lua.Internal;
+
+ internal class Constants
+{
+    public const int VersionMajor = 5;
+    public const int VersionMinor = 2;
+    public const int MultipleReturns = -1;
+    public const int MaxStack = 1000000;
+    public const int MaxUpValue = byte.MaxValue;
+    public const int MaxCallCount = 200;
+    public const int MaxLine = 10000;
+}

+ 16 - 11
src/Lua/Internal/FarmHash.cs

@@ -146,12 +146,14 @@ internal static class FarmHash
                 ulong d = (Rotate64(a, 25) + b) * mul;
                 ulong d = (Rotate64(a, 25) + b) * mul;
                 return HashLen16(c, d, mul);
                 return HashLen16(c, d, mul);
             }
             }
+
             if (len >= 4)
             if (len >= 4)
             {
             {
                 ulong mul = k2 + len * 2;
                 ulong mul = k2 + len * 2;
                 ulong a = Fetch32(s);
                 ulong a = Fetch32(s);
                 return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
                 return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
             }
             }
+
             if (len > 0)
             if (len > 0)
             {
             {
                 ushort a = s[0];
                 ushort a = s[0];
@@ -161,6 +163,7 @@ internal static class FarmHash
                 uint z = len + ((uint)c << 2);
                 uint z = len + ((uint)c << 2);
                 return ShiftMix(y * k2 ^ z * k0) * k2;
                 return ShiftMix(y * k2 ^ z * k0) * k2;
             }
             }
+
             return k2;
             return k2;
         }
         }
     }
     }
@@ -177,7 +180,7 @@ internal static class FarmHash
             ulong c = Fetch64(s + len - 8) * mul;
             ulong c = Fetch64(s + len - 8) * mul;
             ulong d = Fetch64(s + len - 16) * k2;
             ulong d = Fetch64(s + len - 16) * k2;
             return HashLen16(Rotate64(a + b, 43) + Rotate64(c, 30) + d,
             return HashLen16(Rotate64(a + b, 43) + Rotate64(c, 30) + d,
-                             a + Rotate64(b + k2, 18) + c, mul);
+                a + Rotate64(b + k2, 18) + c, mul);
         }
         }
     }
     }
 
 
@@ -254,11 +257,11 @@ internal static class FarmHash
     static unsafe Pair WeakHashLen32WithSeeds(byte* s, ulong a, ulong b)
     static unsafe Pair WeakHashLen32WithSeeds(byte* s, ulong a, ulong b)
     {
     {
         return WeakHashLen32WithSeeds(Fetch64(s),
         return WeakHashLen32WithSeeds(Fetch64(s),
-                                      Fetch64(s + 8),
-                                      Fetch64(s + 16),
-                                      Fetch64(s + 24),
-                                      a,
-                                      b);
+            Fetch64(s + 8),
+            Fetch64(s + 16),
+            Fetch64(s + 24),
+            a,
+            b);
     }
     }
 
 
     // na(97-256) farmhashna.cc
     // na(97-256) farmhashna.cc
@@ -294,6 +297,7 @@ internal static class FarmHash
                 swap(ref z, ref x);
                 swap(ref z, ref x);
                 s += 64;
                 s += 64;
             } while (s != end);
             } while (s != end);
+
             ulong mul = k1 + ((z & 0xff) << 1);
             ulong mul = k1 + ((z & 0xff) << 1);
             // Make s point to the last 64 bytes of input.
             // Make s point to the last 64 bytes of input.
             s = last64;
             s = last64;
@@ -309,8 +313,8 @@ internal static class FarmHash
             w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
             w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
             swap(ref z, ref x);
             swap(ref z, ref x);
             return HashLen16(HashLen16(v.first, w.first, mul) + ShiftMix(y) * k0 + z,
             return HashLen16(HashLen16(v.first, w.first, mul) + ShiftMix(y) * k0 + z,
-                             HashLen16(v.second, w.second, mul) + x,
-                             mul);
+                HashLen16(v.second, w.second, mul) + x,
+                mul);
         }
         }
     }
     }
 
 
@@ -401,6 +405,7 @@ internal static class FarmHash
                 swap(ref u, ref z);
                 swap(ref u, ref z);
                 s += 64;
                 s += 64;
             } while (s != end);
             } while (s != end);
+
             // Make s point to the last 64 bytes of input.
             // Make s point to the last 64 bytes of input.
             s = last64;
             s = last64;
             u *= 9;
             u *= 9;
@@ -417,9 +422,9 @@ internal static class FarmHash
             v = WeakHashLen32WithSeeds(s, v.second * mul, x + w.first);
             v = WeakHashLen32WithSeeds(s, v.second * mul, x + w.first);
             w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
             w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
             return H(HashLen16(v.first + x, w.first ^ y, mul) + z - u,
             return H(HashLen16(v.first + x, w.first ^ y, mul) + z - u,
-                     H(v.second + y, w.second + z, k2, 30) ^ x,
-                     k2,
-                     31);
+                H(v.second + y, w.second + z, k2, 30) ^ x,
+                k2,
+                31);
         }
         }
     }
     }
 }
 }

+ 11 - 3
src/Lua/Internal/FastListCore.cs

@@ -34,9 +34,15 @@ public struct FastListCore<T>
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void RemoveAtSwapback(int index)
+    public void Pop()
+    {
+        CheckIndex(tailIndex - 1);
+        array![tailIndex - 1] = default!;
+        tailIndex--;
+    }
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void RemoveAtSwapBack(int index)
     {
     {
-        if (array == null) throw new IndexOutOfRangeException();
         CheckIndex(index);
         CheckIndex(index);
 
 
         array![index] = array[tailIndex - 1];
         array![index] = array[tailIndex - 1];
@@ -102,6 +108,8 @@ public struct FastListCore<T>
 
 
     readonly void CheckIndex(int index)
     readonly void CheckIndex(int index)
     {
     {
-        if (index < 0 || index > tailIndex) throw new IndexOutOfRangeException();
+        if (array == null||index < 0 || index > tailIndex) ThrowIndexOutOfRange();
     }
     }
+    
+    static void ThrowIndexOutOfRange() => throw new IndexOutOfRangeException();
 }
 }

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

@@ -71,6 +71,7 @@ public struct FastStackCore<T>
         {
         {
             return false;
             return false;
         }
         }
+
         array[--tail] = default;
         array[--tail] = default;
 
 
         return true;
         return true;

+ 32 - 33
src/Lua/Internal/LuaDebug.cs

@@ -211,7 +211,7 @@ internal readonly struct LuaDebug : IDisposable
                         }
                         }
                     case 'l':
                     case 'l':
                         {
                         {
-                            CurrentLine = (pc >= 0 && closure is not null) ? closure.Proto.SourcePositions[pc].Line : -1;
+                            CurrentLine = (pc >= 0 && closure is not null) ? closure.Proto.LineInfo[pc] : -1;
                             break;
                             break;
                         }
                         }
                     case 'u':
                     case 'u':
@@ -278,10 +278,10 @@ internal readonly struct LuaDebug : IDisposable
             else
             else
             {
             {
                 var p = cl.Proto;
                 var p = cl.Proto;
-                Source = p.GetRoot().Name;
+                Source = p.Source;
                 LineDefined = p.LineDefined;
                 LineDefined = p.LineDefined;
                 LastLineDefined = p.LastLineDefined;
                 LastLineDefined = p.LastLineDefined;
-                What = (p.GetRoot() == p) ? "main" : "Lua";
+                What = (LineDefined==0) ? "main" : "Lua";
             }
             }
 
 
             ShortSourceLength = WriteShortSource(Source, ShortSource);
             ShortSourceLength = WriteShortSource(Source, ShortSource);
@@ -289,19 +289,18 @@ internal readonly struct LuaDebug : IDisposable
     }
     }
 
 
 
 
-    internal static string? GetLocalName(Chunk chunk, int register, int pc)
+    internal static string? GetLocalName(Prototype prototype, int register, int pc)
     {
     {
-        var locals = chunk.Locals;
-        foreach (var local in locals)
+        var locals = prototype.LocalVariables;
+        var localId = register+1;
+        foreach (var l in locals)
         {
         {
-            if (local.Index == register && pc >= local.StartPc && pc < local.EndPc)
+            if(pc<l.StartPc)break;
+            if(l.EndPc<=pc)continue;
+            localId--;
+            if (localId == 0)
             {
             {
-                return local.Name.ToString();
-            }
-
-            if (local.Index > register)
-            {
-                break;
+                return l.Name;
             }
             }
         }
         }
 
 
@@ -315,12 +314,12 @@ internal readonly struct LuaDebug : IDisposable
         else return pc; /* current position sets that register */
         else return pc; /* current position sets that register */
     }
     }
 
 
-    internal static int FindSetRegister(Chunk chunk, int lastPc, int reg)
+    internal static int FindSetRegister(Prototype prototype, int lastPc, int reg)
     {
     {
         int pc;
         int pc;
         int setReg = -1; /* keep last instruction that changed 'reg' */
         int setReg = -1; /* keep last instruction that changed 'reg' */
         int jmpTarget = 0; /* any code before this address is conditional */
         int jmpTarget = 0; /* any code before this address is conditional */
-        var instructions = chunk.Instructions;
+        var instructions = prototype.Code;
         for (pc = 0; pc < lastPc; pc++)
         for (pc = 0; pc < lastPc; pc++)
         {
         {
             Instruction i = instructions[pc];
             Instruction i = instructions[pc];
@@ -377,12 +376,12 @@ internal readonly struct LuaDebug : IDisposable
         return setReg;
         return setReg;
     }
     }
 
 
-    static void GetConstantName(Chunk p, int pc, int c, out string name)
+    static void GetConstantName(Prototype p, int pc, int c, out string name)
     {
     {
         if (c >= 256)
         if (c >= 256)
         {
         {
             /* is 'c' a constant? */
             /* is 'c' a constant? */
-            ref var kvalue = ref p.Constants[c - 256];
+             var kvalue =  p.Constants[c - 256];
             if (kvalue.TryReadString(out name))
             if (kvalue.TryReadString(out name))
             {
             {
                 /* literal constant? */
                 /* literal constant? */
@@ -407,19 +406,19 @@ internal readonly struct LuaDebug : IDisposable
     }
     }
 
 
 
 
-    internal static string? GetName(Chunk chunk, int lastPc, int reg, out string? name)
+    internal static string? GetName(Prototype prototype, int lastPc, int reg, out string? name)
     {
     {
-        name = GetLocalName(chunk, reg, lastPc);
+        name = GetLocalName(prototype, reg, lastPc);
         if (name != null)
         if (name != null)
         {
         {
             return "local";
             return "local";
         }
         }
 
 
-        var pc = FindSetRegister(chunk, lastPc, reg);
+        var pc = FindSetRegister(prototype, lastPc, reg);
         if (pc != -1)
         if (pc != -1)
         {
         {
             /* could find instruction? */
             /* could find instruction? */
-            Instruction i = chunk.Instructions[pc];
+            Instruction i = prototype.Code[pc];
             OpCode op = i.OpCode;
             OpCode op = i.OpCode;
             switch (op)
             switch (op)
             {
             {
@@ -427,7 +426,7 @@ internal readonly struct LuaDebug : IDisposable
                     {
                     {
                         int b = i.B; /* move from 'b' to 'a' */
                         int b = i.B; /* move from 'b' to 'a' */
                         if (b < i.A)
                         if (b < i.A)
-                            return GetName(chunk, pc, b, out name); /* get name for 'b' */
+                            return GetName(prototype, pc, b, out name); /* get name for 'b' */
                         break;
                         break;
                     }
                     }
                 case OpCode.GetTabUp:
                 case OpCode.GetTabUp:
@@ -437,23 +436,23 @@ internal readonly struct LuaDebug : IDisposable
                         int t = i.B; /* table index */
                         int t = i.B; /* table index */
 
 
                         var vn = (op == OpCode.GetTable) /* name of indexed variable */
                         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);
+                            ? GetLocalName(prototype, t + 1, pc)
+                            : prototype.UpValues[t].Name.ToString();
+                        GetConstantName(prototype, pc, k, out name);
                         return vn is "_ENV" ? "global" : "field";
                         return vn is "_ENV" ? "global" : "field";
                     }
                     }
                 case OpCode.GetUpVal:
                 case OpCode.GetUpVal:
                     {
                     {
-                        name = chunk.UpValues[i.B].Name.ToString();
+                        name = prototype.UpValues[i.B].Name.ToString();
                         return "upvalue";
                         return "upvalue";
                     }
                     }
                 case OpCode.LoadK:
                 case OpCode.LoadK:
                 case OpCode.LoadKX:
                 case OpCode.LoadKX:
                     {
                     {
-                        uint b = (op == OpCode.LoadKX)
+                        int b = (op == OpCode.LoadKX)
                             ? i.Bx
                             ? i.Bx
-                            : (chunk.Instructions[pc + 1].Ax);
-                        if (chunk.Constants[b].TryReadString(out name))
+                            : (prototype.Code[pc + 1].Ax);
+                        if (prototype.Constants[b].TryReadString(out name))
                         {
                         {
                             return "constant";
                             return "constant";
                         }
                         }
@@ -463,7 +462,7 @@ internal readonly struct LuaDebug : IDisposable
                 case OpCode.Self:
                 case OpCode.Self:
                     {
                     {
                         int k = i.C; /* key index */
                         int k = i.C; /* key index */
-                        GetConstantName(chunk, pc, k, out name);
+                        GetConstantName(prototype, pc, k, out name);
                         return "method";
                         return "method";
                     }
                     }
                 default: break; /* go through to return NULL */
                 default: break; /* go through to return NULL */
@@ -473,14 +472,14 @@ internal readonly struct LuaDebug : IDisposable
         return null; /* could not find reasonable name */
         return null; /* could not find reasonable name */
     }
     }
 
 
-    internal static string? GetFuncName(Chunk chunk, int pc, out string? name)
+    internal static string? GetFuncName(Prototype prototype, int pc, out string? name)
     {
     {
-        Instruction i = chunk.Instructions[pc]; /* calling instruction */
+        Instruction i = prototype.Code[pc]; /* calling instruction */
         switch (i.OpCode)
         switch (i.OpCode)
         {
         {
             case OpCode.Call:
             case OpCode.Call:
             case OpCode.TailCall: /* get function name */
             case OpCode.TailCall: /* get function name */
-                return GetName(chunk, pc, i.A, out name);
+                return GetName(prototype, pc, i.A, out name);
             case OpCode.TForCall:
             case OpCode.TForCall:
                 {
                 {
                     /* for iterator */
                     /* for iterator */

+ 67 - 0
src/Lua/Internal/Pool.cs

@@ -0,0 +1,67 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Lua.Internal;
+
+internal interface IPoolNode<T>
+{
+    ref T? NextNode { get; }
+}
+
+// mutable struct, don't mark readonly.
+[StructLayout(LayoutKind.Auto)]
+internal struct LinkedPool<T>
+    where T : class, IPoolNode<T>
+{
+    int gate;
+    int size;
+    T? root;
+
+    public int Size => size;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool TryPop(out T result)
+    {
+        if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
+        {
+            var v = root;
+            if (!(v is null))
+            {
+                ref var nextNode = ref v.NextNode;
+                root = nextNode;
+                nextNode = null;
+                size--;
+                result = v;
+                Volatile.Write(ref gate, 0);
+                return true;
+            }
+
+            Volatile.Write(ref gate, 0);
+        }
+
+        result = default!;
+        return false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool TryPush(T item)
+    {
+        if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
+        {
+            if (size < 64)
+            {
+                item.NextNode = root;
+                root = item;
+                size++;
+                Volatile.Write(ref gate, 0);
+                return true;
+            }
+            else
+            {
+                Volatile.Write(ref gate, 0);
+            }
+        }
+
+        return false;
+    }
+}

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

@@ -51,7 +51,7 @@ internal ref struct PooledList<T>
             {
             {
                 newSize *= 2;
                 newSize *= 2;
             }
             }
-            
+
             var newArray = ArrayPool<T>.Shared.Rent(newSize);
             var newArray = ArrayPool<T>.Shared.Rent(newSize);
             buffer.AsSpan().CopyTo(newArray);
             buffer.AsSpan().CopyTo(newArray);
             ArrayPool<T>.Shared.Return(buffer);
             ArrayPool<T>.Shared.Return(buffer);
@@ -61,7 +61,7 @@ internal ref struct PooledList<T>
         items.CopyTo(buffer.AsSpan()[tail..]);
         items.CopyTo(buffer.AsSpan()[tail..]);
         tail += items.Length;
         tail += items.Length;
     }
     }
-    
+
     public void Clear()
     public void Clear()
     {
     {
         ThrowIfDisposed();
         ThrowIfDisposed();
@@ -106,10 +106,9 @@ internal ref struct PooledList<T>
     {
     {
         if (tail == -1) ThrowDisposedException();
         if (tail == -1) ThrowDisposedException();
     }
     }
-    
+
     void ThrowDisposedException()
     void ThrowDisposedException()
     {
     {
         throw new ObjectDisposedException(nameof(PooledList<T>));
         throw new ObjectDisposedException(nameof(PooledList<T>));
     }
     }
-    
 }
 }

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

@@ -43,6 +43,7 @@ internal static class StringHelper
                         {
                         {
                             i++;
                             i++;
                         }
                         }
+
                         break;
                         break;
                     case 'a':
                     case 'a':
                         builder.Append('\a');
                         builder.Append('\a');
@@ -108,6 +109,7 @@ internal static class StringHelper
                             result = null;
                             result = null;
                             return false;
                             return false;
                         }
                         }
+
                         break;
                         break;
                     default:
                     default:
                         if (IsNumber(c))
                         if (IsNumber(c))
@@ -129,6 +131,7 @@ internal static class StringHelper
                             result = null;
                             result = null;
                             return false;
                             return false;
                         }
                         }
+
                         break;
                         break;
                 }
                 }
             }
             }

+ 15 - 16
src/Lua/Loaders/CompositeModuleLoader.cs

@@ -1,4 +1,3 @@
-
 namespace Lua.Loaders;
 namespace Lua.Loaders;
 
 
 public static class CompositeModuleLoader
 public static class CompositeModuleLoader
@@ -8,7 +7,7 @@ public static class CompositeModuleLoader
         public bool Exists(string moduleName)
         public bool Exists(string moduleName)
         {
         {
             return loader0.Exists(moduleName) &&
             return loader0.Exists(moduleName) &&
-                loader1.Exists(moduleName);
+                   loader1.Exists(moduleName);
         }
         }
 
 
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
@@ -32,8 +31,8 @@ public static class CompositeModuleLoader
         public bool Exists(string moduleName)
         public bool Exists(string moduleName)
         {
         {
             return loader0.Exists(moduleName) &&
             return loader0.Exists(moduleName) &&
-                loader1.Exists(moduleName) &&
-                loader2.Exists(moduleName);
+                   loader1.Exists(moduleName) &&
+                   loader2.Exists(moduleName);
         }
         }
 
 
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
@@ -62,9 +61,9 @@ public static class CompositeModuleLoader
         public bool Exists(string moduleName)
         public bool Exists(string moduleName)
         {
         {
             return loader0.Exists(moduleName) &&
             return loader0.Exists(moduleName) &&
-                loader1.Exists(moduleName) &&
-                loader2.Exists(moduleName) &&
-                loader3.Exists(moduleName);
+                   loader1.Exists(moduleName) &&
+                   loader2.Exists(moduleName) &&
+                   loader3.Exists(moduleName);
         }
         }
 
 
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
@@ -98,10 +97,10 @@ public static class CompositeModuleLoader
         public bool Exists(string moduleName)
         public bool Exists(string moduleName)
         {
         {
             return loader0.Exists(moduleName) &&
             return loader0.Exists(moduleName) &&
-                loader1.Exists(moduleName) &&
-                loader2.Exists(moduleName) &&
-                loader3.Exists(moduleName) &&
-                loader4.Exists(moduleName);
+                   loader1.Exists(moduleName) &&
+                   loader2.Exists(moduleName) &&
+                   loader3.Exists(moduleName) &&
+                   loader4.Exists(moduleName);
         }
         }
 
 
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
@@ -140,11 +139,11 @@ public static class CompositeModuleLoader
         public bool Exists(string moduleName)
         public bool Exists(string moduleName)
         {
         {
             return loader0.Exists(moduleName) &&
             return loader0.Exists(moduleName) &&
-                loader1.Exists(moduleName) &&
-                loader2.Exists(moduleName) &&
-                loader3.Exists(moduleName) &&
-                loader4.Exists(moduleName) &&
-                loader5.Exists(moduleName);
+                   loader1.Exists(moduleName) &&
+                   loader2.Exists(moduleName) &&
+                   loader3.Exists(moduleName) &&
+                   loader4.Exists(moduleName) &&
+                   loader5.Exists(moduleName);
         }
         }
 
 
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)
         public ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default)

+ 0 - 1
src/Lua/Loaders/FileModuleLoader.cs

@@ -1,4 +1,3 @@
-
 namespace Lua.Loaders;
 namespace Lua.Loaders;
 
 
 public sealed class FileModuleLoader : ILuaModuleLoader
 public sealed class FileModuleLoader : ILuaModuleLoader

+ 1 - 1
src/Lua/LuaCoroutine.cs

@@ -167,7 +167,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                 {
                 {
                     traceback = (ex as LuaRuntimeException)?.LuaTraceback;
                     traceback = (ex as LuaRuntimeException)?.LuaTraceback;
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
-                    return context.Return(false, ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message);
+                    return context.Return(false, ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message);
                 }
                 }
                 else
                 else
                 {
                 {

+ 1 - 2
src/Lua/LuaFunctionExecutionContext.cs

@@ -1,6 +1,5 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
-using Lua.CodeAnalysis;
 using Lua.Runtime;
 using Lua.Runtime;
 
 
 namespace Lua;
 namespace Lua;
@@ -13,7 +12,7 @@ public readonly record struct LuaFunctionExecutionContext
     public required int ArgumentCount { get; init; }
     public required int ArgumentCount { get; init; }
     public required int FrameBase { get; init; }
     public required int FrameBase { get; init; }
     public required int ReturnFrameBase { get; init; }
     public required int ReturnFrameBase { get; init; }
-    public SourcePosition? SourcePosition { get; init; }
+    public int? SourceLine { get; init; }
     public string? RootChunkName { get; init; }
     public string? RootChunkName { get; init; }
     public string? ChunkName { get; init; }
     public string? ChunkName { get; init; }
     public int? CallerInstructionIndex { get; init; }
     public int? CallerInstructionIndex { get; init; }

+ 45 - 6
src/Lua/LuaState.cs

@@ -1,8 +1,11 @@
+using Lua.CodeAnalysis.Compilation;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using Lua.Internal;
 using Lua.Internal;
 using Lua.Loaders;
 using Lua.Loaders;
 using Lua.Runtime;
 using Lua.Runtime;
+using System.Buffers;
+using System.Text;
 
 
 namespace Lua;
 namespace Lua;
 
 
@@ -22,6 +25,7 @@ public sealed class LuaState
 
 
     FastStackCore<LuaDebug.LuaDebugBuffer> debugBufferPool;
     FastStackCore<LuaDebug.LuaDebugBuffer> debugBufferPool;
 
 
+    internal int CallCount;
     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;
@@ -62,7 +66,7 @@ public sealed class LuaState
         envUpValue = UpValue.Closed(environment);
         envUpValue = UpValue.Closed(environment);
     }
     }
 
 
-    public async ValueTask<LuaResult> RunAsync(Chunk chunk, CancellationToken cancellationToken = default)
+    public async ValueTask<LuaResult> RunAsync(LuaClosure closure, CancellationToken cancellationToken = default)
     {
     {
         ThrowIfResultNotDisposed();
         ThrowIfResultNotDisposed();
         ThrowIfRunning();
         ThrowIfRunning();
@@ -70,7 +74,6 @@ public sealed class LuaState
         Volatile.Write(ref isRunning, true);
         Volatile.Write(ref isRunning, true);
         try
         try
         {
         {
-            var closure = new LuaClosure(this, chunk);
             await closure.InvokeAsync(new()
             await closure.InvokeAsync(new()
             {
             {
                 State = this,
                 State = this,
@@ -78,9 +81,9 @@ public sealed class LuaState
                 ArgumentCount = 0,
                 ArgumentCount = 0,
                 FrameBase = 0,
                 FrameBase = 0,
                 ReturnFrameBase = 0,
                 ReturnFrameBase = 0,
-                SourcePosition = null,
-                RootChunkName = chunk.Name,
-                ChunkName = chunk.Name,
+                SourceLine = null,
+                RootChunkName = closure.Proto.Source,
+                ChunkName = closure.Proto.Source,
             }, cancellationToken);
             }, cancellationToken);
 
 
             return new LuaResult(CurrentThread.Stack, 0);
             return new LuaResult(CurrentThread.Stack, 0);
@@ -202,7 +205,7 @@ public sealed class LuaState
             if (upValue.RegisterIndex >= frameBase)
             if (upValue.RegisterIndex >= frameBase)
             {
             {
                 upValue.Close();
                 upValue.Close();
-                openUpValues.RemoveAtSwapback(i);
+                openUpValues.RemoveAtSwapBack(i);
                 i--;
                 i--;
             }
             }
         }
         }
@@ -223,4 +226,40 @@ public sealed class LuaState
             throw new InvalidOperationException("the lua state is currently running");
             throw new InvalidOperationException("the lua state is currently running");
         }
         }
     }
     }
+
+
+    public unsafe LuaClosure Compile(ReadOnlySpan<char> sourceCode, string source,LuaTable? environment = null)
+    {
+        Prototype prototype;
+        fixed (char* ptr = sourceCode)
+        {
+            prototype= Parser.Parse(this, new (ptr,sourceCode.Length), source);
+        }
+        
+        return new LuaClosure(this, prototype, environment);
+    }
+    
+    public LuaClosure Compile(ReadOnlySpan<byte> code, string source , string mode = "bt", LuaTable? environment = null)
+    {
+        if (code.Length > 4)
+        {
+            if (code[0] == '\e')
+            {
+                return new LuaClosure(this,Parser.UnDump(code,source),environment);
+            }
+        }
+        var charCount = Encoding.UTF8.GetCharCount(code);
+        var pooled = ArrayPool<char>.Shared.Rent(charCount);
+        try
+        {
+            var chars = pooled.AsSpan(0, charCount);
+            Encoding.UTF8.GetChars(code, chars);
+            return Compile(chars, source,environment);
+        }
+        finally
+        {
+            ArrayPool<char>.Shared.Return(pooled);
+        }
+    }
+    
 }
 }

+ 13 - 17
src/Lua/LuaStateExtensions.cs

@@ -1,5 +1,5 @@
 using Lua.CodeAnalysis.Compilation;
 using Lua.CodeAnalysis.Compilation;
-using Lua.CodeAnalysis.Syntax;
+using Lua.CodeAnalysis;
 
 
 namespace Lua;
 namespace Lua;
 
 
@@ -7,39 +7,35 @@ public static class LuaStateExtensions
 {
 {
     public static async ValueTask<int> DoStringAsync(this LuaState state, string source, Memory<LuaValue> buffer, string? chunkName = null, CancellationToken cancellationToken = default)
     public static async ValueTask<int> DoStringAsync(this LuaState state, string source, Memory<LuaValue> buffer, string? chunkName = null, CancellationToken cancellationToken = default)
     {
     {
-        var syntaxTree = LuaSyntaxTree.Parse(source, chunkName);
-        var chunk = LuaCompiler.Default.Compile(syntaxTree, chunkName);
-        using var result = await state.RunAsync(chunk, cancellationToken);
-        result.AsSpan().CopyTo(buffer.Span);
+        var closure = state.Compile(source, chunkName??source);
+        using var result = await state.RunAsync(closure, cancellationToken);
+        result.AsSpan()[..Math.Min(buffer.Length,result.Length)].CopyTo(buffer.Span);
         return result.Count;
         return result.Count;
     }
     }
-
+    
     public static async ValueTask<LuaValue[]> DoStringAsync(this LuaState state, string source, string? chunkName = null, CancellationToken cancellationToken = default)
     public static async ValueTask<LuaValue[]> DoStringAsync(this LuaState state, string source, string? chunkName = null, CancellationToken cancellationToken = default)
     {
     {
-        var syntaxTree = LuaSyntaxTree.Parse(source, chunkName);
-        var chunk = LuaCompiler.Default.Compile(syntaxTree, chunkName);
+        var chunk = state.Compile(source, chunkName??source);
         using var result = await state.RunAsync(chunk, cancellationToken);
         using var result = await state.RunAsync(chunk, cancellationToken);
         return result.AsSpan().ToArray();
         return result.AsSpan().ToArray();
     }
     }
 
 
     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 bytes = await File.ReadAllBytesAsync(path, cancellationToken);
         var fileName = "@" + Path.GetFileName(path);
         var fileName = "@" + Path.GetFileName(path);
-        var syntaxTree = LuaSyntaxTree.Parse(text, fileName);
-        var chunk = LuaCompiler.Default.Compile(syntaxTree, fileName);
-        using var result = await state.RunAsync(chunk, cancellationToken);
-        result.AsSpan().CopyTo(buffer.Span);
+        var closure = state.Compile(bytes, fileName);
+        using var result = await state.RunAsync(closure, cancellationToken);
+        result.AsSpan()[..Math.Min(buffer.Length,result.Length)].CopyTo(buffer.Span);
         return result.Count;
         return result.Count;
     }
     }
 
 
     public static async ValueTask<LuaValue[]> DoFileAsync(this LuaState state, string path, CancellationToken cancellationToken = default)
     public static async ValueTask<LuaValue[]> DoFileAsync(this LuaState state, string path, CancellationToken cancellationToken = default)
     {
     {
-        var text = await File.ReadAllTextAsync(path, cancellationToken);
+        var bytes = await File.ReadAllBytesAsync(path, cancellationToken);
         var fileName = "@" + Path.GetFileName(path);
         var fileName = "@" + Path.GetFileName(path);
-        var syntaxTree = LuaSyntaxTree.Parse(text, fileName);
-        var chunk = LuaCompiler.Default.Compile(syntaxTree, fileName);
-        using var result = await state.RunAsync(chunk, cancellationToken);
+        var closure = state.Compile(bytes, fileName);
+        using var result = await state.RunAsync(closure, cancellationToken);
         return result.AsSpan().ToArray();
         return result.AsSpan().ToArray();
     }
     }
 }
 }

+ 3 - 3
src/Lua/LuaTable.cs

@@ -11,7 +11,7 @@ public sealed class LuaTable
 
 
     public LuaTable(int arrayCapacity, int dictionaryCapacity)
     public LuaTable(int arrayCapacity, int dictionaryCapacity)
     {
     {
-        array = new LuaValue[arrayCapacity];
+        array = new LuaValue[Math.Max(arrayCapacity, 8)];
         dictionary = new(dictionaryCapacity);
         dictionary = new(dictionaryCapacity);
     }
     }
 
 
@@ -56,14 +56,14 @@ public sealed class LuaTable
                 if (MathEx.IsInteger(d))
                 if (MathEx.IsInteger(d))
                 {
                 {
                     var index = (int)d;
                     var index = (int)d;
-                    
+
                     var distance = index - array.Length;
                     var distance = index - array.Length;
                     if (distance > MaxDistance)
                     if (distance > MaxDistance)
                     {
                     {
                         dictionary[key] = value;
                         dictionary[key] = value;
                         return;
                         return;
                     }
                     }
-                    
+
                     if (0 < index && index < MaxArraySize && index <= Math.Max(array.Length * 2, 8))
                     if (0 < index && index < MaxArraySize && index <= Math.Max(array.Length * 2, 8))
                     {
                     {
                         if (array.Length < index)
                         if (array.Length < index)

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

@@ -1,30 +0,0 @@
-using Lua.CodeAnalysis;
-
-namespace Lua.Runtime;
-
-public sealed class Chunk
-{
-    public Chunk? Parent { get; internal set; }
-
-    public required string Name { get; init; }
-    public required Instruction[] Instructions { get; init; }
-    public required SourcePosition[] SourcePositions { get; init; }
-    public required LuaValue[] Constants { get; init; }
-    public required UpValueInfo[] UpValues { get; init; }
-    public required LocalValueInfo[] Locals { get; init; }
-    public required Chunk[] Functions { get; init; }
-    public required int ParameterCount { get; init; }
-    public required bool HasVariableArguments { get; init; }
-    public required byte MaxStackPosition { get; init; }
-    public required int LineDefined { get; init; }
-    public required int LastLineDefined { get; init; }
-
-    Chunk? rootCache;
-
-    internal Chunk GetRoot()
-    {
-        if (rootCache != null) return rootCache;
-        if (Parent == null) return rootCache = this;
-        return rootCache = Parent.GetRoot();
-    }
-}

+ 389 - 126
src/Lua/Runtime/Instruction.cs

@@ -1,159 +1,422 @@
-using System.Runtime.CompilerServices;
-
 namespace Lua.Runtime;
 namespace Lua.Runtime;
 
 
-public struct Instruction : IEquatable<Instruction>
+public partial struct Instruction(uint value)
 {
 {
-    uint _value;
-
-    public uint Value
-    {
-        get => _value;
-        set => _value = value;
-    }
+    public const int IABC = 0;
+    public const int IABx = 1;
+    public const int IAsBx = 2;
+    public const int IAx = 3;
+    
+    
+    public uint Value = value;
+    public static implicit operator Instruction(uint value) => new(value);
+
+    public static ReadOnlySpan<string> OpNames => opNames;
+
+    static readonly string[] opNames =
+    [
+        "MOVE",
+        "LOADK",
+        "LOADKX",
+        "LOADBOOL",
+        "LOADNIL",
+        "GETUPVAL",
+        "GETTABUP",
+        "GETTABLE",
+        "SETTABUP",
+        "SETUPVAL",
+        "SETTABLE",
+        "NEWTABLE",
+        "SELF",
+        "ADD",
+        "SUB",
+        "MUL",
+        "DIV",
+        "MOD",
+        "POW",
+        "UNM",
+        "NOT",
+        "LEN",
+        "CONCAT",
+        "JMP",
+        "EQ",
+        "LT",
+        "LE",
+        "TEST",
+        "TESTSET",
+        "CALL",
+        "TAILCALL",
+        "RETURN",
+        "FORLOOP",
+        "FORPREP",
+        "TFORCALL",
+        "TFORLOOP",
+        "SETLIST",
+        "CLOSURE",
+        "VARARG",
+        "EXTRAARG"
+    ];
+
+    /*
+const (
+    sizeC             = 9
+    sizeB             = 9
+    sizeBx            = sizeC + sizeB
+    sizeA             = 8
+    sizeAx            = sizeC + sizeB + sizeA
+    sizeOp            = 6
+    posOp             = 0
+    posA              = posOp + sizeOp
+    posC              = posA + sizeA
+    posB              = posC + sizeC
+    posBx             = posC
+    posAx             = posA
+    bitRK             = 1 << (sizeB - 1)
+    maxIndexRK        = bitRK - 1
+    maxArgAx          = 1<<sizeAx - 1
+    maxArgBx          = 1<<sizeBx - 1
+    maxArgSBx         = maxArgBx >> 1 // sBx is signed
+    maxArgA           = 1<<sizeA - 1
+    maxArgB           = 1<<sizeB - 1
+    maxArgC           = 1<<sizeC - 1
+    listItemsPerFlush = 50 // # list items to accumulate before a setList instruction
+)
+     */
+    public const int SizeC = 9;
+    public const int SizeB = 9;
+    public const int SizeBx = SizeC + SizeB;
+    public const int SizeA = 8;
+    public const int SizeAx = SizeC + SizeB + SizeA;
+    public const int SizeOp = 6;
+    public const int PosOp = 0;
+    public const int PosA = PosOp + SizeOp;
+    public const int PosC = PosA + SizeA;
+    public const int PosB = PosC + SizeC;
+    public const int PosBx = PosC;
+    public const int PosAx = PosA;
+    public const int BitRK = 1 << (SizeB - 1);
+    public const int MaxIndexRK = BitRK - 1;
+    public const int MaxArgAx = (1 << SizeAx) - 1;
+    public const int MaxArgBx = (1 << SizeBx) - 1;
+    public const int MaxArgSBx = MaxArgBx >> 1; // sBx is signed
+    public const int MaxArgA = (1 << SizeA) - 1;
+    public const int MaxArgB = (1 << SizeB) - 1;
+    public const int MaxArgC = (1 << SizeC) - 1;
+    public const int ListItemsPerFlush = 50; // # list items to accumulate before a setList instruction
+
+    /*
+func isConstant(x int) bool   { return 0 != x&bitRK }
+func constantIndex(r int) int { return r & ^bitRK }
+func asConstant(r int) int    { return r | bitRK }
+
+// creates a mask with 'n' 1 bits at position 'p'
+func mask1(n, p uint) instruction { return ^(^instruction(0) << n) << p }
+// creates a mask with 'n' 0 bits at position 'p'
+func mask0(n, p uint) instruction { return ^mask1(n, p) }
+func (i instruction) opCode() opCode         { return opCode(i >> posOp & (1<<sizeOp - 1)) }
+func (i instruction) arg(pos, size uint) int { return int(i >> pos & mask1(size, 0)) }
+func (i *instruction) setOpCode(op opCode)   { i.setArg(posOp, sizeOp, int(op)) }
+func (i *instruction) setArg(pos, size uint, arg int) {
+    *i = *i&mask0(size, pos) | instruction(arg)<<pos&mask1(size, pos)}
+     */
+    public static bool IsConstant(int x) => 0 != (x & BitRK);
+    public static int ConstantIndex(int r) => r & ~BitRK;
+    public static int AsConstant(int r) => r | BitRK;
+
+    // creates a mask with 'n' 1 bits at position 'p'
+    public static uint Mask1(uint n, uint p) => (uint)(~(~0 << (int)n) << (int)p);
+
+    // creates a mask with 'n' 0 bits at position 'p'
+    public static uint Mask0(uint n, uint p) => (~Mask1(n, p));
 
 
     public OpCode OpCode
     public OpCode OpCode
     {
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (OpCode)(byte)(_value & 0x3F); // 6 bits
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => _value = (_value & 0xFFFFFFC0) | ((uint)value & 0x3F);
+        get => (OpCode)((Value >> PosOp) & ((1 << SizeOp) - 1));
+        set => SetArg(PosOp, SizeOp, (byte)value);
     }
     }
 
 
-    public byte A
-    {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (byte)((_value >> 6)); // 8 bits
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => _value = (_value & 0xFFFFC03F) | (((uint)value & 0xFF) << 6);
-    }
+    public int Arg(uint pos) => (int)((Value >> (int)pos) & Mask1(1, 0));
 
 
-    public ushort B
+    public void SetArg(uint pos, uint size, int arg)
     {
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (ushort)((_value >> 23) & 0x1FF); // 9 bits
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => _value = (_value & 0xC07FFFFF) | (((uint)value & 0x1FF) << 23);
+        Value = (uint)(Value & Mask0(size, pos) | arg << (int)pos & Mask1(size, pos));
     }
     }
 
 
-    internal uint UIntB
-    {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (_value >> 23) & 0x1FF; // 9 bits
-    }
 
 
-    public ushort C
-    {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (ushort)((_value >> 14) & 0x1FF); // 9 bits
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => _value = (_value & 0xFF803FFF) | (((uint)value & 0x1FF) << 14);
-    }
+    /*
+func (i instruction) a() int   { return int(i >> posA & maxArgA) }
+func (i instruction) b() int   { return int(i >> posB & maxArgB) }
+func (i instruction) c() int   { return int(i >> posC & maxArgC) }
+func (i instruction) bx() int  { return int(i >> posBx & maxArgBx) }
+func (i instruction) ax() int  { return int(i >> posAx & maxArgAx) }
+func (i instruction) sbx() int { return int(i>>posBx&maxArgBx) - maxArgSBx }
 
 
-    internal uint UIntC
+     */
+    public int A
     {
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (_value >> 14) & 0x1FF; // 9 bits
+        get => (int)((Value >> PosA) & MaxArgA);
+        set => SetArg(PosA, SizeA, value);
     }
     }
 
 
-    public uint Bx
+    public int B
     {
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (_value >> 14) & 0x3FFFF; // 18 bits (14-31)
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => _value = (_value & 0x00003FFF) | ((value & 0x3FFFF) << 14);
+        get => (int)(Value >> PosB & MaxArgB);
+        set => SetArg(PosB, SizeB, value);
     }
     }
 
 
-    public int SBx
+    public int C
     {
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (int)(Bx - 131071); // signed 18 bits
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => Bx = (uint)(value + 131071);
+        get => (int)Value >> PosC & MaxArgC;
+        set => SetArg(PosC, SizeC, value);
     }
     }
 
 
-    public uint Ax
+    public int Bx
     {
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        get => (_value >> 6) & 0x3FFFFFF; // 26 bits (6-31)
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        set => _value = (_value & 0x0000003F) | ((value & 0x3FFFFFF) << 6);
+        get => (int)Value >> PosBx & MaxArgBx;
+        set => SetArg(PosBx, SizeBx, value);
     }
     }
 
 
-    public bool Equals(Instruction other)
+    public int Ax
     {
     {
-        return _value == other._value;
+        get => (int)Value >> PosAx & MaxArgAx;
+        set => SetArg(PosAx, SizeAx, value);
     }
     }
 
 
-    public override bool Equals(object? obj)
-    {
-        if (obj is Instruction instruction) return Equals(instruction);
-        return false;
-    }
-
-    public override int GetHashCode()
+    public int SBx
     {
     {
-        return _value.GetHashCode();
-    }
-
+        get => (int)(Value >> PosBx & MaxArgBx) - MaxArgSBx;
+        set => SetArg(PosBx, SizeBx, value + MaxArgSBx);
+    }
+
+    /*
+
+func createABC(op opCode, a, b, c int) instruction {
+    return instruction(op)<<posOp |
+        instruction(a)<<posA |
+        instruction(b)<<posB |
+        instruction(c)<<posC
+}
+
+func createABx(op opCode, a, bx int) instruction {
+    return instruction(op)<<posOp |
+        instruction(a)<<posA |
+        instruction(bx)<<posBx
+}
+
+func createAx(op opCode, a int) instruction { return instruction(op)<<posOp | instruction(a)<<posAx }
+
+*/
+    public static uint CreateABC(OpCode op, int a, int b, int c) => (uint)(((byte)op << PosOp) | (a << PosA) | (b << PosB) | (c << PosC));
+
+    public static uint CreateABx(OpCode op, int a, int bx) => (uint)(((byte)op << PosOp) | (a << PosA) | (bx << PosBx));
+    public static uint CreateAx(OpCode op, int a) => (uint)(((byte)op << PosOp) | (a << PosAx));
+
+
+    /*
+
+func (i instruction) String() string {
+    op := i.opCode()
+    s := opNames[op]
+    switch opMode(op) {
+    case iABC:
+        s = fmt.Sprintf("%s %d", s, i.a())
+        if bMode(op) == opArgK && isConstant(i.b()) {
+            s = fmt.Sprintf("%s constant %d", s, constantIndex(i.b()))
+        } else if bMode(op) != opArgN {
+            s = fmt.Sprintf("%s %d", s, i.b())
+        }
+        if cMode(op) == opArgK && isConstant(i.c()) {
+            s = fmt.Sprintf("%s constant %d", s, constantIndex(i.c()))
+        } else if cMode(op) != opArgN {
+            s = fmt.Sprintf("%s %d", s, i.c())
+        }
+    case iAsBx:
+        s = fmt.Sprintf("%s %d", s, i.a())
+        if bMode(op) != opArgN {
+            s = fmt.Sprintf("%s %d", s, i.sbx())
+        }
+    case iABx:
+        s = fmt.Sprintf("%s %d", s, i.a())
+        if bMode(op) != opArgN {
+            s = fmt.Sprintf("%s %d", s, i.bx())
+        }
+    case iAx:
+        s = fmt.Sprintf("%s %d", s, i.ax())
+    }
+    return s
+}
+*/
     public override string ToString()
     public override string ToString()
     {
     {
-        return OpCode switch
-        {
-            OpCode.Move => $"MOVE      {A} {B}",
-            OpCode.LoadK => $"LOADK     {A} {Bx}",
-            OpCode.LoadKX => $"LOADKX    {A}",
-            OpCode.LoadBool => $"LOADBOOL  {A} {B} {C}",
-            OpCode.LoadNil => $"LOADNIL   {A} {B}",
-            OpCode.GetUpVal => $"GETUPVAL  {A} {B}",
-            OpCode.GetTabUp => $"GETTABUP  {A} {B} {C}",
-            OpCode.GetTable => $"GETTABLE  {A} {B} {C}",
-            OpCode.SetTabUp => $"SETTABUP  {A} {B} {C}",
-            OpCode.SetUpVal => $"SETUPVAL  {A} {B}",
-            OpCode.SetTable => $"SETTABLE  {A} {B} {C}",
-            OpCode.NewTable => $"NEWTABLE  {A} {B} {C}",
-            OpCode.Self => $"SELF      {A} {B} {C}",
-            OpCode.Add => $"ADD       {A} {B} {C}",
-            OpCode.Sub => $"SUB       {A} {B} {C}",
-            OpCode.Mul => $"MUL       {A} {B} {C}",
-            OpCode.Div => $"DIV       {A} {B} {C}",
-            OpCode.Mod => $"MOD       {A} {B} {C}",
-            OpCode.Pow => $"POW       {A} {B} {C}",
-            OpCode.Unm => $"UNM       {A} {B}",
-            OpCode.Not => $"NOT       {A} {B}",
-            OpCode.Len => $"LEN       {A} {B}",
-            OpCode.Concat => $"CONCAT    {A} {B} {C}",
-            OpCode.Jmp => $"JMP       {A} {SBx}",
-            OpCode.Eq => $"EQ        {A} {B} {C}",
-            OpCode.Lt => $"LT        {A} {B} {C}",
-            OpCode.Le => $"LE        {A} {B} {C}",
-            OpCode.Test => $"TEST      {A} {C}",
-            OpCode.TestSet => $"TESTSET   {A} {B} {C}",
-            OpCode.Call => $"CALL      {A} {B} {C}",
-            OpCode.TailCall => $"TAILCALL  {A} {B} {C}",
-            OpCode.Return => $"RETURN    {A} {B}",
-            OpCode.ForLoop => $"FORLOOP   {A} {SBx}",
-            OpCode.ForPrep => $"FORPREP   {A} {SBx}",
-            OpCode.TForCall => $"TFORCALL  {A} {C}",
-            OpCode.TForLoop => $"TFORLOOP  {A} {SBx}",
-            OpCode.SetList => $"SETLIST   {A} {B} {C}",
-            OpCode.Closure => $"CLOSURE   {A} {SBx}",
-            OpCode.VarArg => $"VARARG    {A} {B}",
-            OpCode.ExtraArg => $"EXTRAARG  {Ax}",
-            _ => "",
-        };
-    }
-
-    public static bool operator ==(Instruction left, Instruction right)
-    {
-        return left.Equals(right);
-    }
-
-    public static bool operator !=(Instruction left, Instruction right)
-    {
-        return !(left == right);
-    }
-
+        var op = OpCode;
+        var s = OpNames[(byte)op];
+        switch (OpMode(op))
+        {
+            case IABC:
+                s = $"{s} {A}";
+                if (BMode(op) == OpArgK && IsConstant(B))
+                    s = $"{s} -{1 + ConstantIndex(B)}";
+                else if (BMode(op) != OpArgN)
+                    s = $"{s} {B}";
+                if (CMode(op) == OpArgK && IsConstant(C))
+                    s = $"{s} -{1 + ConstantIndex(C)}";
+                else if (CMode(op) != OpArgN)
+                    s = $"{s} {C}";
+                // s = $"{s} {A}";
+                // if (BMode(op) == OpArgK && IsConstant(B))
+                // 	s = $"{s} constant {ConstantIndex(B)}";
+                // else if (BMode(op) != OpArgN)
+                // 	s = $"{s} {B}";
+                // if (CMode(op) == OpArgK && IsConstant(C))
+                // 	s = $"{s} constant {ConstantIndex(C)}";
+                // else if (CMode(op) != OpArgN)
+                // 	s = $"{s} {C}";
+                break;
+            case IAsBx:
+                s = $"{s} {A}";
+                if (BMode(op) != OpArgN)
+                    s = $"{s} {SBx}";
+                break;
+            case IABx:
+                s = $"{s} {A}";
+                if (BMode(op) != OpArgN)
+                    s = $"{s} {Bx}";
+
+                break;
+            case IAx:
+                s = $"{s} {Ax}";
+                break;
+        }
+
+        return s;
+    }
+
+    /*
+
+func opmode(t, a, b, c, m int) byte { return byte(t<<7 | a<<6 | b<<4 | c<<2 | m) }
+*/
+    public static byte OpMode(int t, int a, int b, int c, int m) => (byte)(t << 7 | a << 6 | b << 4 | c << 2 | m);
+
+    /*
+const (
+    opArgN = iota // 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
+)
+*/
+    public const int OpArgN = 0;
+    public const int OpArgU = 1;
+    public const int OpArgR = 2;
+
+    public const int OpArgK = 3;
+
+    /*
+
+func opMode(m opCode) int     { return int(opModes[m] & 3) }
+func bMode(m opCode) byte     { return (opModes[m] >> 4) & 3 }
+func cMode(m opCode) byte     { return (opModes[m] >> 2) & 3 }
+func testAMode(m opCode) bool { return opModes[m]&(1<<6) != 0 }
+func testTMode(m opCode) bool { return opModes[m]&(1<<7) != 0 }
+*/
+    public static int OpMode(OpCode m) => (int)(opModes[(byte)m] & 3);
+    public static byte BMode(OpCode m) => (byte)((opModes[(byte)m] >> 4) & 3);
+    public static byte CMode(OpCode m) => (byte)((opModes[(byte)m] >> 2) & 3);
+    public static bool TestAMode(OpCode m) => (opModes[(byte)m] & (1 << 6)) != 0;
+    public static bool TestTMode(OpCode m) => (opModes[(byte)m] & (1 << 7)) != 0;
+
+/*
+var opModes []byte = []byte{
+//     T  A    B       C     mode		    opcode
+opmode(0, 1, opArgR, opArgN, iABC),  // opMove
+opmode(0, 1, opArgK, opArgN, iABx),  // opLoadConstant
+opmode(0, 1, opArgN, opArgN, iABx),  // opLoadConstantEx
+opmode(0, 1, opArgU, opArgU, iABC),  // opLoadBool
+opmode(0, 1, opArgU, opArgN, iABC),  // opLoadNil
+opmode(0, 1, opArgU, opArgN, iABC),  // opGetUpValue
+opmode(0, 1, opArgU, opArgK, iABC),  // opGetTableUp
+opmode(0, 1, opArgR, opArgK, iABC),  // opGetTable
+opmode(0, 0, opArgK, opArgK, iABC),  // opSetTableUp
+opmode(0, 0, opArgU, opArgN, iABC),  // opSetUpValue
+opmode(0, 0, opArgK, opArgK, iABC),  // opSetTable
+opmode(0, 1, opArgU, opArgU, iABC),  // opNewTable
+opmode(0, 1, opArgR, opArgK, iABC),  // opSelf
+opmode(0, 1, opArgK, opArgK, iABC),  // opAdd
+opmode(0, 1, opArgK, opArgK, iABC),  // opSub
+opmode(0, 1, opArgK, opArgK, iABC),  // opMul
+opmode(0, 1, opArgK, opArgK, iABC),  // opDiv
+opmode(0, 1, opArgK, opArgK, iABC),  // opMod
+opmode(0, 1, opArgK, opArgK, iABC),  // opPow
+opmode(0, 1, opArgR, opArgN, iABC),  // opUnaryMinus
+opmode(0, 1, opArgR, opArgN, iABC),  // opNot
+opmode(0, 1, opArgR, opArgN, iABC),  // opLength
+opmode(0, 1, opArgR, opArgR, iABC),  // opConcat
+opmode(0, 0, opArgR, opArgN, iAsBx), // opJump
+opmode(1, 0, opArgK, opArgK, iABC),  // opEqual
+opmode(1, 0, opArgK, opArgK, iABC),  // opLessThan
+opmode(1, 0, opArgK, opArgK, iABC),  // opLessOrEqual
+opmode(1, 0, opArgN, opArgU, iABC),  // opTest
+opmode(1, 1, opArgR, opArgU, iABC),  // opTestSet
+opmode(0, 1, opArgU, opArgU, iABC),  // opCall
+opmode(0, 1, opArgU, opArgU, iABC),  // opTailCall
+opmode(0, 0, opArgU, opArgN, iABC),  // opReturn
+opmode(0, 1, opArgR, opArgN, iAsBx), // opForLoop
+opmode(0, 1, opArgR, opArgN, iAsBx), // opForPrep
+opmode(0, 0, opArgN, opArgU, iABC),  // opTForCall
+opmode(0, 1, opArgR, opArgN, iAsBx), // opTForLoop
+opmode(0, 0, opArgU, opArgU, iABC),  // opSetList
+opmode(0, 1, opArgU, opArgN, iABx),  // opClosure
+opmode(0, 1, opArgU, opArgN, iABC),  // opVarArg
+opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
+}
+ */
+    public static ReadOnlySpan<byte> OpModes => (opModes);
+
+    static readonly byte[] opModes =
+    [
+        //         T   A    B         C          mode	opcode]
+        OpMode(0, 1, OpArgR, OpArgN, IABC), // opMove
+        OpMode(0, 1, OpArgK, OpArgN, IABx), // opLoadConstant
+        OpMode(0, 1, OpArgN, OpArgN, IABx), // opLoadConstantEx
+        OpMode(0, 1, OpArgU, OpArgU, IABC), // opLoadBool
+        OpMode(0, 1, OpArgU, OpArgN, IABC), // opLoadNil
+        OpMode(0, 1, OpArgU, OpArgN, IABC), // opGetUpValue
+        OpMode(0, 1, OpArgU, OpArgK, IABC), // opGetTableUp
+        OpMode(0, 1, OpArgR, OpArgK, IABC), // opGetTable
+        OpMode(0, 0, OpArgK, OpArgK, IABC), // opSetTableUp
+        OpMode(0, 0, OpArgU, OpArgN, IABC), // opSetUpValue
+        OpMode(0, 0, OpArgK, OpArgK, IABC), // opSetTable
+        OpMode(0, 1, OpArgU, OpArgU, IABC), // opNewTable
+        OpMode(0, 1, OpArgR, OpArgK, IABC), // opSelf
+        OpMode(0, 1, OpArgK, OpArgK, IABC), // opAdd
+        OpMode(0, 1, OpArgK, OpArgK, IABC), // opSub
+        OpMode(0, 1, OpArgK, OpArgK, IABC), // opMul
+        OpMode(0, 1, OpArgK, OpArgK, IABC), // opDiv
+        OpMode(0, 1, OpArgK, OpArgK, IABC), // opMod
+        OpMode(0, 1, OpArgK, OpArgK, IABC), // opPow
+        OpMode(0, 1, OpArgR, OpArgN, IABC), // opUnaryMinus
+        OpMode(0, 1, OpArgR, OpArgN, IABC), // opNot
+        OpMode(0, 1, OpArgR, OpArgN, IABC), // opLength
+        OpMode(0, 1, OpArgR, OpArgR, IABC), // opConcat
+        OpMode(0, 0, OpArgR, OpArgN, IAsBx), // opJump
+        OpMode(1, 0, OpArgK, OpArgK, IABC), // opEqual
+        OpMode(1, 0, OpArgK, OpArgK, IABC), // opLessThan
+        OpMode(1, 0, OpArgK, OpArgK, IABC), // opLessOrEqual
+        OpMode(1, 0, OpArgN, OpArgU, IABC), // opTest
+        OpMode(1, 1, OpArgR, OpArgU, IABC), // opTestSet
+        OpMode(0, 1, OpArgU, OpArgU, IABC), // opCall
+        OpMode(0, 1, OpArgU, OpArgU, IABC), // opTailCall
+        OpMode(0, 0, OpArgU, OpArgN, IABC), // opReturn
+        OpMode(0, 1, OpArgR, OpArgN, IAsBx), // opForLoop
+        OpMode(0, 1, OpArgR, OpArgN, IAsBx), // opForPrep
+        OpMode(0, 0, OpArgN, OpArgU, IABC), // opTForCall
+        OpMode(0, 1, OpArgR, OpArgN, IAsBx), // opTForLoop
+        OpMode(0, 0, OpArgU, OpArgU, IABC), // opSetList
+        OpMode(0, 1, OpArgU, OpArgN, IABx), // opClosure
+        OpMode(0, 1, OpArgU, OpArgN, IABC), // opVarArg
+        OpMode(0, 0, OpArgU, OpArgU, IAx), // opExtraArg
+    ];
+    
     /// <summary>
     /// <summary>
     /// R(A) := R(B)
     /// R(A) := R(B)
     /// </summary>
     /// </summary>
@@ -176,7 +439,7 @@ public struct Instruction : IEquatable<Instruction>
         {
         {
             OpCode = OpCode.LoadK,
             OpCode = OpCode.LoadK,
             A = a,
             A = a,
-            Bx = bx,
+            Bx = (int)bx,
         };
         };
     }
     }
 
 
@@ -693,7 +956,7 @@ public struct Instruction : IEquatable<Instruction>
         return new()
         return new()
         {
         {
             OpCode = OpCode.ExtraArg,
             OpCode = OpCode.ExtraArg,
-            Ax = ax,
+            Ax = (int)ax,
         };
         };
     }
     }
 }
 }

+ 23 - 13
src/Lua/Runtime/LuaClosure.cs

@@ -1,3 +1,4 @@
+using Lua.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using Lua.Internal;
 using Lua.Internal;
 
 
@@ -5,24 +6,37 @@ namespace Lua.Runtime;
 
 
 public sealed class LuaClosure : LuaFunction
 public sealed class LuaClosure : LuaFunction
 {
 {
-    Chunk proto;
     FastListCore<UpValue> upValues;
     FastListCore<UpValue> upValues;
 
 
-    public LuaClosure(LuaState state, Chunk proto, LuaTable? environment = null)
-        : base(proto.Name, (context, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, ct))
+    public LuaClosure(LuaState state, Prototype proto, LuaTable? environment = null)
+        : base(proto.Source, static (context, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, ct))
     {
     {
-        this.proto = proto;
+        Proto = proto;
+        if (environment != null)
+        {
+            upValues.Add(UpValue.Closed(environment));
+            return;
+        }
+
+        if (state.CurrentThread.CallStack.Count == 0)
+        {
+            upValues.Add(state.EnvUpValue);
+            return;
+        }
+
+        var baseIndex = state.CurrentThread.CallStack.Peek().Base;
 
 
         // add upvalues
         // add upvalues
         for (int i = 0; i < proto.UpValues.Length; i++)
         for (int i = 0; i < proto.UpValues.Length; i++)
         {
         {
             var description = proto.UpValues[i];
             var description = proto.UpValues[i];
-            var upValue = GetUpValueFromDescription(state, state.CurrentThread, environment == null ? state.EnvUpValue : UpValue.Closed(environment), description);
+            var upValue = GetUpValueFromDescription(state, state.CurrentThread, description, baseIndex);
             upValues.Add(upValue);
             upValues.Add(upValue);
         }
         }
     }
     }
 
 
-    public Chunk Proto => proto;
+    public Prototype Proto { get; }
+
     public ReadOnlySpan<UpValue> UpValues => upValues.AsSpan();
     public ReadOnlySpan<UpValue> UpValues => upValues.AsSpan();
     internal Span<UpValue> GetUpValuesSpan() => upValues.AsSpan();
     internal Span<UpValue> GetUpValuesSpan() => upValues.AsSpan();
 
 
@@ -44,17 +58,13 @@ public sealed class LuaClosure : LuaFunction
         upValues[index].SetValue(value);
         upValues[index].SetValue(value);
     }
     }
 
 
-    static UpValue GetUpValueFromDescription(LuaState state, LuaThread thread, UpValue envUpValue, UpValueInfo description)
+    static UpValue GetUpValueFromDescription(LuaState state, LuaThread thread, UpValueDesc description, int baseIndex = 0)
     {
     {
-        if (description.IsInRegister)
+        if (description.IsLocal)
         {
         {
-            return state.GetOrAddUpValue(thread, thread.GetCurrentFrame().Base + description.Index);
+            return state.GetOrAddUpValue(thread, baseIndex + description.Index);
         }
         }
 
 
-        if (description.Index == -1) // -1 is global environment
-        {
-            return envUpValue;
-        }
 
 
         if (thread.GetCurrentFrame().Function is LuaClosure parentClosure)
         if (thread.GetCurrentFrame().Function is LuaClosure parentClosure)
         {
         {

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

@@ -9,14 +9,14 @@ internal static class LuaRuntimeExtensions
     {
     {
         result = default;
         result = default;
         return state.TryGetMetatable(value, out var metatable) &&
         return state.TryGetMetatable(value, out var metatable) &&
-            metatable.TryGetValue(methodName, out result);
+               metatable.TryGetValue(methodName, out result);
     }
     }
 
 
     [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 LuaClosure { Proto.HasVariableArguments: true } luaClosure
         return function is LuaClosure { Proto.HasVariableArguments: true } luaClosure
-            ?argumentCount - luaClosure.Proto.ParameterCount
+            ? argumentCount - luaClosure.Proto.ParameterCount
             : 0;
             : 0;
     }
     }
 }
 }

+ 7 - 8
src/Lua/Runtime/LuaVirtualMachine.Debug.cs

@@ -5,7 +5,7 @@ namespace Lua.Runtime;
 public static partial class LuaVirtualMachine
 public static partial class LuaVirtualMachine
 {
 {
     [MethodImpl(MethodImplOptions.NoInlining)]
     [MethodImpl(MethodImplOptions.NoInlining)]
-    static bool ExecutePerInstructionHook(ref VirtualMachineExecutionContext context)
+    static bool ExecutePerInstructionHook(VirtualMachineExecutionContext context)
     {
     {
         var r = Impl(context);
         var r = Impl(context);
         if (r.IsCompleted)
         if (r.IsCompleted)
@@ -25,6 +25,8 @@ public static partial class LuaVirtualMachine
         static async ValueTask<int> Impl(VirtualMachineExecutionContext context)
         static async ValueTask<int> Impl(VirtualMachineExecutionContext context)
         {
         {
             bool countHookIsDone = false;
             bool countHookIsDone = false;
+            var pc = context.Pc;
+            var prototype = context.Prototype;
             if (context.Thread.IsCountHookEnabled && --context.Thread.HookCount == 0)
             if (context.Thread.IsCountHookEnabled && --context.Thread.HookCount == 0)
             {
             {
                 context.Thread.HookCount = context.Thread.BaseHookCount;
                 context.Thread.HookCount = context.Thread.BaseHookCount;
@@ -62,11 +64,10 @@ public static partial class LuaVirtualMachine
 
 
             if (context.Thread.IsLineHookEnabled)
             if (context.Thread.IsLineHookEnabled)
             {
             {
-                var pc = context.Pc;
-                var sourcePositions = context.Chunk.SourcePositions;
-                var line = sourcePositions[pc].Line;
+                var sourcePositions = prototype.LineInfo;
+                var line = sourcePositions[pc];
 
 
-                if (countHookIsDone || pc == 0 || context.Thread.LastPc < 0 || pc <= context.Thread.LastPc || sourcePositions[context.Thread.LastPc].Line != line)
+                if (countHookIsDone || pc == 0 || context.Thread.LastPc < 0 || pc <= context.Thread.LastPc || sourcePositions[context.Thread.LastPc] != line)
                 {
                 {
                     if (countHookIsDone)
                     if (countHookIsDone)
                     {
                     {
@@ -99,7 +100,6 @@ public static partial class LuaVirtualMachine
                     context.Thread.PushCallStackFrame(frame);
                     context.Thread.PushCallStackFrame(frame);
                     await hook.Func(funcContext, context.CancellationToken);
                     await hook.Func(funcContext, context.CancellationToken);
                     context.Thread.IsInHook = false;
                     context.Thread.IsInHook = false;
-                    context.Pc--;
                     context.Thread.LastPc = pc;
                     context.Thread.LastPc = pc;
                     return 0;
                     return 0;
                 }
                 }
@@ -109,7 +109,6 @@ public static partial class LuaVirtualMachine
 
 
             if (countHookIsDone)
             if (countHookIsDone)
             {
             {
-                context.Pc--;
                 return 0;
                 return 0;
             }
             }
 
 
@@ -118,7 +117,7 @@ public static partial class LuaVirtualMachine
     }
     }
 
 
     [MethodImpl(MethodImplOptions.NoInlining)]
     [MethodImpl(MethodImplOptions.NoInlining)]
-    static ValueTask<int> ExecuteCallHook(ref VirtualMachineExecutionContext context, in CallStackFrame frame, int arguments, bool isTailCall = false)
+    static ValueTask<int> ExecuteCallHook(VirtualMachineExecutionContext context, in CallStackFrame frame, int arguments, bool isTailCall = false)
     {
     {
         return ExecuteCallHook(new()
         return ExecuteCallHook(new()
         {
         {

File diff suppressed because it is too large
+ 208 - 159
src/Lua/Runtime/LuaVirtualMachine.cs


+ 31 - 0
src/Lua/Runtime/Prototype.cs

@@ -0,0 +1,31 @@
+using Lua.CodeAnalysis;
+
+namespace Lua.Runtime;
+public  sealed  class Prototype(
+    string source,
+    int lineDefined,
+    int lastLineDefined,
+    int parameterCount,
+    int maxStackSize,
+    bool hasVariableArguments,
+    LuaValue[] constants,
+    Instruction[] code,
+    Prototype[] childPrototypes,
+    int[] lineInfo,
+    LocalVariable[] localVariables,
+    UpValueDesc[] upValues
+)
+{
+    public ReadOnlySpan<LuaValue> Constants => constants;
+    public   ReadOnlySpan<Instruction> Code => code;
+    public ReadOnlySpan<Prototype> ChildPrototypes => childPrototypes;
+    public ReadOnlySpan<int> LineInfo => lineInfo;
+    public ReadOnlySpan<LocalVariable> LocalVariables => localVariables;
+    public ReadOnlySpan<UpValueDesc> UpValues => upValues;
+
+    //public LuaClosure Cache;
+    public readonly string Source = source;
+    public readonly int LineDefined = lineDefined, LastLineDefined = lastLineDefined;
+    public readonly int ParameterCount = parameterCount,  MaxStackSize = maxStackSize;
+    public readonly bool HasVariableArguments = hasVariableArguments;
+}

+ 38 - 9
src/Lua/Runtime/Tracebacks.cs

@@ -11,9 +11,39 @@ public class Traceback(LuaState state)
     public required LuaClosure RootFunc { get; init; }
     public required LuaClosure RootFunc { get; init; }
     public required CallStackFrame[] StackFrames { get; init; }
     public required CallStackFrame[] StackFrames { get; init; }
 
 
-    internal string RootChunkName => RootFunc.Proto.Name;
+    internal string RootChunkName => RootFunc.Proto.Source;
+    
+    internal void WriteLastLuaTrace(ref PooledList<char> list )
+    {
+        var intFormatBuffer = (stackalloc char[15]);
+        var shortSourceBuffer = (stackalloc char[59]);
+        var stackFrames = StackFrames.AsSpan();
+        for (var index = stackFrames.Length - 1; index >= 0; index--)
+        {
+            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
+            var frame = stackFrames[index];
+            if (!frame.IsTailCall && lastFunc is LuaClosure closure)
+            {
+                var p = closure.Proto;
+                var len = LuaDebug.WriteShortSource(p.Source, shortSourceBuffer);
+                list.AddRange(shortSourceBuffer[..len]);
+                list.AddRange(":");
+                if (p.LineInfo.Length <= frame.CallerInstructionIndex)
+                {
+                    list.AddRange("Trace back error");
+                }
+                else
+                {
+                    p.LineInfo[frame.CallerInstructionIndex].TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture);
+                    list.AddRange(intFormatBuffer[..charsWritten]);
+                }
+                return;
+            }
+        }
+        
+    }
 
 
-    internal SourcePosition LastPosition
+    public int LastLine
     {
     {
         get
         get
         {
         {
@@ -25,13 +55,13 @@ public class Traceback(LuaState state)
                 if (!frame.IsTailCall && lastFunc is LuaClosure 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)
+                    if (frame.CallerInstructionIndex < 0 || p.LineInfo.Length <= frame.CallerInstructionIndex)
                     {
                     {
                         Console.WriteLine($"Trace back error");
                         Console.WriteLine($"Trace back error");
                         return default;
                         return default;
                     }
                     }
 
 
-                    return p.SourcePositions[frame.CallerInstructionIndex];
+                    return p.LineInfo[frame.CallerInstructionIndex];
                 }
                 }
             }
             }
 
 
@@ -94,24 +124,23 @@ public class Traceback(LuaState state)
                 }
                 }
 
 
                 var p = closure.Proto;
                 var p = closure.Proto;
-                var root = p.GetRoot();
                 list.AddRange("\t");
                 list.AddRange("\t");
-                var len = LuaDebug.WriteShortSource(root.Name, shortSourceBuffer);
+                var len = LuaDebug.WriteShortSource(p.Source, shortSourceBuffer);
                 list.AddRange(shortSourceBuffer[..len]);
                 list.AddRange(shortSourceBuffer[..len]);
                 list.AddRange(":");
                 list.AddRange(":");
-                if (p.SourcePositions.Length <= frame.CallerInstructionIndex)
+                if (p.LineInfo.Length <= frame.CallerInstructionIndex)
                 {
                 {
                     list.AddRange("Trace back error");
                     list.AddRange("Trace back error");
                 }
                 }
                 else
                 else
                 {
                 {
-                    p.SourcePositions[frame.CallerInstructionIndex].Line.TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture);
+                    p.LineInfo[frame.CallerInstructionIndex].TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture);
                     list.AddRange(intFormatBuffer[..charsWritten]);
                     list.AddRange(intFormatBuffer[..charsWritten]);
                 }
                 }
 
 
 
 
                 list.AddRange(": in ");
                 list.AddRange(": in ");
-                if (root == p)
+                if (p.LineDefined == 0)
                 {
                 {
                     list.AddRange("main chunk");
                     list.AddRange("main chunk");
                     list.AddRange("\n");
                     list.AddRange("\n");

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

@@ -1,5 +1,4 @@
 using System.Globalization;
 using System.Globalization;
-using Lua.CodeAnalysis.Compilation;
 using Lua.Internal;
 using Lua.Internal;
 using Lua.Runtime;
 using Lua.Runtime;
 
 
@@ -89,11 +88,10 @@ public sealed class BasicLibrary
         var arg0 = context.GetArgument<string>(0);
         var arg0 = context.GetArgument<string>(0);
 
 
         // 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 bytes = await File.ReadAllBytesAsync(arg0, cancellationToken);
         var fileName = "@" + Path.GetFileName(arg0);
         var fileName = "@" + Path.GetFileName(arg0);
-        var chunk = LuaCompiler.Default.Compile(text, fileName);
 
 
-        return await new LuaClosure(context.State, chunk).InvokeAsync(context, cancellationToken);
+        return await context.State.Compile(bytes, fileName).InvokeAsync(context, cancellationToken);
     }
     }
 
 
     public ValueTask<int> Error(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> Error(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -103,11 +101,6 @@ public sealed class BasicLibrary
             : context.Arguments[0];
             : context.Arguments[0];
 
 
         var traceback = context.State.GetTraceback();
         var traceback = context.State.GetTraceback();
-        if (value.TryReadString(out var str))
-        {
-            value = $"{traceback.RootChunkName}:{traceback.LastPosition.Line}: {str}";
-        }
-
         throw new LuaRuntimeException(traceback, value);
         throw new LuaRuntimeException(traceback, value);
     }
     }
 
 
@@ -160,6 +153,9 @@ public sealed class BasicLibrary
     {
     {
         // Lua-CSharp does not support binary chunks, the mode argument is ignored.
         // Lua-CSharp does not support binary chunks, the mode argument is ignored.
         var arg0 = context.GetArgument<string>(0);
         var arg0 = context.GetArgument<string>(0);
+        var mode  = context.HasArgument(1)
+            ? context.GetArgument<string>(1)
+            : "bt";
         var arg2 = context.HasArgument(2)
         var arg2 = context.HasArgument(2)
             ? context.GetArgument<LuaTable>(2)
             ? context.GetArgument<LuaTable>(2)
             : null;
             : null;
@@ -167,10 +163,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
         try
         try
         {
         {
-            var text = await File.ReadAllTextAsync(arg0, cancellationToken);
+            var bytes = await File.ReadAllBytesAsync(arg0, cancellationToken);
             var fileName = "@" + Path.GetFileName(arg0);
             var fileName = "@" + Path.GetFileName(arg0);
-            var chunk = LuaCompiler.Default.Compile(text, fileName);
-            return context.Return(new LuaClosure(context.State, chunk, arg2));
+            return context.Return(context.State.Compile(bytes, fileName,mode,arg2));
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
@@ -183,21 +178,24 @@ public sealed class BasicLibrary
         // Lua-CSharp does not support binary chunks, the mode argument is ignored.
         // Lua-CSharp does not support binary chunks, the mode argument is ignored.
         var arg0 = context.GetArgument(0);
         var arg0 = context.GetArgument(0);
 
 
-        var arg1 = context.HasArgument(1)
+        var name = context.HasArgument(1)
             ? context.GetArgument<string>(1)
             ? context.GetArgument<string>(1)
             : null;
             : null;
+        
+        var mode = context.HasArgument(2)
+            ? context.GetArgument<string>(2)
+            : "bt";
 
 
         var arg3 = context.HasArgument(3)
         var arg3 = context.HasArgument(3)
             ? context.GetArgument<LuaTable>(3)
             ? context.GetArgument<LuaTable>(3)
-            : null;
+            : context.State.Environment;
 
 
         // do not use LuaState.DoFileAsync as it uses the newExecutionContext
         // do not use LuaState.DoFileAsync as it uses the newExecutionContext
         try
         try
         {
         {
             if (arg0.TryRead<string>(out var str))
             if (arg0.TryRead<string>(out var str))
             {
             {
-                var chunk = LuaCompiler.Default.Compile(str, arg1 ?? str);
-                return new(context.Return(new LuaClosure(context.State, chunk, arg3)));
+                return new(context.Return(context.State.Compile(str, name ?? str, arg3)));
             }
             }
             else if (arg0.TryRead<LuaFunction>(out var function))
             else if (arg0.TryRead<LuaFunction>(out var function))
             {
             {
@@ -261,9 +259,9 @@ public sealed class BasicLibrary
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
-            if (ex is LuaRuntimeException { ErrorObject: not null } luaEx)
+            if (ex is LuaRuntimeException  luaEx)
             {
             {
-                return context.Return(false, luaEx.ErrorObject.Value);
+                return context.Return(false, luaEx.ErrorObject);
             }
             }
             else
             else
             {
             {
@@ -562,7 +560,7 @@ public sealed class BasicLibrary
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
-            var error = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message;
+            var error = ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message;
 
 
             context.State.Push(error);
             context.State.Push(error);
 
 

+ 14 - 14
src/Lua/Standard/DebugLibrary.cs

@@ -84,28 +84,28 @@ public class DebugLibrary
 
 
         if (frame.Function is LuaClosure closure)
         if (frame.Function is LuaClosure closure)
         {
         {
-            var locals = closure.Proto.Locals;
+            var locals = closure.Proto.LocalVariables;
             var nextFrame = callStack[^level];
             var nextFrame = callStack[^level];
             var currentPc = nextFrame.CallerInstructionIndex;
             var currentPc = nextFrame.CallerInstructionIndex;
             {
             {
-                int nextFrameBase = (closure.Proto.Instructions[currentPc].OpCode is OpCode.Call or OpCode.TailCall) ? nextFrame.Base - 1 : nextFrame.Base;
+                int nextFrameBase = (closure.Proto.Code[currentPc].OpCode is OpCode.Call or OpCode.TailCall) ? nextFrame.Base - 1 : nextFrame.Base;
                 if (nextFrameBase - 1 < frameBase + index)
                 if (nextFrameBase - 1 < frameBase + index)
                 {
                 {
                     name = null;
                     name = null;
                     return ref Unsafe.NullRef<LuaValue>();
                     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)
+            var localId = index+1;
+            foreach (var l in locals)
+            {
+                if(currentPc<l.StartPc)break;
+                if(l.EndPc<=currentPc)continue;
+                localId--;
+                if (localId == 0)
                 {
                 {
-                    break;
+                    name = l.Name;
+                    return ref thread.Stack.Get(frameBase + index);
                 }
                 }
             }
             }
         }
         }
@@ -133,7 +133,7 @@ public class DebugLibrary
                 var paramCount = closure.Proto.ParameterCount;
                 var paramCount = closure.Proto.ParameterCount;
                 if (0 <= index && index < paramCount)
                 if (0 <= index && index < paramCount)
                 {
                 {
-                    return closure.Proto.Locals[index].Name.ToString();
+                    return closure.Proto.LocalVariables[index].Name;
                 }
                 }
             }
             }
 
 
@@ -596,9 +596,9 @@ public class DebugLibrary
             if (functionToInspect is LuaClosure closure)
             if (functionToInspect is LuaClosure closure)
             {
             {
                 var activeLines = new LuaTable(0, 8);
                 var activeLines = new LuaTable(0, 8);
-                foreach (var pos in closure.Proto.SourcePositions)
+                foreach (var line in closure.Proto.LineInfo)
                 {
                 {
-                    activeLines[pos.Line] = true;
+                    activeLines[line] = true;
                 }
                 }
 
 
                 table["activelines"] = activeLines;
                 table["activelines"] = activeLines;

+ 4 - 2
src/Lua/Standard/Internal/ConsoleHelper.cs

@@ -13,6 +13,7 @@ public class ConsoleHelper
         {
         {
             return Console.OpenStandardInput();
             return Console.OpenStandardInput();
         }
         }
+
         _inputStream ??= new MemoryStream();
         _inputStream ??= new MemoryStream();
         _inputReader ??= new StreamReader(_inputStream);
         _inputReader ??= new StreamReader(_inputStream);
         return _inputStream;
         return _inputStream;
@@ -24,14 +25,15 @@ public class ConsoleHelper
         {
         {
             return Console.Read();
             return Console.Read();
         }
         }
+
         return _inputReader?.Read() ?? 0;
         return _inputReader?.Read() ?? 0;
     }
     }
-    
+
     public static Stream OpenStandardOutput()
     public static Stream OpenStandardOutput()
     {
     {
         return Console.OpenStandardOutput();
         return Console.OpenStandardOutput();
     }
     }
-    
+
     public static Stream OpenStandardError()
     public static Stream OpenStandardError()
     {
     {
         return Console.OpenStandardError();
         return Console.OpenStandardError();

+ 3 - 4
src/Lua/Standard/Internal/LuaPlatformUtility.cs

@@ -4,14 +4,14 @@ public class LuaPlatformUtility
 {
 {
     public static bool IsSandBox => SupportStdio;
     public static bool IsSandBox => SupportStdio;
     public static bool SupportStdio => _supportStdioTryLazy.Value;
     public static bool SupportStdio => _supportStdioTryLazy.Value;
-    
+
     private static Lazy<bool> _supportStdioTryLazy = new Lazy<bool>(() =>
     private static Lazy<bool> _supportStdioTryLazy = new Lazy<bool>(() =>
     {
     {
         try
         try
         {
         {
 #if NET6_0_OR_GREATER
 #if NET6_0_OR_GREATER
-            var isDesktop = System.OperatingSystem.IsWindows() || 
-                            System.OperatingSystem.IsLinux() || 
+            var isDesktop = System.OperatingSystem.IsWindows() ||
+                            System.OperatingSystem.IsLinux() ||
                             System.OperatingSystem.IsMacOS();
                             System.OperatingSystem.IsMacOS();
             if (!isDesktop)
             if (!isDesktop)
             {
             {
@@ -27,5 +27,4 @@ public class LuaPlatformUtility
             return false;
             return false;
         }
         }
     });
     });
-
 }
 }

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

@@ -23,8 +23,7 @@ public sealed class ModuleLibrary
         if (!loaded.TryGetValue(arg0, out var loadedTable))
         if (!loaded.TryGetValue(arg0, out var loadedTable))
         {
         {
             var module = await context.State.ModuleLoader.LoadAsync(arg0, cancellationToken);
             var module = await context.State.ModuleLoader.LoadAsync(arg0, cancellationToken);
-            var chunk = LuaCompiler.Default.Compile(module.ReadText(), module.Name);
-            await new LuaClosure(context.State, chunk).InvokeAsync(context, cancellationToken);
+            await context.State.Compile(module.ReadText(), module.Name).InvokeAsync(context, cancellationToken);
 
 
             loadedTable = context.Thread.Stack.Get(context.ReturnFrameBase);
             loadedTable = context.Thread.Stack.Get(context.ReturnFrameBase);
             loaded[arg0] = loadedTable;
             loaded[arg0] = loadedTable;

+ 7 - 19
src/Lua/Standard/TableLibrary.cs

@@ -1,3 +1,4 @@
+using Lua.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
 using Lua.Internal;
 using Lua.Internal;
@@ -26,30 +27,17 @@ public sealed class TableLibrary
     public readonly LuaFunction[] Functions;
     public readonly LuaFunction[] Functions;
 
 
     // TODO: optimize
     // TODO: optimize
-    static readonly Chunk defaultComparer = new()
-    {
-        Name = "comp",
-        Functions = [],
-        Constants = [],
-        Instructions =
+    private static readonly Prototype defaultComparer = new Prototype(
+        "comp", 0,0,2,2,false,
+        [],
         [
         [
             Instruction.Le(1, 0, 1),
             Instruction.Le(1, 0, 1),
             Instruction.LoadBool(2, 1, 1),
             Instruction.LoadBool(2, 1, 1),
             Instruction.LoadBool(2, 0, 0),
             Instruction.LoadBool(2, 0, 0),
             Instruction.Return(2, 2),
             Instruction.Return(2, 2),
-        ],
-        SourcePositions =
-        [
-            default, default, default, default,
-        ],
-        ParameterCount = 2,
-        UpValues = [],
-        Locals = [new LocalValueInfo() { Name = "a".AsMemory(), StartPc = 0, Index = 0, EndPc = 4 }, new LocalValueInfo() { Name = "b".AsMemory(), StartPc = 0, Index = 1, EndPc = 4 }],
-        MaxStackPosition = 2,
-        HasVariableArguments = false,
-        LineDefined = 0,
-        LastLineDefined = 0,
-    };
+        ],[],[0,0,0,0],[new LocalVariable(){Name = "a",StartPc = 0,EndPc = 4}, new LocalVariable(){Name = "b",StartPc = 0,EndPc = 4}],
+        []
+    );
 
 
     public ValueTask<int> Concat(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> Concat(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {

+ 0 - 241
tests/Lua.Tests/LexerTests.cs

@@ -1,241 +0,0 @@
-using Lua.CodeAnalysis.Syntax;
-
-namespace Lua.Tests;
-
-public class LexerTests
-{
-    [Test]
-    [TestCase("0")]
-    [TestCase("123")]
-    [TestCase("1234567890")]
-    public void Test_Numeric_Integer(string x)
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Number(x, new(1, 0))
-        };
-        var actual = GetTokens(x);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("1.2")]
-    [TestCase("123.45")]
-    [TestCase("12345.6789")]
-    public void Test_Numeric_Decimal(string x)
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Number(x, new(1, 0))
-        };
-        var actual = GetTokens(x);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("0x123")]
-    [TestCase("0x456")]
-    [TestCase("0x789")]
-    public void Test_Numeric_Hex(string x)
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Number(x, new(1, 0))
-        };
-        var actual = GetTokens(x);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("12E3")]
-    [TestCase("45E+6")]
-    [TestCase("78E-9")]
-    [TestCase("1e+2")]
-    [TestCase("3e-4")]
-    public void Test_Numeric_Exponential(string x)
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Number(x, new(1, 0))
-        };
-        var actual = GetTokens(x);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("\"\"")]
-    [TestCase("\"hello\"")]
-    [TestCase("\"1.23\"")]
-    [TestCase("\"1-2-3-4-5\"")]
-    [TestCase("\'hello\'")]
-    public void Test_String(string x)
-    {
-        var expected = new[]
-        {
-            SyntaxToken.String(x.AsMemory(1, x.Length - 2), new(1, 0))
-        };
-        var actual = GetTokens(x);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("foo")]
-    [TestCase("bar")]
-    [TestCase("baz")]
-    public void Test_Identifier(string x)
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Identifier(x, new(1, 0))
-        };
-        var actual = GetTokens(x);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("-- hello!")]
-    [TestCase("-- how are you?")]
-    [TestCase("-- goodbye!")]
-    public void Test_Comment_Line(string code)
-    {
-        var expected = Array.Empty<SyntaxToken>();
-        var actual = GetTokens(code);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase(@"--[[
-        hello!
-        how are you?
-        goodbye!
-    ]]--")]
-    [TestCase(@"--[[
-        hello!
-        how are you?
-        goodbye!
-    ]]")]
-    public void Test_Comment_Block(string code)
-    {
-        var expected = Array.Empty<SyntaxToken>();
-        var actual = GetTokens(code);
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    [TestCase("--[[ \n hello")]
-    [TestCase("--[[ \r hello")]
-    [TestCase("--[[ \r\n hello")]
-    public void Test_Comment_Block_Error(string code)
-    {
-        Assert.Throws<LuaParseException>(() => GetTokens(code), "main.lua:(1,5): unfinished long comment (starting at line 0)");
-    }
-
-    [Test]
-    public void Test_Comment_Line_WithCode()
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Number("10.0", new(1, 0))
-        };
-        var actual = GetTokens("10.0 -- this is numeric literal");
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    public void Test_Nil()
-    {
-        var expected = new[]
-        {
-            SyntaxToken.Nil(new(1, 0))
-        };
-        var actual = GetTokens("nil");
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    public void Test_True()
-    {
-        var expected = new[]
-        {
-            SyntaxToken.True(new(1, 0))
-        };
-        var actual = GetTokens("true");
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    public void Test_False()
-    {
-        var expected = new[]
-        {
-            SyntaxToken.False(new(1, 0))
-        };
-        var actual = GetTokens("false");
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    public void Test_If()
-    {
-        var expected = new[]
-        {
-            SyntaxToken.If(new(1, 0)), SyntaxToken.Identifier("x", new(1, 3)), SyntaxToken.Equality(new(1, 5)), SyntaxToken.Number("1.0", new(1, 8)), SyntaxToken.Then(new(1, 12)), SyntaxToken.EndOfLine(new(1, 16)),
-            SyntaxToken.Return(new(2, 4)), SyntaxToken.Nil(new(2, 11)), SyntaxToken.EndOfLine(new(2, 14)),
-            SyntaxToken.End(new(3, 0)),
-        };
-        var actual = GetTokens(
-@"if x == 1.0 then
-    return nil
-end");
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    [Test]
-    public void Test_If_Else()
-    {
-        var expected = new[]
-        {
-            SyntaxToken.If(new(1, 0)), SyntaxToken.Identifier("x", new(1, 3)), SyntaxToken.Equality(new(1, 5)), SyntaxToken.Number("1.0", new(1, 8)), SyntaxToken.Then(new(1, 12)), SyntaxToken.EndOfLine(new(1, 16)),
-            SyntaxToken.Return(new(2, 4)), SyntaxToken.Number("1.0", new(2, 11)), SyntaxToken.EndOfLine(new(2, 14)),
-            SyntaxToken.Else(new(3, 0)), SyntaxToken.EndOfLine(new(3, 4)),
-            SyntaxToken.Return(new(4, 4)), SyntaxToken.Number("0.0", new(4, 11)), SyntaxToken.EndOfLine(new(4, 14)),
-            SyntaxToken.End(new(5, 0)),
-        };
-        var actual = GetTokens(
-@"if x == 1.0 then
-    return 1.0
-else
-    return 0.0
-end");
-
-        CollectionAssert.AreEqual(expected, actual);
-    }
-
-    static SyntaxToken[] GetTokens(string source)
-    {
-        var list = new List<SyntaxToken>();
-        var lexer = new Lexer
-        {
-            Source = source.AsMemory(),
-            ChunkName = "main.lua"
-        };
-        while (lexer.MoveNext())
-        {
-            list.Add(lexer.Current);
-        }
-        return list.ToArray();
-    }
-}

+ 28 - 53
tests/Lua.Tests/LuaTests.cs

@@ -1,4 +1,5 @@
 using Lua.Standard;
 using Lua.Standard;
+using System.Globalization;
 
 
 namespace Lua.Tests;
 namespace Lua.Tests;
 
 
@@ -12,58 +13,32 @@ public class LuaTests
         state = LuaState.Create();
         state = LuaState.Create();
         state.OpenStandardLibraries();
         state.OpenStandardLibraries();
     }
     }
-
-    [Test]
-    public async Task Test_Closure()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/closure.lua"));
-    }
-
-    [Test]
-    public async Task Test_Vararg()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/vararg.lua"));
-    }
-
-    [Test]
-    public async Task Test_NextVar()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/nextvar.lua"));
-    }
-
-    [Test]
-    public async Task Test_Math()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/math.lua"));
-    }
-
-    [Test]
-    public async Task Test_Bitwise()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/bitwise.lua"));
-    }
-
-    [Test]
-    public async Task Test_Strings()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/strings.lua"));
-    }
-
-    [Test]
-    public async Task Test_Coroutine()
-    {
-        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]
-    public async Task Test_VeryBig()
-    {
-        await state.DoFileAsync(FileHelper.GetAbsolutePath("tests-lua/verybig.lua"));
+    
+
+    [Test]
+    [TestCase("tests-lua/calls.lua")]
+    [TestCase("tests-lua/closure.lua")]
+    [TestCase("tests-lua/vararg.lua")]
+    [TestCase("tests-lua/nextvar.lua")]
+    [TestCase("tests-lua/math.lua")]
+    [TestCase("tests-lua/bitwise.lua")]
+    [TestCase("tests-lua/strings.lua")]
+    [TestCase("tests-lua/coroutine.lua")]
+    [TestCase("tests-lua/db.lua")]
+    [TestCase("tests-lua/verybig.lua")]
+    
+    public async Task Test_Lua(string file)
+    {
+        var path = FileHelper.GetAbsolutePath(file);
+        try
+        {
+            await state.DoFileAsync(FileHelper.GetAbsolutePath(file));
+        }
+        catch (LuaRuntimeException e)
+        {
+            var traceback = e.LuaTraceback;
+            var line = traceback.LastLine;
+            throw new Exception($"{path}:line {line}\n{e.InnerException} {e} ");
+        }
     }
     }
 }
 }

+ 1 - 1
tests/Lua.Tests/OperatorTests.cs

@@ -80,7 +80,7 @@ public class OperatorTests
     [TestCase(false, false)]
     [TestCase(false, false)]
     public async Task Test_Or(bool a, bool b)
     public async Task Test_Or(bool a, bool b)
     {
     {
-        string strA = a.ToString().ToLower(), strB =  b.ToString().ToLower();
+        string strA = a.ToString().ToLower(), strB = b.ToString().ToLower();
         var result = await LuaState.Create().DoStringAsync($"return {strA} or {strB}");
         var result = await LuaState.Create().DoStringAsync($"return {strA} or {strB}");
         Assert.That(result, Has.Length.EqualTo(1));
         Assert.That(result, Has.Length.EqualTo(1));
         Assert.That(result[0], Is.EqualTo(new LuaValue(a || b)));
         Assert.That(result[0], Is.EqualTo(new LuaValue(a || b)));

+ 0 - 29
tests/Lua.Tests/ParserTests.cs

@@ -1,29 +0,0 @@
-using Lua.CodeAnalysis.Syntax;
-using Lua.CodeAnalysis.Syntax.Nodes;
-
-namespace Lua.Tests
-{
-    // TODO: add more tests
-
-    public class ParserTests
-    {
-        [Test]
-        public void Test_If_ElseIf_Else_Empty()
-        {
-            var source =
-@"if true then
-elseif true then
-else
-end";
-            var actual = LuaSyntaxTree.Parse(source).Nodes[0];
-            var expected = new IfStatementNode(
-                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));
-
-            Assert.That(actual, Is.TypeOf<IfStatementNode>());
-            Assert.That(actual.ToString(), Is.EqualTo(expected.ToString()));
-        }
-    }
-}

Some files were not shown because too many files changed in this diff