Browse Source

Merge pull request #176 from nuskey8/v0.5-dev

version 0.5
Yusuke Nakada 3 weeks ago
parent
commit
7309f6f009
100 changed files with 6870 additions and 3194 deletions
  1. 9 0
      .claude/settings.local.json
  2. 14 14
      .editorconfig
  3. 126 0
      CLAUDE.md
  4. 0 59
      Lua.sln
  5. 16 0
      Lua.slnx
  6. 1 1
      README.md
  7. 4 3
      sandbox/Benchmark/AddBenchmark.cs
  8. 14 14
      sandbox/Benchmark/Benchmark.csproj
  9. 1 1
      sandbox/Benchmark/BenchmarkCore.cs
  10. 40 0
      sandbox/Benchmark/HookedBenchmark.cs
  11. 7 53
      sandbox/Benchmark/InterpreterSteps.cs
  12. 1 1
      sandbox/Benchmark/NBodyBenchmark.cs
  13. 119 0
      sandbox/Benchmark/hooked.lua
  14. 16 16
      sandbox/ConsoleApp1/ConsoleApp1.csproj
  15. 25 10
      sandbox/ConsoleApp1/LVec3.cs
  16. 90 31
      sandbox/ConsoleApp1/Program.cs
  17. 2 114
      sandbox/ConsoleApp1/test.lua
  18. 479 0
      sandbox/ConsoleApp2/.gitignore
  19. 15 0
      sandbox/ConsoleApp2/ConsoleApp2.csproj
  20. 69 0
      sandbox/ConsoleApp2/Program.cs
  21. 480 0
      sandbox/JitTest/.gitignore
  22. 21 0
      sandbox/JitTest/JitTest.csproj
  23. 55 0
      sandbox/JitTest/Program.cs
  24. 630 0
      sandbox/JitTest/db.lua
  25. 388 0
      sandbox/JitTest/events.lua
  26. 118 0
      sandbox/JitTest/test.lua
  27. 1 1
      src/Lua.SourceGenerator/Comparer.cs
  28. 18 18
      src/Lua.SourceGenerator/Lua.SourceGenerator.csproj
  29. 166 51
      src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs
  30. 3 3
      src/Lua.SourceGenerator/LuaObjectMetamethod.cs
  31. 2 2
      src/Lua.SourceGenerator/MethodMetadata.cs
  32. 26 7
      src/Lua.SourceGenerator/SymbolReferences.cs
  33. 24 6
      src/Lua.SourceGenerator/TypeMetadata.cs
  34. 28 6
      src/Lua.SourceGenerator/Utilities/CodeBuilder.cs
  35. 3 5
      src/Lua.SourceGenerator/Utilities/RoslynAnalyzerExtensions.cs
  36. 1 1
      src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs
  37. 10 9
      src/Lua.Unity/Assets/AddressableAssetsData/AddressableAssetSettings.asset
  38. 5 5
      src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset
  39. 1 1
      src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset.meta
  40. 1 1
      src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset
  41. 1 1
      src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset
  42. 1 1
      src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset
  43. 1 1
      src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset.meta
  44. 98 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs
  45. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs.meta
  46. 32 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs
  47. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs.meta
  48. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/Resources.meta
  49. 0 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png
  50. 0 0
      src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png.meta
  51. 4 3
      src/Lua.Unity/Assets/Lua.Unity/Runtime/AddressablesModuleLoader.cs
  52. 15 1
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAsset.cs
  53. 10 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs
  54. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs.meta
  55. 30 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs
  56. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs.meta
  57. 15 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs
  58. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs.meta
  59. 7 8
      src/Lua.Unity/Assets/Lua.Unity/Runtime/ResourcesModuleLoader.cs
  60. 48 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs
  61. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs.meta
  62. 77 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs
  63. 3 0
      src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs.meta
  64. 1 0
      src/Lua.Unity/Assets/Sandbox/Resources/foo.lua
  65. BIN
      src/Lua.Unity/Assets/Sandbox/Resources/test.luac
  66. 1 1
      src/Lua.Unity/Assets/Sandbox/SampleScene.unity
  67. 47 3
      src/Lua.Unity/Assets/Sandbox/Sandbox.cs
  68. 1 0
      src/Lua.Unity/Assets/Sandbox/bar.lua
  69. 1 0
      src/Lua.Unity/Assets/StreamingAssets/streaming.lua
  70. 3 0
      src/Lua.Unity/Assets/StreamingAssets/streaming.lua.meta
  71. 1 0
      src/Lua.Unity/Packages/manifest.json
  72. 0 8
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1.meta
  73. BIN
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/.signature.p7s
  74. BIN
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png
  75. 0 169
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png.meta
  76. 0 24
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec
  77. 0 7
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec.meta
  78. 0 519
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md
  79. 0 7
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md.meta
  80. 0 8
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers.meta
  81. 0 8
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet.meta
  82. 0 8
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs.meta
  83. BIN
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll
  84. 0 51
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll.meta
  85. 0 8
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib.meta
  86. 0 8
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1.meta
  87. BIN
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll
  88. 0 29
      src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll.meta
  89. 3 2
      src/Lua.Unity/Packages/nuget-packages/packages.config
  90. 6 0
      src/Lua.Unity/Packages/packages-lock.json
  91. 1 1
      src/Lua.Unity/ProjectSettings/EditorBuildSettings.asset
  92. 52 0
      src/Lua/CodeAnalysis/Compilation/BomUtility.cs
  93. 131 0
      src/Lua/CodeAnalysis/Compilation/Declarements.cs
  94. 0 36
      src/Lua/CodeAnalysis/Compilation/Descriptions.cs
  95. 525 0
      src/Lua/CodeAnalysis/Compilation/Dump.cs
  96. 1633 0
      src/Lua/CodeAnalysis/Compilation/Function.cs
  97. 0 499
      src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs
  98. 0 1350
      src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs
  99. 988 0
      src/Lua/CodeAnalysis/Compilation/Parser.cs
  100. 84 0
      src/Lua/CodeAnalysis/Compilation/PrototypeBuilder.cs

+ 9 - 0
.claude/settings.local.json

@@ -0,0 +1,9 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(dotnet build:*)",
+      "Bash(dotnet run:*)"
+    ],
+    "deny": []
+  }
+}

+ 14 - 14
.editorconfig

@@ -39,7 +39,7 @@ dotnet_style_parentheses_in_other_operators = never_if_unnecessary
 dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
 
 # Modifier preferences
-dotnet_style_require_accessibility_modifiers = for_non_interface_members
+dotnet_style_require_accessibility_modifiers = omit_if_default
 
 # Expression-level preferences
 dotnet_style_coalesce_expression = true
@@ -77,9 +77,9 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true
 #### C# Coding Conventions ####
 
 # var preferences
-csharp_style_var_elsewhere = false
-csharp_style_var_for_built_in_types = false
-csharp_style_var_when_type_is_apparent = false
+csharp_style_var_elsewhere = true
+csharp_style_var_for_built_in_types = true
+csharp_style_var_when_type_is_apparent = true
 
 # Expression-bodied members
 csharp_style_expression_bodied_accessors = true
@@ -104,14 +104,14 @@ csharp_style_conditional_delegate_call = true
 
 # Modifier preferences
 csharp_prefer_static_local_function = true
-csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async
 csharp_style_prefer_readonly_struct = true
 csharp_style_prefer_readonly_struct_member = true
 
 # Code-block preferences
 csharp_prefer_braces = true
 csharp_prefer_simple_using_statement = true
-csharp_style_namespace_declarations = block_scoped
+csharp_style_namespace_declarations = file_scoped
 csharp_style_prefer_method_group_conversion = true
 csharp_style_prefer_primary_constructors = true
 csharp_style_prefer_top_level_statements = true
@@ -208,26 +208,26 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
 
 dotnet_naming_symbols.interface.applicable_kinds = interface
 dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.interface.required_modifiers = 
+dotnet_naming_symbols.interface.required_modifiers =
 
 dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
 dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.types.required_modifiers = 
+dotnet_naming_symbols.types.required_modifiers =
 
 dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
 dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.non_field_members.required_modifiers = 
+dotnet_naming_symbols.non_field_members.required_modifiers =
 
 # Naming styles
 
-dotnet_naming_style.pascal_case.required_prefix = 
-dotnet_naming_style.pascal_case.required_suffix = 
-dotnet_naming_style.pascal_case.word_separator = 
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
 dotnet_naming_style.pascal_case.capitalization = pascal_case
 
 dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.required_suffix = 
-dotnet_naming_style.begins_with_i.word_separator = 
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
 dotnet_naming_style.begins_with_i.capitalization = pascal_case
 
 

+ 126 - 0
CLAUDE.md

@@ -0,0 +1,126 @@
+
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Lua-CSharp is a high-performance Lua interpreter implemented in C# for .NET and Unity. It provides a Lua 5.2 interpreter with async/await integration, Source Generator support for easy C#-Lua interop, and Unity support.
+
+## Common Development Commands
+
+### Building
+```bash
+# Build entire solution
+dotnet build
+
+# Build in Release mode
+dotnet build -c Release
+
+# Build specific project
+dotnet build src/Lua/Lua.csproj
+```
+### Running
+
+Write lua to `sandbox/ConsoleApp1/test.lua`, then:
+```bash
+# Run simple tests
+dotnet run --project sandbox/ConsoleApp1/ConsoleApp1.csproj
+
+# For pattern matching testing, you can run specific Lua scripts like:
+# echo 'print(string.gsub("hello", "()l", function(pos) return "[" .. pos .. "]" end))' > sandbox/ConsoleApp1/test.lua
+# echo 'print(string.gsub("abc", "", "."))' > sandbox/ConsoleApp1/test.lua  
+# echo 'print(string.gsub("(hello) and (world)", "%b()", function(s) return s:upper() end))' > sandbox/ConsoleApp1/test.lua
+```
+
+
+### Testing
+```bash
+# Run all tests
+dotnet test
+
+# Run tests with detailed output
+dotnet test --logger "console;verbosity=detailed"
+
+# Run specific test project
+dotnet test tests/Lua.Tests/Lua.Tests.csproj
+```
+
+### Benchmarking
+```bash
+# Run performance benchmarks
+dotnet run -c Release --project sandbox/Benchmark/Benchmark.csproj
+```
+
+### Packaging
+```bash
+# Create NuGet package
+dotnet pack -c Release
+```
+
+## Architecture Overview
+
+### Core Components
+
+1. **Lua Runtime (`src/Lua/`)**
+   - `LuaState.cs`: Main entry point for Lua execution
+   - `LuaValue.cs`: Represents values in Lua (nil, boolean, number, string, table, function, userdata, thread)
+   - `Runtime/LuaVirtualMachine.cs`: Core VM implementation that executes Lua bytecode
+   - `Runtime/OpCode.cs`, `Runtime/Instruction.cs`: VM instruction definitions
+   - `CodeAnalysis/`: Lexer, parser, and compiler that converts Lua source to bytecode
+
+2. **Source Generator (`src/Lua.SourceGenerator/`)**
+   - Generates code for classes marked with `[LuaObject]` attribute
+   - Enables seamless C#-Lua interop by auto-generating wrapper code
+   - Key file: `LuaObjectGenerator.cs`
+
+3. **Standard Libraries (`src/Lua/Standard/`)**
+   - Implementations of Lua standard libraries (math, string, table, io, etc.)
+   - Entry point: `OpenLibsExtensions.cs`
+
+### Key Design Patterns
+
+1. **Async/Await Integration**
+   - All Lua execution methods are async (`DoStringAsync`, `DoFileAsync`)
+   - LuaFunction can wrap async C# methods
+   - Enables non-blocking execution of Lua scripts
+
+2. **Value Representation**
+   - `LuaValue` is a discriminated union struct
+   - Implicit conversions between C# and Lua types
+   - Zero-allocation for primitive types
+
+3**Memory Management**
+   - Heavy use of object pooling (`Pool.cs`, `PooledArray.cs`, `PooledList.cs`)
+   - Stack-based value types where possible
+   - Careful management of closures and upvalues
+
+### Unity Integration (`src/Lua.Unity/`)
+
+- Custom asset importer for `.lua` files
+- Integration with Unity's Resources and Addressables systems
+- Works with both Mono and IL2CPP
+
+### Testing Structure
+
+- Unit tests in `tests/Lua.Tests/`
+- Lua test suite from official Lua 5.2 in `tests/Lua.Tests/tests-lua/`
+- Benchmarks comparing with MoonSharp and NLua in `sandbox/Benchmark/`
+
+## Important Notes
+
+- The project targets .NET Standard 2.1, .NET 6.0, and .NET 8.0
+- Uses C# 13 language features
+- Heavy use of unsafe code for performance
+- Strings are UTF-16 (differs from standard Lua)
+
+## TODO
+
+- **ILuaStream Interface Changes**: The ILuaStream interface has been updated with new methods:
+  - Added `IsOpen` property to track stream state
+  - Added `ReadNumberAsync()` for reading numeric values (supports formats like "6.0", "-3.23", "15e12", hex numbers)
+  - Changed `ReadLineAsync()` to accept a `keepEol` parameter for controlling line ending behavior
+  - Renamed `ReadStringAsync()` to `ReadAsync()`
+  - Added `CloseAsync()` method for async stream closing
+  - ✅ Implemented `ReadNumberAsync()` in all implementations
+  - Need to properly implement the `keepEol` parameter in `ReadLineAsync()` for TextLuaStream

+ 0 - 59
Lua.sln

@@ -1,59 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18A64E25-9557-457B-80AE-A6EFE853118D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lua", "src\Lua\Lua.csproj", "{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{79458370-DD8A-48A4-B11E-8DF631520E8C}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lua.Tests", "tests\Lua.Tests\Lua.Tests.csproj", "{7572B7BC-FC73-42F0-B4F7-DA291B4EDB36}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{33883F28-679F-48AD-8E64-3515C7BDAF5A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "sandbox\ConsoleApp1\ConsoleApp1.csproj", "{718A361C-AAF3-45A4-84D4-8C4FB6BB374E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "sandbox\Benchmark\Benchmark.csproj", "{FC157C29-8AAE-49C8-9536-208E3F0698DA}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lua.SourceGenerator", "src\Lua.SourceGenerator\Lua.SourceGenerator.csproj", "{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D}.Release|Any CPU.Build.0 = Release|Any CPU
-		{7572B7BC-FC73-42F0-B4F7-DA291B4EDB36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{7572B7BC-FC73-42F0-B4F7-DA291B4EDB36}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{7572B7BC-FC73-42F0-B4F7-DA291B4EDB36}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{7572B7BC-FC73-42F0-B4F7-DA291B4EDB36}.Release|Any CPU.Build.0 = Release|Any CPU
-		{718A361C-AAF3-45A4-84D4-8C4FB6BB374E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{718A361C-AAF3-45A4-84D4-8C4FB6BB374E}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{718A361C-AAF3-45A4-84D4-8C4FB6BB374E}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{718A361C-AAF3-45A4-84D4-8C4FB6BB374E}.Release|Any CPU.Build.0 = Release|Any CPU
-		{FC157C29-8AAE-49C8-9536-208E3F0698DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{FC157C29-8AAE-49C8-9536-208E3F0698DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{FC157C29-8AAE-49C8-9536-208E3F0698DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{FC157C29-8AAE-49C8-9536-208E3F0698DA}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(NestedProjects) = preSolution
-		{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D} = {18A64E25-9557-457B-80AE-A6EFE853118D}
-		{7572B7BC-FC73-42F0-B4F7-DA291B4EDB36} = {79458370-DD8A-48A4-B11E-8DF631520E8C}
-		{718A361C-AAF3-45A4-84D4-8C4FB6BB374E} = {33883F28-679F-48AD-8E64-3515C7BDAF5A}
-		{FC157C29-8AAE-49C8-9536-208E3F0698DA} = {33883F28-679F-48AD-8E64-3515C7BDAF5A}
-		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4} = {18A64E25-9557-457B-80AE-A6EFE853118D}
-	EndGlobalSection
-EndGlobal

+ 16 - 0
Lua.slnx

@@ -0,0 +1,16 @@
+<Solution>
+    <Folder Name="/sandbox/">
+        <File Path="sandbox/ConsoleApp2/.gitignore"/>
+        <Project Path="sandbox/Benchmark/Benchmark.csproj"/>
+        <Project Path="sandbox/ConsoleApp1/ConsoleApp1.csproj"/>
+        <Project Path="sandbox/ConsoleApp2/ConsoleApp2.csproj"/>
+        <Project Path="sandbox/JitTest/JitTest.csproj"/>
+    </Folder>
+    <Folder Name="/src/">
+        <Project Path="src/Lua.SourceGenerator/Lua.SourceGenerator.csproj"/>
+        <Project Path="src/Lua/Lua.csproj"/>
+    </Folder>
+    <Folder Name="/tests/">
+        <Project Path="tests/Lua.Tests/Lua.Tests.csproj"/>
+    </Folder>
+</Solution>

+ 1 - 1
README.md

@@ -552,4 +552,4 @@ While `collectgarbage()` is available, it simply calls the corresponding .NET ga
 
 ## License
 
-Lua-CSharp is licensed under the [MIT License](LICENSE).
+Lua-CSharp is licensed under the [MIT License](LICENSE).

+ 4 - 3
sandbox/Benchmark/AddBenchmark.cs

@@ -22,10 +22,11 @@ public class AddBenchmark
         core.Setup("add.lua");
         core.LuaCSharpState.OpenStandardLibraries();
 
-        core.LuaCSharpState.Environment["add"] = new LuaFunction("add", (context, buffer, ct) =>
+        core.LuaCSharpState.Environment["add"] = new LuaFunction("add", (context, ct) =>
         {
-            buffer.Span[0] = context.GetArgument<double>(0) + context.GetArgument<double>(1);
-            return new(1);
+            var a = context.GetArgument<double>(0);
+            var b = context.GetArgument<double>(1);
+            return new(context.Return(a + b));
         });
         core.MoonSharpState.Globals["add"] = (Func<double, double, double>)Add;
         core.NLuaState.RegisterFunction("add", typeof(AddBenchmark).GetMethod(nameof(Add), BindingFlags.Static | BindingFlags.Public));

+ 14 - 14
sandbox/Benchmark/Benchmark.csproj

@@ -1,20 +1,20 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>net8.0</TargetFramework>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <Nullable>enable</Nullable>
-  </PropertyGroup>
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
 
-  <ItemGroup>
-    <PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
-    <PackageReference Include="MoonSharp" Version="2.0.0" />
-    <PackageReference Include="NLua" Version="1.7.3" />
-  </ItemGroup>
+    <ItemGroup>
+        <PackageReference Include="BenchmarkDotNet" Version="0.14.0"/>
+        <PackageReference Include="MoonSharp" Version="2.0.0"/>
+        <PackageReference Include="NLua" Version="1.7.3"/>
+    </ItemGroup>
 
-  <ItemGroup>
-    <ProjectReference Include="..\..\src\Lua\Lua.csproj" />
-  </ItemGroup>
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\Lua\Lua.csproj"/>
+    </ItemGroup>
 
 </Project>

+ 1 - 1
sandbox/Benchmark/BenchmarkCore.cs

@@ -18,7 +18,7 @@ public class BenchmarkCore : IDisposable
     public void Setup(string fileName)
     {
         // moonsharp
-        moonSharpState = new Script();
+        moonSharpState = new();
         Script.WarmUp();
 
         // NLua

+ 40 - 0
sandbox/Benchmark/HookedBenchmark.cs

@@ -0,0 +1,40 @@
+using BenchmarkDotNet.Attributes;
+using Lua;
+using Lua.Standard;
+
+[Config(typeof(BenchmarkConfig))]
+public class HookedBenchmark
+{
+    BenchmarkCore core = default!;
+    LuaValue[] buffer = new LuaValue[1];
+
+    [IterationSetup]
+    public void Setup()
+    {
+        core = new();
+        core.Setup("hooked.lua");
+        core.LuaCSharpState.OpenStandardLibraries();
+    }
+
+    [IterationCleanup]
+    public void Cleanup()
+    {
+        core.Dispose();
+        core = default!;
+        GC.Collect();
+    }
+
+
+    [Benchmark(Description = "NLua (DoString)", Baseline = true)]
+    public object[] Benchmark_NLua_String()
+    {
+        return core.NLuaState.DoString(core.SourceText);
+    }
+
+    [Benchmark(Description = "Lua-CSharp (DoString)")]
+    public async Task<LuaValue> Benchmark_LuaCSharp_String()
+    {
+        await core.LuaCSharpState.DoStringAsync(core.SourceText, buffer);
+        return buffer[0];
+    }
+}

+ 7 - 53
sandbox/Benchmark/InterpreterSteps.cs

@@ -1,7 +1,5 @@
 using BenchmarkDotNet.Attributes;
 using Lua;
-using Lua.CodeAnalysis.Compilation;
-using Lua.CodeAnalysis.Syntax;
 using Lua.Runtime;
 using Lua.Standard;
 
@@ -10,38 +8,16 @@ public class InterpreterSteps
 {
     string sourceText = default!;
     LuaState state = default!;
-    SyntaxToken[] tokens = [];
-    LuaSyntaxTree ast = default!;
-    Chunk chunk = default!;
-    LuaValue[] results = new LuaValue[1];
+    LuaClosure closure = default!;
 
     [GlobalSetup]
     public void GlobalSetup()
     {
         var filePath = FileHelper.GetAbsolutePath("n-body.lua");
         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.Load(sourceText, sourceText);
     }
 
     [IterationSetup]
@@ -60,38 +36,16 @@ public class InterpreterSteps
         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]
-    public Chunk Compile()
+    public LuaClosure Compile()
     {
-        return LuaCompiler.Default.Compile(ast);
+        return state.Load(sourceText, sourceText);
     }
 
     [Benchmark]
     public async ValueTask RunAsync()
     {
-        await state.RunAsync(chunk, results);
+        await state.CallAsync(closure, []);
     }
 }

+ 1 - 1
sandbox/Benchmark/NBodyBenchmark.cs

@@ -37,7 +37,7 @@ public class NBodyBenchmark
         return core.MoonSharpState.DoFile(core.FilePath);
     }
 
-    [Benchmark(Description = "NLua (DoString)")]
+    [Benchmark(Description = "NLua (DoString)", Baseline = true)]
     public object[] Benchmark_NLua_String()
     {
         return core.NLuaState.DoString(core.SourceText);

+ 119 - 0
sandbox/Benchmark/hooked.lua

@@ -0,0 +1,119 @@
+debug.sethook(function ()
+end,"",1000000)
+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
+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 10000
+local nbody = #bodies
+
+offsetMomentum(bodies, nbody)
+energy(bodies, nbody)
+for i = 1, N do advance(bodies, nbody, 0.01) end
+energy(bodies, nbody)
+debug.sethook(function ()
+end,"",1000000)

+ 16 - 16
sandbox/ConsoleApp1/ConsoleApp1.csproj

@@ -1,21 +1,21 @@
 <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>
-
-    <EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
-    <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
-  </PropertyGroup>
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net9.0</TargetFramework>
+        <LangVersion>13</LangVersion>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
+        <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
+    </PropertyGroup>
 
 </Project>

+ 25 - 10
sandbox/ConsoleApp1/LVec3.cs

@@ -9,31 +9,46 @@ public partial class LVec3
     [LuaMember("x")]
     public float X
     {
-        get => value.X;
-        set => this.value = this.value with { X = value };
+        get
+        {
+            return value.X;
+        }
+        set
+        {
+            this.value = this.value with { X = value };
+        }
     }
 
     [LuaMember("y")]
     public float Y
     {
-        get => value.Y;
-        set => this.value = this.value with { Y = value };
+        get
+        {
+            return value.Y;
+        }
+        set
+        {
+            this.value = this.value with { Y = value };
+        }
     }
 
     [LuaMember("z")]
     public float Z
     {
-        get => value.Z;
-        set => this.value = this.value with { Z = value };
+        get
+        {
+            return value.Z;
+        }
+        set
+        {
+            this.value = this.value with { Z = value };
+        }
     }
 
     [LuaMember("create")]
     public static LVec3 Create(float x, float y, float z)
     {
-        return new LVec3()
-        {
-            value = new Vector3(x, y, z)
-        };
+        return new() { value = new(x, y, z) };
     }
 
     public override string ToString()

+ 90 - 31
sandbox/ConsoleApp1/Program.cs

@@ -1,49 +1,70 @@
 using System.Runtime.CompilerServices;
-using Lua.CodeAnalysis.Syntax;
-using Lua.CodeAnalysis.Compilation;
+using System.Text;
+using System.Text.RegularExpressions;
 using Lua.Runtime;
 using Lua;
 using Lua.Standard;
 
 var state = LuaState.Create();
 state.OpenStandardLibraries();
-
-state.Environment["vec3"] = new LVec3();
-
+state.Environment["escape"] = new LuaFunction("escape",
+    (c, _) =>
+    {
+        var arg = c.HasArgument(0) ? c.GetArgument<string>(0) : "";
+        return new(c.Return(Regex.Escape(arg)));
+    });
+var source = "";
 try
 {
-    var source = File.ReadAllText(GetAbsolutePath("test.lua"));
-
-    var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua");
-
-    Console.WriteLine("Source Code " + new string('-', 50));
+    source = File.ReadAllText(GetAbsolutePath("test.lua"));
 
-    var debugger = new DisplayStringSyntaxVisitor();
-    Console.WriteLine(debugger.GetDisplayString(syntaxTree));
+    // Console.WriteLine("Source Code " + new string('-', 50));
+    // Console.WriteLine(source);
 
-    var chunk = LuaCompiler.Default.Compile(syntaxTree, "test.lua");
+    var closure = state.Load(source, "@test.lua");
 
-    DebugChunk(chunk, 0);
+    DebugChunk(closure.Proto, 0);
 
     Console.WriteLine("Output " + new string('-', 50));
 
-    var results = new LuaValue[64];
-    var resultCount = await state.RunAsync(chunk, results);
+    // Console.Read();
 
-    Console.WriteLine("Result " + new string('-', 50));
-
-    for (int i = 0; i < resultCount; i++)
+    var timer = new System.Diagnostics.Stopwatch();
+    timer.Start();
+    for (var i = 0; i < 1000; i++)
     {
-        Console.WriteLine(results[i]);
+        var count = await state.RunAsync(closure);
+        state.Pop(count);
+        if (i % 100 == 0)
+        {
+            Console.WriteLine($"Iteration {i} completed. Time elapsed: {timer.ElapsedMilliseconds} ms");
+            Thread.Sleep(100);
+        }
     }
 
+    // Console.WriteLine("Result " + new string('-', 50));
+    // using var results = state.RootAccess.ReadStack(count);
+    // for (var i = 0; i < count; i++)
+    // {
+    //     Console.WriteLine(results[i]);
+    // }
+
     Console.WriteLine("End " + new string('-', 50));
 }
 catch (Exception ex)
 {
+    if (ex is LuaCompileException luaCompileException)
+    {
+        Console.WriteLine("CompileError " + new string('-', 50));
+        Console.WriteLine(RustLikeExceptionHook.OnCatch(source, luaCompileException));
+        Console.WriteLine(new string('-', 55));
+    }
+
     Console.WriteLine(ex);
-    if(ex is LuaRuntimeException { InnerException: not null } luaEx)
+
+    if (ex is LuaRuntimeException { InnerException: not null } luaEx)
     {
+        Console.WriteLine("Inner Exception " + new string('-', 50));
         Console.WriteLine(luaEx.InnerException);
     }
 }
@@ -53,24 +74,24 @@ static string GetAbsolutePath(string relativePath, [CallerFilePath] string calle
     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($"Parameters:{chunk.ParameterCount}");
 
-    Console.WriteLine("Instructions " + new string('-', 50));
+    Console.WriteLine("Code " + new string('-', 50));
     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++;
     }
 
-    Console.WriteLine("Locals " + new string('-', 50));
+    Console.WriteLine("LocalVariables " + new string('-', 50));
     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++;
     }
 
@@ -78,7 +99,7 @@ static void DebugChunk(Chunk chunk, int id)
     index = 0;
     foreach (var constant in chunk.Constants.ToArray())
     {
-        Console.WriteLine($"[{index}]\t{constant}");
+        Console.WriteLine($"[{index}]\t{Regex.Escape(constant.ToString())}");
         index++;
     }
 
@@ -86,16 +107,54 @@ static void DebugChunk(Chunk chunk, int id)
     index = 0;
     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++;
     }
 
     Console.WriteLine();
 
     var nestedChunkId = 0;
-    foreach (var localChunk in chunk.Functions)
+    foreach (var localChunk in chunk.ChildPrototypes)
     {
         DebugChunk(localChunk, nestedChunkId);
         nestedChunkId++;
     }
+}
+
+public class LuaRustLikeException(string message, Exception? innerException) : Exception(message, innerException);
+
+class RustLikeExceptionHook //: ILuaCompileHook
+{
+    public static string OnCatch(ReadOnlySpan<char> source, LuaCompileException exception)
+    {
+        var lineOffset = exception.OffSet - exception.Position.Column + 1;
+        var length = 0;
+        if (lineOffset < 0)
+        {
+            lineOffset = 0;
+        }
+
+        foreach (var c in source[lineOffset..])
+        {
+            if (c is '\n' or '\r')
+            {
+                break;
+            }
+
+            length++;
+        }
+
+        var builder = new StringBuilder();
+        builder.AppendLine();
+        builder.AppendLine("[error]: " + exception.MessageWithNearToken);
+        builder.AppendLine("-->" + exception.ChunkName + ":" + exception.Position.Line + ":" + exception.Position.Column);
+        var line = source.Slice(lineOffset, length).ToString();
+        var lineNumString = exception.Position.Line.ToString();
+        builder.AppendLine(new string(' ', lineNumString.Length) + " |");
+        builder.AppendLine(lineNumString + " | " + line);
+        builder.AppendLine(new string(' ', lineNumString.Length) + " | " +
+                           new string(' ', exception.Position.Column - 1) +
+                           "^ " + exception.MainMessage);
+        return builder.ToString();
+    }
 }

+ 2 - 114
sandbox/ConsoleApp1/test.lua

@@ -1,115 +1,3 @@
-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
-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)
+a ="aaaa[
+"

+ 479 - 0
sandbox/ConsoleApp2/.gitignore

@@ -0,0 +1,479 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp

+ 15 - 0
sandbox/ConsoleApp2/ConsoleApp2.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net9.0</TargetFramework>
+        <LangVersion>13</LangVersion>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\Lua\Lua.csproj"/>
+    </ItemGroup>
+
+</Project>

+ 69 - 0
sandbox/ConsoleApp2/Program.cs

@@ -0,0 +1,69 @@
+using Lua.Runtime;
+using Lua;
+using Lua.Standard;
+
+var state = LuaState.Create();
+state.OpenStandardLibraries();
+{
+    var closure = state.Load("return function (a,b,...)  print('a : '..a..' b :'..'args : ',...) end", "@simple");
+    using var access = state.CreateThread();
+    {
+        var count = await access.RunAsync(closure, 0);
+        var results = access.ReadStack(count);
+        for (var i = 0; i < results.Length; i++)
+        {
+            Console.WriteLine(results[i]);
+        }
+
+        var f = results[0].Read<LuaClosure>();
+        results.Dispose();
+        access.Push("hello", "world", 1, 2, 3);
+        count = await access.RunAsync(f, 5);
+        results = access.ReadStack(count);
+        for (var i = 0; i < results.Length; i++)
+        {
+            Console.WriteLine(results[i]);
+        }
+
+        results.Dispose();
+    }
+}
+
+{
+    var results = await state.DoStringAsync(
+        """
+        return function (...)  
+            local args = {...}
+            for i = 1, #args do
+                local v = args[i]
+                print('In Lua:', coroutine.yield('from lua', i,v))
+            end
+        end
+        """, "coroutine");
+    var f = results[0].Read<LuaClosure>();
+    using var coroutine = state.CreateCoroutine(f);
+    {
+        var stack = new LuaStack();
+        stack.PushRange("a", "b", "c", "d", "e");
+
+        for (var i = 0; coroutine.CanResume; i++)
+        {
+            if (i != 0)
+            {
+                stack.Push("from C# ");
+                stack.Push(i);
+            }
+
+            await coroutine.ResumeAsync(stack);
+            Console.Write("In C#:\t");
+            for (var j = 1; j < stack.Count; j++)
+            {
+                Console.Write(stack[j]);
+                Console.Write('\t');
+            }
+
+            Console.WriteLine();
+            stack.Clear();
+        }
+    }
+}

+ 480 - 0
sandbox/JitTest/.gitignore

@@ -0,0 +1,480 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
+/obj/

+ 21 - 0
sandbox/JitTest/JitTest.csproj

@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net9.0</TargetFramework>
+        <LangVersion>13</LangVersion>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\src\Lua.SourceGenerator\Lua.SourceGenerator.csproj" />
+      <ProjectReference Include="..\..\src\Lua\Lua.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="JitInspect" Version="1.0.3" />
+    </ItemGroup>
+
+</Project>

+ 55 - 0
sandbox/JitTest/Program.cs

@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using JitInspect;
+using Lua;
+using Lua.Runtime;
+using Lua.Standard;
+
+// dotnet run --configuration Release /p:DefineConstants="CASE_MARKER"
+// to activate the CASE_MARKER
+// JitInspect can be run in Windows and Linux (MacOS is not supported yet)
+var luaState = LuaState.Create();
+luaState.OpenStandardLibraries();
+var closure = luaState.Load(File.ReadAllBytes(GetAbsolutePath("test.lua")), "test.lua");
+
+for (var i = 0; i < 1000; i++)
+{
+    await luaState.RunAsync(closure);
+    luaState.Stack.Clear();
+}
+
+var savePath = GetAbsolutePath("history");
+var thisDir = GetThisDirectoryName();
+var newJIitPath = Path.Join(thisDir, $"jit_{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.txt");
+var lastJitPaths = Directory.GetFiles(thisDir).Where(x => x.Contains("jit_"));
+if (!Directory.Exists(savePath))
+{
+    Directory.CreateDirectory(savePath);
+}
+
+if (lastJitPaths.Any())
+{
+    Console.WriteLine("Last:" + File.ReadAllLines(lastJitPaths.First())[^1]);
+    foreach (var jitPath in lastJitPaths)
+    {
+        var last = jitPath;
+        var dest = Path.Join(savePath, Path.GetFileName(jitPath));
+        File.Move(last, dest);
+    }
+}
+
+var method = typeof(LuaVirtualMachine).GetMethod("MoveNext", BindingFlags.Static | BindingFlags.NonPublic)!;
+using var disassembler = JitDisassembler.Create();
+var nextJitText = disassembler.Disassemble(method, new() { PrintInstructionAddresses = true });
+File.WriteAllText(newJIitPath, nextJitText);
+// Console.WriteLine("New:" + nextJitText.Split("\n")[^1]);
+
+static string GetThisDirectoryName([CallerFilePath] string callerFilePath = "")
+{
+    return Path.GetDirectoryName(callerFilePath)!;
+}
+
+static string GetAbsolutePath(string relativePath, [CallerFilePath] string callerFilePath = "")
+{
+    return Path.Join(Path.GetDirectoryName(callerFilePath)!, relativePath);
+}

+ 630 - 0
sandbox/JitTest/db.lua

@@ -0,0 +1,630 @@
+-- testing debug library
+
+debug = require "debug"
+
+local function dostring(s) return assert(load(s))() end
+
+print"testing debug library and debug information"
+
+do
+local a=1
+end
+
+function test (s, l, p)
+  collectgarbage()   -- avoid gc during trace
+  local function f (event, line)
+    assert(event == 'line')
+    local l = table.remove(l, 1)
+    if p then print(l, line) end
+    assert(l == line, "wrong trace!!")
+  end
+  debug.sethook(f,"l"); load(s)(); debug.sethook()
+  assert(#l == 0)
+end
+
+
+do
+  assert(not pcall(debug.getinfo, print, "X"))   -- invalid option
+  assert(debug.getinfo(1000) == nil)   -- out of range level
+  assert(debug.getinfo(-1) == nil)     -- out of range level
+  local a = debug.getinfo(print)
+  assert(a.what == "C#" and a.short_src == "[C#]") -- changed C to C#
+  a = debug.getinfo(print, "L")
+  assert(a.activelines == nil)
+  local b = debug.getinfo(test, "SfL")
+  assert(b.name == nil and b.what == "Lua" and b.linedefined == 13 and
+         b.lastlinedefined == b.linedefined + 10 and
+         b.func == test and not string.find(b.short_src, "%["))
+  assert(b.activelines[b.linedefined + 1] and
+         b.activelines[b.lastlinedefined])
+  assert(not b.activelines[b.linedefined] and
+         not b.activelines[b.lastlinedefined + 1])
+end
+
+
+-- test file and string names truncation
+a = "function f () end"
+local function dostring (s, x) return load(s, x)() end
+dostring(a)
+assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a))
+dostring(a..string.format("; %s\n=1", string.rep('p', 400)))
+assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
+dostring(a..string.format("; %s=1", string.rep('p', 400)))
+assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
+dostring("\n"..a)
+assert(debug.getinfo(f).short_src == '[string "..."]')
+dostring(a, "")
+assert(debug.getinfo(f).short_src == '[string ""]')
+dostring(a, "@xuxu")
+assert(debug.getinfo(f).short_src == "xuxu")
+dostring(a, "@"..string.rep('p', 1000)..'t')
+assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$"))
+dostring(a, "=xuxu")
+assert(debug.getinfo(f).short_src == "xuxu")
+dostring(a, string.format("=%s", string.rep('x', 500)))
+assert(string.find(debug.getinfo(f).short_src, "^x*$"))
+dostring(a, "=")
+assert(debug.getinfo(f).short_src == "")
+a = nil; f = nil;
+
+
+repeat
+  local g = {x = function ()
+    local a = debug.getinfo(2)
+    assert(a.name == 'f' and a.namewhat == 'local')
+    a = debug.getinfo(1)
+    assert(a.name == 'x' and a.namewhat == 'field')
+    return 'xixi'
+  end}
+  local f = function () return 1+1 and (not 1 or g.x()) end
+  assert(f() == 'xixi')
+  g = debug.getinfo(f)
+  assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
+
+  function f (x, name)   -- local!
+    name = name or 'f'
+    local a = debug.getinfo(1)
+    assert(a.name == name and a.namewhat == 'local')
+    return x
+  end
+
+  -- breaks in different conditions
+  if 3>4 then break end; f()
+  if 3<4 then a=1 else break end; f()
+  while 1 do local x=10; break end; f()
+  local b = 1
+  if 3>4 then return math.sin(1) end; f()
+  a = 3<4; f()
+  a = 3<4 or 1; f()
+  repeat local x=20; if 4>3 then f() else break end; f() until 1
+  g = {}
+  f(g).x = f(2) and f(10)+f(9)
+  assert(g.x == f(19))
+  function g(x) if not x then return 3 end return (x('a', 'x')) end
+  assert(g(f) == 'a')
+until 1
+
+test([[if
+math.sin(1)
+then
+  a=1
+else
+  a=2
+end
+]], {2,3,4,7})
+
+test([[--
+if nil then
+  a=1
+else
+  a=2
+end
+]], {2,5,6})
+
+test([[a=1
+repeat
+  a=a+1
+until a==3
+]], {1,3,4,3,4})
+
+test([[ do
+  return
+end
+]], {2})
+
+test([[local a
+a=1
+while a<=3 do
+  a=a+1
+end
+]], {1,2,3,4,3,4,3,4,3,5})
+
+test([[while math.sin(1) do
+  if math.sin(1)
+  then break
+  end
+end
+a=1]], {1,2,3,6})
+
+test([[for i=1,3 do
+  a=i
+end
+]], {1,2,1,2,1,2,1,3})
+
+test([[for i,v in pairs{'a','b'} do
+  a=i..v
+end
+]], {1,2,1,2,1,3})
+
+test([[for i=1,4 do a=1 end]], {1,1,1,1,1})
+
+
+
+print'+'
+
+-- invalid levels in [gs]etlocal
+assert(not pcall(debug.getlocal, 20, 1))
+assert(not pcall(debug.setlocal, -1, 1, 10))
+
+
+-- parameter names
+local function foo (a,b,...) local d, e end
+local co = coroutine.create(foo)
+
+assert(debug.getlocal(foo, 1) == 'a')
+assert(debug.getlocal(foo, 2) == 'b')
+assert(debug.getlocal(foo, 3) == nil)
+assert(debug.getlocal(co, foo, 1) == 'a')
+assert(debug.getlocal(co, foo, 2) == 'b')
+assert(debug.getlocal(co, foo, 3) == nil)
+
+assert(debug.getlocal(print, 1) == nil)
+
+
+-- varargs
+local function foo (a, ...)
+  local t = table.pack(...)
+  for i = 1, t.n do
+    local n, v = debug.getlocal(1, -i)
+    assert(n == "(*vararg)" and v == t[i])
+  end
+  assert(not debug.getlocal(1, -(t.n + 1)))
+  assert(not debug.setlocal(1, -(t.n + 1), 30))
+  if t.n > 0 then
+    (function (x)
+      assert(debug.setlocal(2, -1, x) == "(*vararg)")
+      assert(debug.setlocal(2, -t.n, x) == "(*vararg)")
+     end)(430)
+     assert(... == 430)
+  end
+end
+
+foo()
+foo(print)
+foo(200, 3, 4)
+local a = {}
+for i = 1,1000 do a[i] = i end
+foo(table.unpack(a))
+a = nil
+
+-- access to vararg in non-vararg function
+local function foo () return debug.getlocal(1, -1) end
+assert(foo(10) == nil)
+
+
+a = {}; L = nil
+local glob = 1
+local oldglob = glob
+debug.sethook(function (e,l)
+  collectgarbage()   -- force GC during a hook
+  local f, m, c = debug.gethook()
+  assert(m == 'crl' and c == 0)
+  if e == "line" then
+    if glob ~= oldglob then
+      L = l-1   -- get the first line where "glob" has changed
+      oldglob = glob
+    end
+  elseif e == "call" then
+      local f = debug.getinfo(2, "f").func
+      a[f] = 1
+  else assert(e == "return")
+  end
+end, "crl")
+
+
+function f(a,b)
+  collectgarbage()
+  local _, x = debug.getlocal(1, 1)
+  local _, y = debug.getlocal(1, 2)
+  assert(x == a and y == b)
+  assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
+  assert(debug.setlocal(2, 4, "ma��") == "B")
+  x = debug.getinfo(2)
+  assert(x.func == g and x.what == "Lua" and x.name == 'g' and
+         x.nups == 1 and string.find(x.source, "^@.*db%.lua$"))
+  glob = glob+1
+  assert(debug.getinfo(1, "l").currentline == L+1)
+  assert(debug.getinfo(1, "l").currentline == L+2)
+end
+
+function foo()
+  glob = glob+1
+  assert(debug.getinfo(1, "l").currentline == L+1)
+end; foo()  -- set L
+-- check line counting inside strings and empty lines
+
+_ = 'alo\
+alo' .. [[
+
+]]
+--[[
+]]
+assert(debug.getinfo(1, "l").currentline == L+11)  -- check count of lines
+
+
+function g(...)
+  local arg = {...}
+  do local a,b,c; a=math.sin(40); end
+  local feijao
+  local AAAA,B = "xuxu", "mam�o"
+  f(AAAA,B)
+  assert(AAAA == "pera" and B == "ma��")
+  do
+     local B = 13
+     local x,y = debug.getlocal(1,5)
+     assert(x == 'B' and y == 13)
+  end
+end
+
+g()
+
+
+assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print])
+
+
+-- tests for manipulating non-registered locals (C and Lua temporaries)
+
+local n, v = debug.getlocal(0, 1)
+assert(v == 0 and n == "(*temporary)")
+local n, v = debug.getlocal(0, 2)
+assert(v == 2 and n == "(*temporary)")
+assert(not debug.getlocal(0, 3))
+assert(not debug.getlocal(0, 0))
+
+function f()
+  assert(select(2, debug.getlocal(2,3)) == 1)
+  assert(not debug.getlocal(2,4))
+  debug.setlocal(2, 3, 10)
+  return 20
+end
+
+function g(a,b) return (a+1) + f() end
+
+assert(g(0,0) == 30)
+ 
+
+debug.sethook(nil);
+assert(debug.gethook() == nil)
+
+
+-- testing access to function arguments
+
+X = nil
+a = {}
+function a:f (a, b, ...) local arg = {...}; local c = 13 end
+debug.sethook(function (e)
+  assert(e == "call")
+  dostring("XX = 12")  -- test dostring inside hooks
+  -- testing errors inside hooks
+  assert(not pcall(load("a='joao'+1")))
+  debug.sethook(function (e, l) 
+    assert(debug.getinfo(2, "l").currentline == l)
+    local f,m,c = debug.gethook()
+    assert(e == "line")
+    assert(m == 'l' and c == 0)
+    debug.sethook(nil)  -- hook is called only once
+    assert(not X)       -- check that
+    X = {}; local i = 1
+    local x,y
+    while 1 do
+      x,y = debug.getlocal(2, i)
+      if x==nil then break end
+      X[x] = y
+      i = i+1
+    end
+  end, "l")
+end, "c")
+
+a:f(1,2,3,4,5)
+assert(X.self == a and X.a == 1   and X.b == 2 and X.c == nil)
+assert(XX == 12)
+assert(debug.gethook() == nil)
+
+
+-- testing upvalue access
+local function getupvalues (f)
+  local t = {}
+  local i = 1
+  while true do
+    local name, value = debug.getupvalue(f, i)
+    if not name then break end
+    assert(not t[name])
+    t[name] = value
+    i = i + 1
+  end
+  return t
+end
+
+local a,b,c = 1,2,3
+local function foo1 (a) b = a; return c end
+local function foo2 (x) a = x; return c+b end
+assert(debug.getupvalue(foo1, 3) == nil)
+assert(debug.getupvalue(foo1, 0) == nil)
+assert(debug.setupvalue(foo1, 3, "xuxu") == nil)
+local t = getupvalues(foo1)
+assert(t.a == nil and t.b == 2 and t.c == 3)
+t = getupvalues(foo2)
+assert(t.a == 1 and t.b == 2 and t.c == 3)
+assert(debug.setupvalue(foo1, 1, "xuxu") == "b")
+assert(({debug.getupvalue(foo2, 3)})[2] == "xuxu")
+-- upvalues of C functions are allways "called" "" (the empty string)
+assert(debug.getupvalue(string.gmatch("x", "x"), 1) == "")  
+
+
+-- testing count hooks
+local a=0
+debug.sethook(function (e) a=a+1 end, "", 1)
+a=0; for i=1,1000 do end; assert(1000 < a and a < 1012)
+debug.sethook(function (e) a=a+1 end, "", 4)
+a=0; for i=1,1000 do end; assert(250 < a and a < 255)
+local f,m,c = debug.gethook()
+assert(m == "" and c == 4)
+debug.sethook(function (e) a=a+1 end, "", 4000)
+a=0; for i=1,1000 do end; assert(a == 0)
+
+if not _no32 then
+  debug.sethook(print, "", 2^24 - 1)   -- count upperbound
+  local f,m,c = debug.gethook()
+  assert(({debug.gethook()})[3] == 2^24 - 1)
+end
+
+debug.sethook()
+
+
+-- tests for tail calls
+local function f (x)
+  if x then
+    assert(debug.getinfo(1, "S").what == "Lua")
+    assert(debug.getinfo(1, "t").istailcall == true)
+    local tail = debug.getinfo(2)
+    assert(tail.func == g1 and tail.istailcall == true)
+    assert(debug.getinfo(3, "S").what == "main")
+    print"+"
+    end
+end
+
+function g(x) return f(x) end
+
+function g1(x) g(x) end
+
+local function h (x) local f=g1; return f(x) end
+
+h(true)
+
+local b = {}
+debug.sethook(function (e) table.insert(b, e) end, "cr")
+h(false)
+debug.sethook()
+local res = {"return",   -- first return (from sethook)
+  "call", "tail call", "call", "tail call",
+  "return", "return",
+  "call",    -- last call (to sethook)
+}
+for i = 1, #res do assert(res[i] == table.remove(b, 1)) end
+
+b = 0
+debug.sethook(function (e)
+                if e == "tail call" then
+                  b = b + 1
+                  assert(debug.getinfo(2, "t").istailcall == true)
+                else
+                  assert(debug.getinfo(2, "t").istailcall == false)
+                end
+              end, "c")
+h(false)
+debug.sethook()
+assert(b == 2)   -- two tail calls
+
+lim = 30000
+if _soft then limit = 3000 end
+local function foo (x)
+  if x==0 then
+    assert(debug.getinfo(2).what == "main")
+    local info = debug.getinfo(1)
+    assert(info.istailcall == true and info.func == foo)
+  else return foo(x-1)
+  end
+end
+
+foo(lim)
+
+
+print"+"
+
+
+-- testing local function information
+co = load[[
+  local A = function ()
+    return x
+  end
+  return
+]]
+
+local a = 0
+-- 'A' should be visible to debugger only after its complete definition
+debug.sethook(function (e, l)
+  if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == nil)-- assert(debug.getlocal(2, 1) == "(*temporary)") --changed behavior Lua-CSharp
+   elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
+  end
+end, "l")
+co()  -- run local function definition
+debug.sethook()  -- turn off hook
+assert(a == 2)   -- ensure all two lines where hooked
+
+-- testing traceback
+
+assert(debug.traceback(print) == print)
+assert(debug.traceback(print, 4) == print)
+assert(string.find(debug.traceback("hi", 4), "^hi\n"))
+assert(string.find(debug.traceback("hi"), "^hi\n"))
+assert(not string.find(debug.traceback("hi"), "'traceback'"))
+assert(string.find(debug.traceback("hi", 0), "'traceback'"))
+assert(string.find(debug.traceback(), "^stack traceback:\n"))
+
+
+-- testing nparams, nups e isvararg
+local t = debug.getinfo(print, "u")
+assert(t.isvararg == true and t.nparams == 0 and t.nups == 0)
+
+t = debug.getinfo(function (a,b,c) end, "u")
+assert(t.isvararg == false and t.nparams == 3 and t.nups == 0)
+
+t = debug.getinfo(function (a,b,...) return t[a] end, "u")
+assert(t.isvararg == true and t.nparams == 2 and t.nups == 1)
+
+t = debug.getinfo(1)   -- main
+assert(t.isvararg == true and t.nparams == 0 and t.nups == 1 and
+       debug.getupvalue(t.func, 1) == "_ENV")
+
+
+-- testing debugging of coroutines
+
+local function checktraceback (co, p, level)
+  local tb = debug.traceback(co, nil, level)
+  local i = 0
+  for l in string.gmatch(tb, "[^\n]+\n?") do
+    assert(i == 0 or string.find(l, p[i]))
+    i = i+1
+  end
+  assert(p[i] == nil)
+end
+
+
+local function f (n)
+  if n > 0 then f(n-1)
+  else coroutine.yield() end
+end
+
+local co = coroutine.create(f)
+coroutine.resume(co, 3)
+checktraceback(co, {"yield", "db.lua", "db.lua", "db.lua", "db.lua"})
+checktraceback(co, {"db.lua", "db.lua", "db.lua", "db.lua"}, 1)
+checktraceback(co, {"db.lua", "db.lua", "db.lua"}, 2)
+checktraceback(co, {"db.lua"}, 4)
+checktraceback(co, {}, 40)
+
+
+co = coroutine.create(function (x)
+       local a = 1
+       coroutine.yield(debug.getinfo(1, "l"))
+       coroutine.yield(debug.getinfo(1, "l").currentline)
+       return a
+     end)
+
+local tr = {}
+local foo = function (e, l) if l then table.insert(tr, l) end end
+debug.sethook(co, foo, "lcr")
+
+local _, l = coroutine.resume(co, 10)
+local x = debug.getinfo(co, 1, "lfLS")
+assert(x.currentline == l.currentline and x.activelines[x.currentline])
+assert(type(x.func) == "function")
+for i=x.linedefined + 1, x.lastlinedefined do
+  assert(x.activelines[i])
+  x.activelines[i] = nil
+end
+assert(next(x.activelines) == nil)   -- no 'extra' elements
+assert(debug.getinfo(co, 2) == nil)
+local a,b = debug.getlocal(co, 1, 1)
+assert(a == "x" and b == 10)
+a,b = debug.getlocal(co, 1, 2)
+assert(a == "a" and b == 1)
+debug.setlocal(co, 1, 2, "hi")
+assert(debug.gethook(co) == foo)
+assert(#tr == 2 and
+       tr[1] == l.currentline-1 and tr[2] == l.currentline)
+
+a,b,c = pcall(coroutine.resume, co)
+assert(a and b and c == l.currentline+1)
+checktraceback(co, {"yield", "in function <"})
+
+a,b = coroutine.resume(co)
+assert(a and b == "hi")
+assert(#tr == 4 and tr[4] == l.currentline+2)
+assert(debug.gethook(co) == foo)
+assert(debug.gethook() == nil)
+checktraceback(co, {})
+
+
+-- check traceback of suspended (or dead with error) coroutines
+
+function f(i) if i==0 then error(i) else coroutine.yield(); f(i-1) end end
+
+co = coroutine.create(function (x) f(x) end)
+a, b = coroutine.resume(co, 3)
+t = {"'yield'", "'f'", "in function <"}
+while coroutine.status(co) == "suspended" do
+  checktraceback(co, t)
+  a, b = coroutine.resume(co)
+  table.insert(t, 2, "'f'")   -- one more recursive call to 'f'
+end
+t[1] = "'error'"
+checktraceback(co, t)
+
+
+-- test acessing line numbers of a coroutine from a resume inside
+-- a C function (this is a known bug in Lua 5.0)
+
+local function g(x)
+    coroutine.yield(x)
+end
+
+local function f (i)
+  debug.sethook(function () end, "l")
+  for j=1,1000 do
+    g(i+j)
+  end
+end
+
+local co = coroutine.wrap(f)
+co(10)
+pcall(co)
+pcall(co)
+
+
+assert(type(debug.getregistry()) == "table")
+
+
+-- test tagmethod information
+local a = {}
+local function f (t)
+  local info = debug.getinfo(1);
+  assert(info.namewhat == "metamethod")
+  a.op = info.name
+  return info.name
+end
+setmetatable(a, {
+  __index = f; __add = f; __div = f; __mod = f; __concat = f; __pow = f;
+  __eq = f; __le = f; __lt = f;
+})
+
+local b = setmetatable({}, getmetatable(a))
+
+assert(a[3] == "index" and a^3 == "pow" and a..a == "concat")
+assert(a/3 == "div" and 3%a == "mod")
+assert (a==b and a.op == "eq")
+assert (a>=b and a.op == "le")
+assert (a>b and a.op == "lt")
+
+
+print"OK"

+ 388 - 0
sandbox/JitTest/events.lua

@@ -0,0 +1,388 @@
+print('testing metatables')
+
+X = 20; B = 30
+
+_ENV = setmetatable({}, {__index=_G})
+
+collectgarbage()
+
+X = X+10
+assert(X == 30 and _G.X == 20)
+B = false
+assert(B == false)
+B = nil
+assert(B == 30)
+
+assert(getmetatable{} == nil)
+assert(getmetatable(4) == nil)
+assert(getmetatable(nil) == nil)
+a={}; setmetatable(a, {__metatable = "xuxu",
+                    __tostring=function(x) return x.name end})
+assert(getmetatable(a) == "xuxu")
+assert(tostring(a) == nil)
+-- cannot change a protected metatable
+assert(pcall(setmetatable, a, {}) == false)
+a.name = "gororoba"
+assert(tostring(a) == "gororoba")
+
+local a, t = {10,20,30; x="10", y="20"}, {}
+assert(setmetatable(a,t) == a)
+assert(getmetatable(a) == t)
+assert(setmetatable(a,nil) == a)
+assert(getmetatable(a) == nil)
+assert(setmetatable(a,t) == a)
+
+
+function f (t, i, e)
+  assert(not e)
+  local p = rawget(t, "parent")
+  return (p and p[i]+3), "dummy return"
+end
+
+t.__index = f
+
+a.parent = {z=25, x=12, [4] = 24}
+assert(a[1] == 10 and a.z == 28 and a[4] == 27 and a.x == "10")
+
+collectgarbage()
+
+a = setmetatable({}, t)
+function f(t, i, v) rawset(t, i, v-3) end
+setmetatable(t, t)   -- causes a bug in 5.1 !
+t.__newindex = f
+a[1] = 30; a.x = "101"; a[5] = 200
+assert(a[1] == 27 and a.x == 98 and a[5] == 197)
+
+
+local c = {}
+a = setmetatable({}, t)
+t.__newindex = c
+a[1] = 10; a[2] = 20; a[3] = 90
+assert(c[1] == 10 and c[2] == 20 and c[3] == 90)
+
+
+do
+  local a;
+  a = setmetatable({}, {__index = setmetatable({},
+                     {__index = setmetatable({},
+                     {__index = function (_,n) return a[n-3]+4, "lixo" end})})})
+  a[0] = 20
+  for i=0,10 do
+    assert(a[i*3] == 20 + i*4)
+  end
+end
+
+
+do  -- newindex
+  local foi
+  local a = {}
+  for i=1,10 do a[i] = 0; a['a'..i] = 0; end
+  setmetatable(a, {__newindex = function (t,k,v) foi=true; rawset(t,k,v) end})
+  foi = false; a[1]=0; assert(not foi)
+  foi = false; a['a1']=0; assert(not foi)
+  foi = false; a['a11']=0; assert(foi)
+  foi = false; a[11]=0; assert(foi)
+  foi = false; a[1]=nil; assert(not foi)
+  foi = false; a[1]=nil; assert(foi)
+end
+
+
+setmetatable(t, nil)
+function f (t, ...) return t, {...} end
+t.__call = f
+
+do
+  local x,y = a(table.unpack{'a', 1})
+  assert(x==a and y[1]=='a' and y[2]==1 and y[3]==nil)
+  x,y = a()
+  assert(x==a and y[1]==nil)
+end
+
+
+local b = setmetatable({}, t)
+setmetatable(b,t)
+
+function f(op)
+  return function (...) cap = {[0] = op, ...} ; return (...) end
+end
+t.__add = f("add")
+t.__sub = f("sub")
+t.__mul = f("mul")
+t.__div = f("div")
+t.__mod = f("mod")
+t.__unm = f("unm")
+t.__pow = f("pow")
+t.__len = f("len")
+
+assert(b+5 == b)
+assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==nil)
+assert(b+'5' == b)
+assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==nil)
+assert(5+b == 5)
+assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==nil)
+assert('5'+b == '5')
+assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==nil)
+b=b-3; assert(getmetatable(b) == t)
+assert(5-a == 5)
+assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==nil)
+assert('5'-a == '5')
+assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==nil)
+assert(a*a == a)
+assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil)
+assert(a/0 == a)
+assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil)
+assert(a%2 == a)
+assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil)
+assert(-a == a)
+assert(cap[0] == "unm" and cap[1] == a)
+assert(a^4 == a)
+assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==nil)
+assert(a^'4' == a)
+assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==nil)
+assert(4^a == 4)
+assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==nil)
+assert('4'^a == '4')
+assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==nil)
+assert(#a == a)
+assert(cap[0] == "len" and cap[1] == a)
+
+
+-- test for rawlen
+t = setmetatable({1,2,3}, {__len = function () return 10 end})
+assert(#t == 10 and rawlen(t) == 3)
+assert(rawlen"abc" == 3)
+assert(not pcall(rawlen, io.stdin))
+assert(not pcall(rawlen, 34))
+assert(not pcall(rawlen))
+
+t = {}
+t.__lt = function (a,b,c)
+  collectgarbage()
+  assert(c == nil)
+  if type(a) == 'table' then a = a.x end
+  if type(b) == 'table' then b = b.x end
+ return a<b, "dummy"
+end
+
+function Op(x) return setmetatable({x=x}, t) end
+
+local function test ()
+  assert(not(Op(1)<Op(1)) and (Op(1)<Op(2)) and not(Op(2)<Op(1)))
+  assert(not(1 < Op(1)) and (Op(1) < 2) and not(2 < Op(1)))
+  assert(not(Op('a')<Op('a')) and (Op('a')<Op('b')) and not(Op('b')<Op('a')))
+  assert(not('a' < Op('a')) and (Op('a') < 'b') and not(Op('b') < Op('a')))
+  assert((Op(1)<=Op(1)) and (Op(1)<=Op(2)) and not(Op(2)<=Op(1)))
+  assert((Op('a')<=Op('a')) and (Op('a')<=Op('b')) and not(Op('b')<=Op('a')))
+  assert(not(Op(1)>Op(1)) and not(Op(1)>Op(2)) and (Op(2)>Op(1)))
+  assert(not(Op('a')>Op('a')) and not(Op('a')>Op('b')) and (Op('b')>Op('a')))
+  assert((Op(1)>=Op(1)) and not(Op(1)>=Op(2)) and (Op(2)>=Op(1)))
+  assert((1 >= Op(1)) and not(1 >= Op(2)) and (Op(2) >= 1))
+  assert((Op('a')>=Op('a')) and not(Op('a')>=Op('b')) and (Op('b')>=Op('a')))
+  assert(('a' >= Op('a')) and not(Op('a') >= 'b') and (Op('b') >= Op('a')))
+end
+
+test()
+
+t.__le = function (a,b,c)
+  assert(c == nil)
+  if type(a) == 'table' then a = a.x end
+  if type(b) == 'table' then b = b.x end
+ return a<=b, "dummy"
+end
+
+test()  -- retest comparisons, now using both `lt' and `le'
+
+
+-- test `partial order'
+
+local function Set(x)
+  local y = {}
+  for _,k in pairs(x) do y[k] = 1 end
+  return setmetatable(y, t)
+end
+
+t.__lt = function (a,b)
+  for k in pairs(a) do
+    if not b[k] then return false end
+    b[k] = nil
+  end
+  return next(b) ~= nil
+end
+
+t.__le = nil
+
+assert(Set{1,2,3} < Set{1,2,3,4})
+assert(not(Set{1,2,3,4} < Set{1,2,3,4}))
+assert((Set{1,2,3,4} <= Set{1,2,3,4}))
+assert((Set{1,2,3,4} >= Set{1,2,3,4}))
+assert((Set{1,3} <= Set{3,5}))   -- wrong!! model needs a `le' method ;-)
+
+t.__le = function (a,b)
+  for k in pairs(a) do
+    if not b[k] then return false end
+  end
+  return true
+end
+
+assert(not (Set{1,3} <= Set{3,5}))   -- now its OK!
+assert(not(Set{1,3} <= Set{3,5}))
+assert(not(Set{1,3} >= Set{3,5}))
+
+t.__eq = function (a,b)
+  for k in pairs(a) do
+    if not b[k] then return false end
+    b[k] = nil
+  end
+  return next(b) == nil
+end
+
+local s = Set{1,3,5}
+assert(s == Set{3,5,1})
+assert(not rawequal(s, Set{3,5,1}))
+assert(rawequal(s, s))
+assert(Set{1,3,5,1} == Set{3,5,1})
+assert(Set{1,3,5} ~= Set{3,5,1,6})
+t[Set{1,3,5}] = 1
+assert(t[Set{1,3,5}] == nil)   -- `__eq' is not valid for table accesses
+
+
+t.__concat = function (a,b,c)
+  assert(c == nil)
+  if type(a) == 'table' then a = a.val end
+  if type(b) == 'table' then b = b.val end
+  if A then return a..b
+  else
+    return setmetatable({val=a..b}, t)
+  end
+end
+
+c = {val="c"}; setmetatable(c, t)
+d = {val="d"}; setmetatable(d, t)
+
+A = true
+assert(c..d == 'cd')
+assert(0 .."a".."b"..c..d.."e".."f"..(5+3).."g" == "0abcdef8g")
+
+A = false
+assert((c..d..c..d).val == 'cdcd')
+x = c..d
+assert(getmetatable(x) == t and x.val == 'cd')
+x = 0 .."a".."b"..c..d.."e".."f".."g"
+assert(x.val == "0abcdefg")
+
+
+-- concat metamethod x numbers (bug in 5.1.1)
+c = {}
+local x
+setmetatable(c, {__concat = function (a,b)
+  assert(type(a) == "number" and b == c or type(b) == "number" and a == c)
+  return c
+end})
+assert(c..5 == c and 5 .. c == c)
+assert(4 .. c .. 5 == c and 4 .. 5 .. 6 .. 7 .. c == c)
+
+
+-- test comparison compatibilities
+local t1, t2, c, d
+t1 = {};  c = {}; setmetatable(c, t1)
+d = {}
+t1.__eq = function () return true end
+t1.__lt = function () return true end
+setmetatable(d, t1)
+assert(c == d and c < d and not(d <= c))
+t2 = {}
+t2.__eq = t1.__eq
+t2.__lt = t1.__lt
+setmetatable(d, t2)
+assert(c == d and c < d and not(d <= c))
+
+
+
+-- test for several levels of calls
+local i
+local tt = {
+  __call = function (t, ...)
+    i = i+1
+    if t.f then return t.f(...)
+    else return {...}
+    end
+  end
+}
+
+local a = setmetatable({}, tt)
+local b = setmetatable({f=a}, tt)
+local c = setmetatable({f=b}, tt)
+
+i = 0
+x = c(3,4,5)
+assert(i == 3 and x[1] == 3 and x[3] == 5)
+
+
+assert(_G.X == 20)
+
+print'+'
+
+local _g = _G
+_ENV = setmetatable({}, {__index=function (_,k) return _g[k] end})
+
+
+a = {}
+rawset(a, "x", 1, 2, 3)
+assert(a.x == 1 and rawget(a, "x", 3) == 1)
+
+print '+'
+
+-- testing metatables for basic types
+local debug = require'debug'
+mt = {}
+debug.setmetatable(10, mt)
+assert(getmetatable(-2) == mt)
+mt.__index = function (a,b) return a+b end
+assert((10)[3] == 13)
+assert((10)["3"] == 13)
+debug.setmetatable(23, nil)
+assert(getmetatable(-2) == nil)
+
+debug.setmetatable(true, mt)
+assert(getmetatable(false) == mt)
+mt.__index = function (a,b) return a or b end
+assert((true)[false] == true)
+assert((false)[false] == false)
+debug.setmetatable(false, nil)
+assert(getmetatable(true) == nil)
+
+debug.setmetatable(nil, mt)
+assert(getmetatable(nil) == mt)
+mt.__add = function (a,b) return (a or 0) + (b or 0) end
+assert(10 + nil == 10)
+assert(nil + 23 == 23)
+assert(nil + nil == 0)
+debug.setmetatable(nil, nil)
+assert(getmetatable(nil) == nil)
+
+debug.setmetatable(nil, {})
+
+
+-- loops in delegation
+a = {}; setmetatable(a, a); a.__index = a; a.__newindex = a
+assert(not pcall(function (a,b) return a[b] end, a, 10))
+assert(not pcall(function (a,b,c) a[b] = c end, a, 10, true))
+
+-- bug in 5.1
+T, K, V = nil
+grandparent = {}
+grandparent.__newindex = function(t,k,v) T=t; K=k; V=v end
+
+parent = {}
+parent.__newindex = parent
+setmetatable(parent, grandparent)
+
+child = setmetatable({}, parent)
+child.foo = 10      --> CRASH (on some machines)
+assert(T == parent and K == "foo" and V == 10)
+
+print 'OK'
+
+return 12
+
+

+ 118 - 0
sandbox/JitTest/test.lua

@@ -0,0 +1,118 @@
+
+local temp = 3 %"4"
+
+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
+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)

+ 1 - 1
src/Lua.SourceGenerator/Comparer.cs

@@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis;
 
 namespace Lua.SourceGenerator;
 
-internal sealed class Comparer : IEqualityComparer<(GeneratorAttributeSyntaxContext, Compilation)>
+sealed class Comparer : IEqualityComparer<(GeneratorAttributeSyntaxContext, Compilation)>
 {
     public static readonly Comparer Instance = new();
 

+ 18 - 18
src/Lua.SourceGenerator/Lua.SourceGenerator.csproj

@@ -1,23 +1,23 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
-  <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <LangVersion>12</LangVersion>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <Nullable>enable</Nullable>
-    <IsRoslynComponent>true</IsRoslynComponent>
-    <AnalyzerLanguage>cs</AnalyzerLanguage>
-    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
-    <IncludeBuildOutput>false</IncludeBuildOutput>
-    <DevelopmentDependency>true</DevelopmentDependency>
-    <IncludeSymbols>false</IncludeSymbols>
-    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
-  </PropertyGroup>
+    <PropertyGroup>
+        <TargetFramework>netstandard2.0</TargetFramework>
+        <LangVersion>12</LangVersion>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <IsRoslynComponent>true</IsRoslynComponent>
+        <AnalyzerLanguage>cs</AnalyzerLanguage>
+        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
+        <IncludeBuildOutput>false</IncludeBuildOutput>
+        <DevelopmentDependency>true</DevelopmentDependency>
+        <IncludeSymbols>false</IncludeSymbols>
+        <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
+    </PropertyGroup>
 
-  <ItemGroup>
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0" />
-	
-	  <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
-  </ItemGroup>
+    <ItemGroup>
+        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
+
+        <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
+    </ItemGroup>
 
 </Project>

+ 166 - 51
src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs

@@ -6,6 +6,13 @@ namespace Lua.SourceGenerator;
 
 partial class LuaObjectGenerator
 {
+    static string GetLuaValuePrefix(ITypeSymbol typeSymbol, SymbolReferences references, Compilation compilation)
+    {
+        return compilation.ClassifyCommonConversion(typeSymbol, references.LuaUserData).Exists
+            ? "global::Lua.LuaValue.FromUserData("
+            : "(";
+    }
+
     static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context, Dictionary<INamedTypeSymbol, TypeMetadata> metaDict)
     {
         try
@@ -75,24 +82,24 @@ partial class LuaObjectGenerator
                 (true, true) => "record struct",
                 (true, false) => "record",
                 (false, true) => "struct",
-                (false, false) => "class",
+                (false, false) => "class"
             };
 
             using var _ = builder.BeginBlockScope($"partial {typeDeclarationKeyword} {typeMetadata.TypeName} : global::Lua.ILuaUserData");
 
             var metamethodSet = new HashSet<LuaObjectMetamethod>();
 
-            if (!TryEmitMethods(typeMetadata, builder, references, metamethodSet, context))
+            if (!TryEmitMethods(typeMetadata, builder, references, compilation, metamethodSet, context))
             {
                 return false;
             }
 
-            if (!TryEmitIndexMetamethod(typeMetadata, builder, context))
+            if (!TryEmitIndexMetamethod(typeMetadata, builder, references, compilation, context))
             {
                 return false;
             }
 
-            if (!TryEmitNewIndexMetamethod(typeMetadata, builder, context))
+            if (!TryEmitNewIndexMetamethod(typeMetadata, builder, references, context))
             {
                 return false;
             }
@@ -106,10 +113,13 @@ partial class LuaObjectGenerator
             builder.AppendLine($"public static implicit operator global::Lua.LuaValue({typeMetadata.FullTypeName} value)");
             using (builder.BeginBlockScope())
             {
-                builder.AppendLine("return new(value);");
+                builder.AppendLine("return  global::Lua.LuaValue.FromUserData(value);");
             }
 
-            if (!ns.IsGlobalNamespace) builder.EndBlock();
+            if (!ns.IsGlobalNamespace)
+            {
+                builder.EndBlock();
+            }
 
             builder.AppendLine("#pragma warning restore CS0162 // Unreachable code");
             builder.AppendLine("#pragma warning restore CS0219 // Variable assigned but never used");
@@ -132,8 +142,25 @@ partial class LuaObjectGenerator
 
         foreach (var property in typeMetadata.Properties)
         {
-            if (SymbolEqualityComparer.Default.Equals(property.Type, references.LuaValue)) continue;
-            if (SymbolEqualityComparer.Default.Equals(property.Type, typeMetadata.Symbol)) continue;
+            if (SymbolEqualityComparer.Default.Equals(property.Type, references.LuaValue))
+            {
+                continue;
+            }
+
+            if (SymbolEqualityComparer.Default.Equals(property.Type, references.LuaUserData))
+            {
+                continue;
+            }
+
+            if (SymbolEqualityComparer.Default.Equals(property.Type, typeMetadata.Symbol))
+            {
+                continue;
+            }
+
+            if (compilation.ClassifyConversion(property.Type, references.LuaUserData).Exists)
+            {
+                continue;
+            }
 
             var conversion = compilation.ClassifyConversion(property.Type, references.LuaValue);
             if (!conversion.Exists && (property.Type is not INamedTypeSymbol namedTypeSymbol || !metaDict.ContainsKey(namedTypeSymbol)))
@@ -156,13 +183,33 @@ partial class LuaObjectGenerator
                 if (method.IsAsync)
                 {
                     var namedType = (INamedTypeSymbol)typeSymbol;
-                    if (namedType.TypeArguments.Length == 0) goto PARAMETERS;
+                    if (namedType.TypeArguments.Length == 0)
+                    {
+                        goto PARAMETERS;
+                    }
 
                     typeSymbol = namedType.TypeArguments[0];
                 }
 
-                if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue)) goto PARAMETERS;
-                if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol)) goto PARAMETERS;
+                if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue))
+                {
+                    goto PARAMETERS;
+                }
+
+                if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaUserData))
+                {
+                    goto PARAMETERS;
+                }
+
+                if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol))
+                {
+                    goto PARAMETERS;
+                }
+
+                if (compilation.ClassifyConversion(typeSymbol, references.LuaUserData).Exists)
+                {
+                    goto PARAMETERS;
+                }
 
                 var conversion = compilation.ClassifyConversion(typeSymbol, references.LuaValue);
                 if (!conversion.Exists && (typeSymbol is not INamedTypeSymbol namedTypeSymbol || !metaDict.ContainsKey(namedTypeSymbol)))
@@ -177,11 +224,34 @@ partial class LuaObjectGenerator
             }
 
         PARAMETERS:
-            foreach (var typeSymbol in method.Symbol.Parameters
-                .Select(x => x.Type))
+            for (var index = 0; index < method.Symbol.Parameters.Length; index++)
             {
-                if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue)) continue;
-                if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol)) continue;
+                var parameterSymbol = method.Symbol.Parameters[index];
+                var typeSymbol = parameterSymbol.Type;
+                if (index == method.Symbol.Parameters.Length - 1 && SymbolEqualityComparer.Default.Equals(typeSymbol, references.CancellationToken))
+                {
+                    continue;
+                }
+
+                if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue))
+                {
+                    continue;
+                }
+
+                if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaUserData))
+                {
+                    continue;
+                }
+
+                if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol))
+                {
+                    continue;
+                }
+
+                if (compilation.ClassifyConversion(typeSymbol, references.LuaUserData).Exists)
+                {
+                    continue;
+                }
 
                 var conversion = compilation.ClassifyConversion(typeSymbol, references.LuaValue);
                 if (!conversion.Exists && (typeSymbol is not INamedTypeSymbol namedTypeSymbol || !metaDict.ContainsKey(namedTypeSymbol)))
@@ -199,9 +269,9 @@ partial class LuaObjectGenerator
         return isValid;
     }
 
-    static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, in SourceProductionContext context)
+    static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context)
     {
-        builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction(""index"", (context, buffer, ct) =>");
+        builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction(""index"", (context, ct) =>");
 
         using (builder.BeginBlockScope())
         {
@@ -213,28 +283,29 @@ partial class LuaObjectGenerator
             {
                 foreach (var propertyMetadata in typeMetadata.Properties)
                 {
+                    var conversionPrefix = GetLuaValuePrefix(propertyMetadata.Type, references, compilation);
                     if (propertyMetadata.IsStatic)
                     {
-                        builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => new global::Lua.LuaValue({typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name}),");
+                        builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => {conversionPrefix}{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name}),");
                     }
                     else
                     {
-                        builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => new global::Lua.LuaValue(userData.{propertyMetadata.Symbol.Name}),");
+                        builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => {conversionPrefix}userData.{propertyMetadata.Symbol.Name}),");
                     }
                 }
 
                 foreach (var methodMetadata in typeMetadata.Methods
-                    .Where(x => x.HasMemberAttribute))
+                             .Where(x => x.HasMemberAttribute))
                 {
                     builder.AppendLine(@$"""{methodMetadata.LuaMemberName}"" => new global::Lua.LuaValue(__function_{methodMetadata.LuaMemberName}),");
                 }
 
                 builder.AppendLine(@$"_ => global::Lua.LuaValue.Nil,");
             }
+
             builder.AppendLine(";");
 
-            builder.AppendLine("buffer.Span[0] = result;");
-            builder.AppendLine("return new(1);");
+            builder.AppendLine("return new global::System.Threading.Tasks.ValueTask<int>(context.Return(result));");
         }
 
         builder.AppendLine(");");
@@ -242,9 +313,9 @@ partial class LuaObjectGenerator
         return true;
     }
 
-    static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, in SourceProductionContext context)
+    static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, in SourceProductionContext context)
     {
-        builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction(""newindex"", (context, buffer, ct) =>");
+        builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction(""newindex"", (context, ct) =>");
 
         using (builder.BeginBlockScope())
         {
@@ -262,29 +333,45 @@ partial class LuaObjectGenerator
                     {
                         if (propertyMetadata.IsReadOnly)
                         {
-                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' cannot overwrite."");");
+                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' cannot overwrite."");");
                         }
                         else if (propertyMetadata.IsStatic)
                         {
-                            builder.AppendLine(@$"{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
+                            if (SymbolEqualityComparer.Default.Equals(propertyMetadata.Type, references.LuaValue))
+                            {
+                                builder.AppendLine($"{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name} = context.GetArgument(2);");
+                            }
+                            else
+                            {
+                                builder.AppendLine($"{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
+                            }
+
                             builder.AppendLine("break;");
                         }
                         else
                         {
-                            builder.AppendLine(@$"userData.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
+                            if (SymbolEqualityComparer.Default.Equals(propertyMetadata.Type, references.LuaValue))
+                            {
+                                builder.AppendLine($"userData.{propertyMetadata.Symbol.Name} = context.GetArgument(2);");
+                            }
+                            else
+                            {
+                                builder.AppendLine($"userData.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
+                            }
+
                             builder.AppendLine("break;");
                         }
                     }
                 }
 
                 foreach (var methodMetadata in typeMetadata.Methods
-                    .Where(x => x.HasMemberAttribute))
+                             .Where(x => x.HasMemberAttribute))
                 {
                     builder.AppendLine(@$"case ""{methodMetadata.LuaMemberName}"":");
 
                     using (builder.BeginIndentScope())
                     {
-                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' cannot overwrite."");");
+                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' cannot overwrite."");");
                     }
                 }
 
@@ -292,11 +379,11 @@ partial class LuaObjectGenerator
 
                 using (builder.BeginIndentScope())
                 {
-                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' not found."");");
+                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' not found."");");
                 }
             }
 
-            builder.AppendLine("return new(0);");
+            builder.AppendLine("return new global::System.Threading.Tasks.ValueTask<int>(context.Return());");
         }
 
         builder.AppendLine(");");
@@ -304,7 +391,7 @@ partial class LuaObjectGenerator
         return true;
     }
 
-    static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, HashSet<LuaObjectMetamethod> metamethodSet, in SourceProductionContext context)
+    static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, HashSet<LuaObjectMetamethod> metamethodSet, in SourceProductionContext context)
     {
         builder.AppendLine();
 
@@ -315,7 +402,7 @@ partial class LuaObjectGenerator
             if (methodMetadata.HasMemberAttribute)
             {
                 functionName = $"__function_{methodMetadata.LuaMemberName}";
-                EmitMethodFunction(functionName, methodMetadata.LuaMemberName, typeMetadata, methodMetadata, builder, references);
+                EmitMethodFunction(functionName, methodMetadata.LuaMemberName, typeMetadata, methodMetadata, builder, references, compilation);
             }
 
             if (methodMetadata.HasMetamethodAttribute)
@@ -334,7 +421,7 @@ partial class LuaObjectGenerator
 
                 if (functionName == null)
                 {
-                    EmitMethodFunction($"__metamethod_{methodMetadata.Metamethod}", methodMetadata.Metamethod.ToString().ToLower(), typeMetadata, methodMetadata, builder, references);
+                    EmitMethodFunction($"__metamethod_{methodMetadata.Metamethod}", methodMetadata.Metamethod.ToString().ToLower(), typeMetadata, methodMetadata, builder, references, compilation);
                 }
                 else
                 {
@@ -346,9 +433,9 @@ partial class LuaObjectGenerator
         return true;
     }
 
-    static void EmitMethodFunction(string functionName, string chunkName, TypeMetadata typeMetadata, MethodMetadata methodMetadata, CodeBuilder builder, SymbolReferences references)
+    static void EmitMethodFunction(string functionName, string chunkName, TypeMetadata typeMetadata, MethodMetadata methodMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation)
     {
-        builder.AppendLine($@"static readonly global::Lua.LuaFunction {functionName} = new global::Lua.LuaFunction(""{chunkName}"", {(methodMetadata.IsAsync ? "async" : "")} (context, buffer, ct) =>");
+        builder.AppendLine($@"static readonly global::Lua.LuaFunction {functionName} = new global::Lua.LuaFunction(""{chunkName}"", {(methodMetadata.IsAsync ? "async" : "")} (context, ct) =>");
 
         using (builder.BeginBlockScope())
         {
@@ -360,9 +447,19 @@ partial class LuaObjectGenerator
                 index++;
             }
 
-            foreach (var parameter in methodMetadata.Symbol.Parameters)
+            var hasCancellationToken = false;
+
+            for (var i = 0; i < methodMetadata.Symbol.Parameters.Length; i++)
             {
-                var isParameterLuaValue = SymbolEqualityComparer.Default.Equals(parameter.Type, references.LuaValue);
+                var parameter = methodMetadata.Symbol.Parameters[i];
+                var parameterType = parameter.Type;
+                var isParameterLuaValue = SymbolEqualityComparer.Default.Equals(parameterType, references.LuaValue);
+
+                if (i == methodMetadata.Symbol.Parameters.Length - 1 && SymbolEqualityComparer.Default.Equals(parameterType, references.CancellationToken))
+                {
+                    hasCancellationToken = true;
+                    break;
+                }
 
                 if (parameter.HasExplicitDefaultValue)
                 {
@@ -374,7 +471,7 @@ partial class LuaObjectGenerator
                     }
                     else
                     {
-                        builder.AppendLine($"var arg{index} = context.HasArgument({index}) ?  context.GetArgument<{parameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index}) : {syntax.Default!.Value.ToFullString()};");
+                        builder.AppendLine($"var arg{index} = context.HasArgument({index}) ?  context.GetArgument<{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index}) : {syntax.Default!.Value.ToFullString()};");
                     }
                 }
                 else
@@ -385,9 +482,10 @@ partial class LuaObjectGenerator
                     }
                     else
                     {
-                        builder.AppendLine($"var arg{index} = context.GetArgument<{parameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index});");
+                        builder.AppendLine($"var arg{index} = context.GetArgument<{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index});");
                     }
                 }
+
                 index++;
             }
 
@@ -398,40 +496,56 @@ partial class LuaObjectGenerator
 
             if (methodMetadata.IsAsync)
             {
-                builder.Append("await ", false);
+                builder.Append("await ", !methodMetadata.HasReturnValue);
             }
 
             if (methodMetadata.IsStatic)
             {
-                builder.Append($"{typeMetadata.FullTypeName}.{methodMetadata.Symbol.Name}(", false);
+                builder.Append($"{typeMetadata.FullTypeName}.{methodMetadata.Symbol.Name}(", !(methodMetadata.HasReturnValue || methodMetadata.IsAsync));
                 builder.Append(string.Join(",", Enumerable.Range(0, index).Select(x => $"arg{x}")), false);
+
+                if (hasCancellationToken)
+                {
+                    builder.Append(index > 0 ? ",ct" : "ct", false);
+                }
+
                 builder.AppendLine(");", false);
             }
             else
             {
-                builder.Append($"userData.{methodMetadata.Symbol.Name}(");
+                builder.Append($"userData.{methodMetadata.Symbol.Name}(", !(methodMetadata.HasReturnValue || methodMetadata.IsAsync));
                 builder.Append(string.Join(",", Enumerable.Range(1, index - 1).Select(x => $"arg{x}")), false);
+
+                if (hasCancellationToken)
+                {
+                    builder.Append(index > 1 ? ",ct" : "ct", false);
+                }
+
                 builder.AppendLine(");", false);
             }
 
+            builder.Append("return ");
             if (methodMetadata.HasReturnValue)
             {
-                if (SymbolEqualityComparer.Default.Equals(methodMetadata.Symbol.ReturnType, references.LuaValue))
-                {
-                    builder.AppendLine("buffer.Span[0] = result;");
-                }
-                else
+                var returnType = methodMetadata.Symbol.ReturnType;
+                if (methodMetadata.IsAsync)
                 {
-                    builder.AppendLine("buffer.Span[0] = new global::Lua.LuaValue(result);");
+                    var namedType = (INamedTypeSymbol)returnType;
+                    if (namedType.TypeArguments.Length == 1)
+                    {
+                        returnType = namedType.TypeArguments[0];
+                    }
                 }
 
-                builder.AppendLine($"return {(methodMetadata.IsAsync ? "1" : "new(1)")};");
+                var conversionPrefix = GetLuaValuePrefix(returnType, references, compilation);
+                builder.AppendLine(methodMetadata.IsAsync ? $"context.Return({conversionPrefix}result));" : $"new global::System.Threading.Tasks.ValueTask<int>(context.Return({conversionPrefix}result)));", false);
             }
             else
             {
-                builder.AppendLine($"return {(methodMetadata.IsAsync ? "0" : "new(0)")};");
+                builder.AppendLine(methodMetadata.IsAsync ? "context.Return();" : "new global::System.Threading.Tasks.ValueTask<int>(context.Return());", false);
             }
         }
+
         builder.AppendLine(");");
         builder.AppendLine();
     }
@@ -453,6 +567,7 @@ partial class LuaObjectGenerator
                 {
                     builder.AppendLine($"__metatable[global::Lua.Runtime.Metamethods.{metamethod}] = __metamethod_{metamethod};");
                 }
+
                 builder.AppendLine("return __metatable;");
             }
 

+ 3 - 3
src/Lua.SourceGenerator/LuaObjectMetamethod.cs

@@ -2,7 +2,7 @@ namespace Lua.SourceGenerator;
 
 // same as Lua.LuaObjectMetamethod
 
-internal enum LuaObjectMetamethod
+enum LuaObjectMetamethod
 {
     Add,
     Sub,
@@ -19,5 +19,5 @@ internal enum LuaObjectMetamethod
     Concat,
     Pairs,
     IPairs,
-    ToString,
-}
+    ToString
+}

+ 2 - 2
src/Lua.SourceGenerator/MethodMetadata.cs

@@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis;
 
 namespace Lua.SourceGenerator;
 
-internal class MethodMetadata
+class MethodMetadata
 {
     public IMethodSymbol Symbol { get; }
     public bool IsStatic { get; }
@@ -19,7 +19,7 @@ internal class MethodMetadata
         IsStatic = symbol.IsStatic;
 
         var returnType = symbol.ReturnType;
-        var fullName = (returnType.ContainingNamespace.IsGlobalNamespace ? "" : (returnType.ContainingNamespace + ".")) + returnType.Name;
+        var fullName = (returnType.ContainingNamespace.IsGlobalNamespace ? "" : returnType.ContainingNamespace + ".") + returnType.Name;
         IsAsync = fullName is "System.Threading.Tasks.Task"
             or "System.Threading.Tasks.ValueTask"
             or "Cysharp.Threading.Tasks.UniTask"

+ 26 - 7
src/Lua.SourceGenerator/SymbolReferences.cs

@@ -7,21 +7,40 @@ public sealed class SymbolReferences
     public static SymbolReferences? Create(Compilation compilation)
     {
         var luaObjectAttribute = compilation.GetTypeByMetadataName("Lua.LuaObjectAttribute");
-        if (luaObjectAttribute == null) return null;
+        if (luaObjectAttribute == null)
+        {
+            return null;
+        }
 
-        return new SymbolReferences
+        return new()
         {
             LuaObjectAttribute = luaObjectAttribute,
             LuaMemberAttribute = compilation.GetTypeByMetadataName("Lua.LuaMemberAttribute")!,
             LuaIgnoreMemberAttribute = compilation.GetTypeByMetadataName("Lua.LuaIgnoreMemberAttribute")!,
             LuaMetamethodAttribute = compilation.GetTypeByMetadataName("Lua.LuaMetamethodAttribute")!,
             LuaValue = compilation.GetTypeByMetadataName("Lua.LuaValue")!,
+            Boolean = compilation.GetTypeByMetadataName("System.Boolean")!,
+            String = compilation.GetTypeByMetadataName("System.String")!,
+            Double = compilation.GetTypeByMetadataName("System.Double")!,
+            LuaFunction = compilation.GetTypeByMetadataName("Lua.LuaFunction")!,
+            LuaThread = compilation.GetTypeByMetadataName("Lua.LuaThread")!,
+            LuaTable = compilation.GetTypeByMetadataName("Lua.LuaTable")!,
+            LuaUserData = compilation.GetTypeByMetadataName("Lua.ILuaUserData")!,
+            CancellationToken = compilation.GetTypeByMetadataName("System.Threading.CancellationToken")!
         };
     }
 
-    public INamedTypeSymbol LuaObjectAttribute { get; private set; } = default!;
-    public INamedTypeSymbol LuaMemberAttribute { get; private set; } = default!;
-    public INamedTypeSymbol LuaIgnoreMemberAttribute { get; private set; } = default!;
-    public INamedTypeSymbol LuaMetamethodAttribute { get; private set; } = default!;
-    public INamedTypeSymbol LuaValue { get; private set; } = default!;
+    public INamedTypeSymbol LuaObjectAttribute { get; private set; } = null!;
+    public INamedTypeSymbol LuaMemberAttribute { get; private set; } = null!;
+    public INamedTypeSymbol LuaIgnoreMemberAttribute { get; private set; } = null!;
+    public INamedTypeSymbol LuaMetamethodAttribute { get; private set; } = null!;
+    public INamedTypeSymbol LuaValue { get; private set; } = null!;
+    public INamedTypeSymbol Boolean { get; private set; } = null!;
+    public INamedTypeSymbol String { get; private set; } = null!;
+    public INamedTypeSymbol Double { get; private set; } = null!;
+    public INamedTypeSymbol LuaFunction { get; private set; } = null!;
+    public INamedTypeSymbol LuaThread { get; private set; } = null!;
+    public INamedTypeSymbol LuaTable { get; private set; } = null!;
+    public INamedTypeSymbol LuaUserData { get; private set; } = null!;
+    public INamedTypeSymbol CancellationToken { get; private set; } = null!;
 }

+ 24 - 6
src/Lua.SourceGenerator/TypeMetadata.cs

@@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
 
 namespace Lua.SourceGenerator;
 
-internal class TypeMetadata
+class TypeMetadata
 {
     public TypeDeclarationSyntax Syntax { get; }
     public INamedTypeSymbol Symbol { get; }
@@ -25,13 +25,27 @@ internal class TypeMetadata
             .Where(x => x is (IFieldSymbol or IPropertySymbol) and { IsImplicitlyDeclared: false })
             .Where(x =>
             {
-                if (!x.ContainsAttribute(references.LuaMemberAttribute)) return false;
-                if (x.ContainsAttribute(references.LuaIgnoreMemberAttribute)) return false;
+                if (!x.ContainsAttribute(references.LuaMemberAttribute))
+                {
+                    return false;
+                }
+
+                if (x.ContainsAttribute(references.LuaIgnoreMemberAttribute))
+                {
+                    return false;
+                }
 
                 if (x is IPropertySymbol p)
                 {
-                    if (p.GetMethod == null || p.SetMethod == null) return false;
-                    if (p.IsIndexer) return false;
+                    if (p.GetMethod == null || p.SetMethod == null)
+                    {
+                        return false;
+                    }
+
+                    if (p.IsIndexer)
+                    {
+                        return false;
+                    }
                 }
 
                 return true;
@@ -44,7 +58,11 @@ internal class TypeMetadata
             .Select(x => (IMethodSymbol)x)
             .Where(x =>
             {
-                if (x.ContainsAttribute(references.LuaIgnoreMemberAttribute)) return false;
+                if (x.ContainsAttribute(references.LuaIgnoreMemberAttribute))
+                {
+                    return false;
+                }
+
                 return x.ContainsAttribute(references.LuaMemberAttribute) || x.ContainsAttribute(references.LuaMetamethodAttribute);
             })
             .Select(x => new MethodMetadata(x, references))

+ 28 - 6
src/Lua.SourceGenerator/Utilities/CodeBuilder.cs

@@ -2,7 +2,7 @@ using System.Text;
 
 namespace Lua.SourceGenerator;
 
-internal sealed class CodeBuilder
+sealed class CodeBuilder
 {
     public ref struct IndentScope
     {
@@ -11,7 +11,11 @@ internal sealed class CodeBuilder
         public IndentScope(CodeBuilder source, string? startLine = null)
         {
             this.source = source;
-            source.AppendLine(startLine);
+            if (startLine != null)
+            {
+                source.AppendLine(startLine);
+            }
+
             source.IncreaseIndent();
         }
 
@@ -28,7 +32,11 @@ internal sealed class CodeBuilder
         public BlockScope(CodeBuilder source, string? startLine = null)
         {
             this.source = source;
-            source.AppendLine(startLine);
+            if (startLine != null)
+            {
+                source.AppendLine(startLine);
+            }
+
             source.BeginBlock();
         }
 
@@ -41,8 +49,15 @@ internal sealed class CodeBuilder
     readonly StringBuilder buffer = new();
     int indentLevel;
 
-    public IndentScope BeginIndentScope(string? startLine = null) => new(this, startLine);
-    public BlockScope BeginBlockScope(string? startLine = null) => new(this, startLine);
+    public IndentScope BeginIndentScope(string? startLine = null)
+    {
+        return new(this, startLine);
+    }
+
+    public BlockScope BeginBlockScope(string? startLine = null)
+    {
+        return new(this, startLine);
+    }
 
     public void Append(string value, bool indent = true)
     {
@@ -82,13 +97,18 @@ internal sealed class CodeBuilder
             {
                 buffer.Append(", ");
             }
+
             buffer.Append(x);
             first = false;
         }
+
         buffer.Append(" }");
     }
 
-    public override string ToString() => buffer.ToString();
+    public override string ToString()
+    {
+        return buffer.ToString();
+    }
 
     public void IncreaseIndent()
     {
@@ -98,7 +118,9 @@ internal sealed class CodeBuilder
     public void DecreaseIndent()
     {
         if (indentLevel > 0)
+        {
             indentLevel--;
+        }
     }
 
     public void BeginBlock()

+ 3 - 5
src/Lua.SourceGenerator/Utilities/RoslynAnalyzerExtensions.cs

@@ -2,19 +2,17 @@ using Microsoft.CodeAnalysis;
 
 namespace Lua.SourceGenerator;
 
-internal static class RoslynAnalyzerExtensions
+static class RoslynAnalyzerExtensions
 {
     public static AttributeData? FindAttribute(this IEnumerable<AttributeData> attributeDataList, string typeName)
     {
         return attributeDataList
-            .Where(x => x.AttributeClass?.ToDisplayString() == typeName)
-            .FirstOrDefault();
+            .FirstOrDefault(x => x.AttributeClass?.ToDisplayString() == typeName);
     }
 
     public static AttributeData? FindAttributeShortName(this IEnumerable<AttributeData> attributeDataList, string typeName)
     {
         return attributeDataList
-            .Where(x => x.AttributeClass?.Name == typeName)
-            .FirstOrDefault();
+            .FirstOrDefault(x => x.AttributeClass?.Name == typeName);
     }
 }

+ 1 - 1
src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs

@@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis;
 
 namespace Lua.SourceGenerator;
 
-internal static class SymbolExtensions
+static class SymbolExtensions
 {
     public static bool ContainsAttribute(this ISymbol symbol, INamedTypeSymbol attribtue)
     {

+ 10 - 9
src/Lua.Unity/Assets/AddressableAssetsData/AddressableAssetSettings.asset

@@ -12,10 +12,10 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: 468a46d0ae32c3544b7d98094e6448a9, type: 3}
   m_Name: AddressableAssetSettings
   m_EditorClassIdentifier: 
-  m_DefaultGroup: 2a0e06d9ccf214f8fa1ca5d1510af7d2
+  m_DefaultGroup: d88070ff273c67a47aa61b1ac322925e
   m_currentHash:
     serializedVersion: 2
-    Hash: 00000000000000000000000000000000
+    Hash: 0541ae50fa90e8e78e8bf32544e171fd
   m_OptimizeCatalogSize: 0
   m_BuildRemoteCatalog: 0
   m_CatalogRequestsTimeout: 0
@@ -24,11 +24,11 @@ MonoBehaviour:
   m_InternalBundleIdMode: 1
   m_AssetLoadMode: 0
   m_BundledAssetProviderType:
-    m_AssemblyName: 
-    m_ClassName: 
+    m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+    m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
   m_AssetBundleProviderType:
-    m_AssemblyName: 
-    m_ClassName: 
+    m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+    m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
   m_IgnoreUnsupportedFilesInBuild: 0
   m_UniqueBundleIds: 0
   m_EnableJsonCatalog: 0
@@ -60,6 +60,7 @@ MonoBehaviour:
   m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]'
   m_GroupAssets:
   - {fileID: 11400000, guid: 30f1ec4a0d3c9473d9b32a1b02474847, type: 2}
+  - {fileID: 11400000, guid: 1561a30b342064fa4896f0bfbf686fcb, type: 2}
   m_BuildSettings:
     m_LogResourceManagerExceptions: 1
     m_BundleBuildPath: Temp/com.unity.addressables/AssetBundles
@@ -108,7 +109,7 @@ MonoBehaviour:
     m_ClassName: 
   m_ActivePlayerDataBuilderIndex: 2
   m_DataBuilders:
-  - {fileID: 11400000, guid: d6921a29df7234327863bccd63d917f6, type: 2}
-  - {fileID: 11400000, guid: 703d4c54c8176478e87c027f44a5424e, type: 2}
-  - {fileID: 11400000, guid: 276c7bc6c400449ed930dde9a70d1e70, type: 2}
+  - {fileID: 11400000, guid: 0618f16a59fed47c1933ae8dde25092a, type: 2}
+  - {fileID: 11400000, guid: e4575ed4ef1a746ccb1d0fba41ecd093, type: 2}
+  - {fileID: 11400000, guid: 5f12744ea547f4fe8b261b427250778b, type: 2}
   m_ActiveProfileId: 1d0a56529a3514cf1aebb59dc61a2305

+ 5 - 5
src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset

@@ -13,16 +13,16 @@ MonoBehaviour:
   m_Name: Default Local Group
   m_EditorClassIdentifier: 
   m_GroupName: Default Local Group
-  m_GUID: 2a0e06d9ccf214f8fa1ca5d1510af7d2
+  m_GUID: d88070ff273c67a47aa61b1ac322925e
   m_SerializeEntries:
-  - m_GUID: c611eb631438746b991def52ebe74776
+  - m_GUID: 16a50852a63da4d0d9221dcb9b74c48c
     m_Address: bar
     m_ReadOnly: 0
     m_SerializedLabels: []
     FlaggedDuringContentUpdateRestriction: 0
   m_ReadOnly: 0
-  m_Settings: {fileID: 11400000, guid: f1d773be05107486d85b5a324c1c2fb0, type: 2}
+  m_Settings: {fileID: 11400000, guid: a0f4200005a8343fa9cfda7e23be8a93, type: 2}
   m_SchemaSet:
     m_Schemas:
-    - {fileID: 11400000, guid: 1476390b038364f2f800595d61171b27, type: 2}
-    - {fileID: 11400000, guid: 7ebbf8a9fd56642d283eb1ef4c8ccf85, type: 2}
+    - {fileID: 11400000, guid: a90139adc8865497d8309c82638492b0, type: 2}
+    - {fileID: 11400000, guid: f83e7898558224636ac47d66c1d5f91d, type: 2}

+ 1 - 1
src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset.meta

@@ -2,7 +2,7 @@ fileFormatVersion: 2
 guid: 1561a30b342064fa4896f0bfbf686fcb
 NativeFormatImporter:
   externalObjects: {}
-  mainObjectFileID: 0
+  mainObjectFileID: 11400000
   userData: 
   assetBundleName: 
   assetBundleVariant: 

+ 1 - 1
src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset

@@ -12,7 +12,7 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
   m_Name: Default Local Group_BundledAssetGroupSchema
   m_EditorClassIdentifier: 
-  m_Group: {fileID: 11400000, guid: 30f1ec4a0d3c9473d9b32a1b02474847, type: 2}
+  m_Group: {fileID: 11400000, guid: 1561a30b342064fa4896f0bfbf686fcb, type: 2}
   m_InternalBundleIdMode: 1
   m_Compression: 1
   m_IncludeAddressInCatalog: 1

+ 1 - 1
src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset

@@ -12,5 +12,5 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
   m_Name: Default Local Group_ContentUpdateGroupSchema
   m_EditorClassIdentifier: 
-  m_Group: {fileID: 11400000, guid: 30f1ec4a0d3c9473d9b32a1b02474847, type: 2}
+  m_Group: {fileID: 11400000, guid: 1561a30b342064fa4896f0bfbf686fcb, type: 2}
   m_StaticContent: 0

+ 1 - 1
src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset

@@ -12,4 +12,4 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: 3a189bb168d8d90478a09ea08c2f3d72, type: 3}
   m_Name: DefaultObject
   m_EditorClassIdentifier: 
-  m_AddressableAssetSettingsGuid: f1d773be05107486d85b5a324c1c2fb0
+  m_AddressableAssetSettingsGuid: a0f4200005a8343fa9cfda7e23be8a93

+ 1 - 1
src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset.meta

@@ -2,7 +2,7 @@ fileFormatVersion: 2
 guid: abb3d975b60334895afa04bc9ef91e68
 NativeFormatImporter:
   externalObjects: {}
-  mainObjectFileID: 0
+  mainObjectFileID: 11400000
   userData: 
   assetBundleName: 
   assetBundleVariant: 

+ 98 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs

@@ -0,0 +1,98 @@
+using Lua.CodeAnalysis.Compilation;
+using Lua.Runtime;
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+using UnityEditor;
+
+namespace Lua.Unity.Editor
+{
+    [CustomEditor(typeof(LuacAsset))]
+    public sealed class LuacAssetEditor : UnityEditor.Editor
+    {
+        LuacAsset asset;
+        static StringBuilder sb = new StringBuilder();
+        private byte[] bytes;
+        string prototypeCacheString;
+
+        public override void OnInspectorGUI()
+        {
+            if (asset == null) asset = (LuacAsset)serializedObject.targetObject;
+            if (bytes == null || !asset.bytes.AsSpan().SequenceEqual(bytes))
+            {
+                var prototype = Prototype.FromByteCode(asset.bytes.AsSpan(), asset.name);
+                if (sb == null)
+                    sb = new StringBuilder();
+                sb.Clear();
+                DebugChunk(sb, prototype, 0, 0);
+                prototypeCacheString = sb.ToString();
+                bytes = asset.bytes;
+            }
+
+            using (new EditorGUI.IndentLevelScope(-1))
+            {
+                EditorGUILayout.TextArea(prototypeCacheString);
+            }
+        }
+
+        static void DebugChunk(StringBuilder builder, Prototype chunk, int nestCount, int id)
+        {
+            void AppendLine(string line)
+            {
+                for (int i = 0; i < nestCount; i++)
+                {
+                    builder.Append("    ");
+                }
+
+                builder.AppendLine(line);
+            }
+
+            if (nestCount == 0)
+                AppendLine($"Chunk :{chunk.ChunkName}");
+            else AppendLine("[" + nestCount + "," + id + "]");
+            AppendLine($"Parameters:{chunk.ParameterCount}");
+
+            AppendLine("Code -------------------------------------");
+            var index = 0;
+            foreach (var inst in chunk.Code)
+            {
+                AppendLine($"[{index}]\t{chunk.LineInfo[index]}\t\t{inst}");
+                index++;
+            }
+
+            AppendLine("LocalVariables ---------------------------");
+            index = 0;
+            foreach (var local in chunk.LocalVariables)
+            {
+                AppendLine($"[{index}]\t{local.Name}\t{local.StartPc}\t{local.EndPc}");
+                index++;
+            }
+
+            AppendLine("Constants ---------------------------------");
+            index = 0;
+            foreach (var constant in chunk.Constants.ToArray())
+            {
+                AppendLine($"[{index}]\t{Regex.Escape(constant.ToString())}");
+                index++;
+            }
+
+            AppendLine("UpValues -----------------------------------");
+            index = 0;
+            foreach (var upValue in chunk.UpValues.ToArray())
+            {
+                AppendLine($"[{index}]\t{upValue.Name}\t{(upValue.IsLocal ? 1 : 0)}\t{upValue.Index}");
+                index++;
+            }
+
+            builder.AppendLine();
+
+            var chunkId = 0;
+
+            foreach (var localChunk in chunk.ChildPrototypes)
+            {
+                DebugChunk(builder, localChunk, nestCount + 1, chunkId);
+                chunkId++;
+            }
+        }
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7cb5a3afb8f64363995fb96ba5587c8f
+timeCreated: 1749868556

+ 32 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs

@@ -0,0 +1,32 @@
+using System.IO;
+using System.Runtime.CompilerServices;
+using UnityEditor;
+using UnityEditor.AssetImporters;
+using UnityEngine;
+
+namespace Lua.Unity.Editor
+{
+    [ScriptedImporter(1, "luac")]
+    public sealed class LuacImporter : ScriptedImporter
+    {
+        static Texture2D icon;
+        public override void OnImportAsset(AssetImportContext ctx)
+        {
+            var bytes = File.ReadAllBytes(ctx.assetPath);
+            var asset = ScriptableObject.CreateInstance<LuacAsset>();
+            if (icon == null)
+            {
+                icon = Resources.Load<Texture2D>("LuaAssetIcon");
+                if (icon == null)
+                {
+                    Debug.LogWarning("LuaAssetIcon not found in Resources. Using default icon.");
+                    icon = EditorGUIUtility.IconContent("ScriptableObject Icon").image as Texture2D;
+                }
+            }
+            EditorGUIUtility.SetIconForObject (asset,icon );
+            asset.bytes = bytes;
+            ctx.AddObjectToAsset("Main", asset);
+            ctx.SetMainObject(asset);
+        }
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d8f9fbd84f8449649b27628ba85a3217
+timeCreated: 1749868371

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/Resources.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1d9c4ac6bedc4e7d8f4d5b10742b8e85
+timeCreated: 1749868505

+ 0 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/LuaAssetIcon.png → src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png


+ 0 - 0
src/Lua.Unity/Assets/Lua.Unity/Editor/LuaAssetIcon.png.meta → src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png.meta


+ 4 - 3
src/Lua.Unity/Assets/Lua.Unity/Runtime/AddressablesModuleLoader.cs

@@ -12,7 +12,7 @@ namespace Lua.Unity
 {
     public sealed class AddressablesModuleLoader : ILuaModuleLoader
     {
-        readonly Dictionary<string, LuaAsset> cache = new();
+        readonly Dictionary<string, LuaAssetBase> cache = new();
 
         public bool Exists(string moduleName)
         {
@@ -26,7 +26,8 @@ namespace Lua.Unity
         {
             if (cache.TryGetValue(moduleName, out var asset))
             {
-                return new LuaModule(moduleName, asset.text);
+                
+                return asset .GetModule( moduleName);
             }
 
             var asyncOperation = Addressables.LoadAssetAsync<LuaAsset>(moduleName);
@@ -38,7 +39,7 @@ namespace Lua.Unity
             }
 
             cache.Add(moduleName, asset);
-            return new LuaModule(moduleName, asset.text);
+            return asset .GetModule( moduleName);
         }
     }
     internal static class AsyncOperationHandleExtensions

+ 15 - 1
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAsset.cs

@@ -1,10 +1,24 @@
 using UnityEngine;
 
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
 namespace Lua.Unity
 {
-    public sealed class LuaAsset : ScriptableObject
+    public sealed class LuaAsset : LuaAssetBase
     {
         [SerializeField] internal string text;
         public string Text => text;
+
+        public override LuaModule GetModule(string searchedName)
+        {
+#if UNITY_EDITOR
+            var moduleName = "@"+AssetDatabase.GetAssetPath(this);
+#else
+           var  moduleName =  $"@{searchedName}.lua";
+#endif
+            return new LuaModule(moduleName, text);
+        }
     }
 }

+ 10 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs

@@ -0,0 +1,10 @@
+using UnityEngine;
+
+namespace Lua.Unity
+{
+
+    public abstract class LuaAssetBase : ScriptableObject
+    {
+        public abstract LuaModule GetModule(string searchedName);
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 68f5e96db703412a9d5023a6874176c9
+timeCreated: 1749868448

+ 30 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs

@@ -0,0 +1,30 @@
+using Lua.Runtime;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Lua.Unity
+{
+    public static class LuaThreadAssetExtensions
+    {
+
+        public static ValueTask<int> ExecuteAsync(this LuaState state, LuaAssetBase luaAssetBase, string name, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
+        {
+            if (luaAssetBase == null)
+            {
+                throw new ArgumentNullException(nameof(luaAssetBase));
+            }
+
+            var module = luaAssetBase.GetModule(name);
+            var closure = module.Type == LuaModuleType.Bytes
+                ? state.Load(module.ReadBytes(), module.Name)
+                : state.Load(module.ReadText(), module.Name);
+            return state.ExecuteAsync(closure, buffer, cancellationToken);
+        }
+
+        public static ValueTask<LuaValue[]> ExecuteAsync(this LuaState state, LuaAssetBase luaAssetBase, string name, CancellationToken cancellationToken = default)
+        {
+            return state.ExecuteAsync(luaAssetBase, name, cancellationToken);
+        }
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f82c28987dc44385a582e3eb37501ee8
+timeCreated: 1750856924

+ 15 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs

@@ -0,0 +1,15 @@
+using UnityEngine;
+
+namespace Lua.Unity
+{
+    public sealed class LuacAsset : LuaAssetBase
+    {
+        [HideInInspector]
+        [SerializeField] internal byte[] bytes;
+        public byte[] Bytes => bytes;
+        public override LuaModule GetModule(string searchedName)
+        {
+             return new LuaModule(searchedName,bytes);
+        }
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b8303e0c721c4fc3829ce6b681f6dae0
+timeCreated: 1749869951

+ 7 - 8
src/Lua.Unity/Assets/Lua.Unity/Runtime/ResourcesModuleLoader.cs

@@ -4,18 +4,18 @@ using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading.Tasks;
 using UnityEngine;
-
 namespace Lua.Unity
 {
     public sealed class ResourcesModuleLoader : ILuaModuleLoader
     {
-        readonly Dictionary<string, LuaAsset> cache = new();
+        readonly Dictionary<string, LuaAssetBase> cache = new();
 
         public bool Exists(string moduleName)
         {
+            Debug.Log(moduleName);
             if (cache.TryGetValue(moduleName, out _)) return true;
 
-            var asset = Resources.Load<LuaAsset>(moduleName);
+            var asset = Resources.Load<LuaAssetBase>(moduleName);
             if (asset == null) return false;
 
             cache.Add(moduleName, asset);
@@ -26,10 +26,10 @@ namespace Lua.Unity
         {
             if (cache.TryGetValue(moduleName, out var asset))
             {
-                return new LuaModule(moduleName, asset.text);
+                return asset.GetModule(moduleName);
             }
 
-            var request = Resources.LoadAsync<LuaAsset>(moduleName);
+            var request = Resources.LoadAsync<LuaAssetBase>(moduleName);
             await request;
 
             if (request.asset == null)
@@ -37,10 +37,9 @@ namespace Lua.Unity
                 throw new LuaModuleNotFoundException(moduleName);
             }
 
-            asset = (LuaAsset)request.asset;
+            asset = (LuaAssetBase)request.asset;
             cache.Add(moduleName, asset);
-            return new LuaModule(moduleName, asset.text);
-        }
+            return asset.GetModule(moduleName);        }
     }
 
 #if !UNITY_2023_1_OR_NEWER

+ 48 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs

@@ -0,0 +1,48 @@
+using Lua.Platforms;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Lua.Unity
+{
+    public class UnityApplicationOsEnvironment : ILuaOsEnvironment
+    {
+        public UnityApplicationOsEnvironment(Dictionary<string, string> environmentVariables = null,bool allowToQuitOnExitCall = false)
+        {
+            EnvironmentVariables = environmentVariables ?? new Dictionary<string, string>();
+            AllowToQuitOnExitCall = allowToQuitOnExitCall;
+        }
+
+
+        public bool AllowToQuitOnExitCall { get; }
+        public Dictionary<string, string> EnvironmentVariables { get; }
+
+        public string GetEnvironmentVariable(string name)
+        {
+            if (EnvironmentVariables.TryGetValue(name, out var value))
+            {
+                return value;
+            }
+
+            return null;
+        }
+
+        public ValueTask Exit(int exitCode, CancellationToken cancellationToken)
+        {
+            if (AllowToQuitOnExitCall)
+            {
+                Application.Quit(exitCode);
+                throw new OperationCanceledException();
+            }
+            else
+            {
+                // If quitting is not allowed, we can just throw an exception or log a message.
+                throw new InvalidOperationException("Application exit is not allowed in this environment.");
+            }
+        }
+
+        public double GetTotalProcessorTime() => Time.time;
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2cf920ff7f0f4cff9b392d71c4656d6e
+timeCreated: 1749869194

+ 77 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs

@@ -0,0 +1,77 @@
+using Lua.IO;
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Lua.Unity
+{
+    public class UnityStandardIO : ILuaStandardIO
+    {
+        public UnityStandardIO(ILuaStream input = null)
+        {
+            if (input != null)
+            {
+                Input = input;
+            }
+            else
+            {
+                Input = new DummyInputStream();
+            }
+        }
+
+        public ILuaStream Input { get; }
+        public ILuaStream Output { get; } = new DebugLogStream(false);
+        public ILuaStream Error { get; } = new DebugLogStream(true);
+    }
+
+    internal class DummyInputStream : ILuaStream
+    {
+        public bool IsOpen { get; } = true;
+        public LuaFileOpenMode Mode => LuaFileOpenMode.Read;
+
+        public void Dispose() { }
+    }
+
+    public class DebugLogStream : ILuaStream
+    {
+        public DebugLogStream(bool isError = false)
+        {
+            IsError = isError;
+        }
+
+        public bool IsError { get; } = false;
+        public bool IsOpen { get; } = true;
+        public LuaFileOpenMode Mode => LuaFileOpenMode.Write;
+
+        private readonly StringBuilder stringBuilder = new();
+
+        ValueTask ILuaStream.WriteAsync(ReadOnlyMemory<char> content, CancellationToken cancellationToken)
+        {
+            stringBuilder.Append(content.Span);
+            return default;
+        }
+
+        ValueTask ILuaStream.FlushAsync(CancellationToken cancellationToken)
+        {
+            if (stringBuilder.Length > 0)
+            {
+                var message = stringBuilder.ToString();
+                if (IsError)
+                    UnityEngine.Debug.LogError(message);
+                else
+                    UnityEngine.Debug.Log(message);
+                stringBuilder.Clear();
+            }
+
+            return default;
+        }
+
+        public ValueTask Close(CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException("DebugLogStream cannot be closed.");
+        }
+
+        public void Dispose() { }
+    }
+}

+ 3 - 0
src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 8ba8000b21dc41c4a80569aa46d8a7be
+timeCreated: 1749868964

+ 1 - 0
src/Lua.Unity/Assets/Sandbox/Resources/foo.lua

@@ -4,4 +4,5 @@ t.greet = function()
     print("Foo: Hello!")
 end
 
+print(debug.traceback())
 return t

BIN
src/Lua.Unity/Assets/Sandbox/Resources/test.luac


+ 1 - 1
src/Lua.Unity/Assets/Sandbox/SampleScene.unity

@@ -452,7 +452,7 @@ MonoBehaviour:
   m_GameObject: {fileID: 948832268}
   m_Enabled: 1
   m_EditorHideFlags: 0
-  m_Script: {fileID: 11500000, guid: c59c64163ef484bb6a193121e8fc388c, type: 3}
+  m_Script: {fileID: 11500000, guid: 52a3185e7d5334f5e9b9e6797db6b6d8, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
 --- !u!4 &948832270

+ 47 - 3
src/Lua.Unity/Assets/Sandbox/Sandbox.cs

@@ -1,18 +1,28 @@
 using System;
 using Lua;
+using Lua.IO;
 using Lua.Loaders;
+using Lua.Platforms;
 using Lua.Standard;
 using Lua.Unity;
+using System.Collections.Generic;
+using System.Linq;
 using UnityEngine;
 
 public class Sandbox : MonoBehaviour
 {
     async void Start()
     {
-        var state = LuaState.Create();
+        
+        var state = LuaState.Create( new LuaPlatform(
+            FileSystem: new FileSystem(Application.streamingAssetsPath),
+            OsEnvironment: new UnityApplicationOsEnvironment(),
+            StandardIO: new UnityStandardIO(),
+            TimeProvider: TimeProvider.System
+        ));
         state.ModuleLoader = CompositeModuleLoader.Create(new AddressablesModuleLoader(), new ResourcesModuleLoader());
         state.OpenStandardLibraries();
-        state.Environment["print"] = new LuaFunction("print", (context, buffer, ct) =>
+        state.Environment["print"] = new LuaFunction("print", (context, ct) =>
         {
             Debug.Log(context.GetArgument<string>(0));
             return new(0);
@@ -25,10 +35,12 @@ public class Sandbox : MonoBehaviour
 print('test start')
 local foo = require 'foo'
 foo.greet()
-
 local bar = require 'bar'
 bar.greet()
+require 'test'
 
+local s =require 'streaming'
+s.f()
 ", cancellationToken: destroyCancellationToken);
         }
         catch (Exception ex)
@@ -36,4 +48,36 @@ bar.greet()
             Debug.LogException(ex);
         }
     }
+    
+    MeshTopology[ ] topologies = Enum.GetValues(typeof(MeshTopology)) as MeshTopology[];
+    public bool ContainsTriangle;
+
+    void Update()
+    {
+        if (Input.GetKeyDown(KeyCode.Space))
+        {
+            ContainsTriangle=(topologies.Contains( MeshTopology.Points));
+            
+            Debug.Break();
+        }
+        
+        if (Input.GetKeyDown(KeyCode.A))
+        {
+            ContainsTriangle=(ContainsInArray(topologies, MeshTopology.Points));
+            
+            Debug.Break();
+        }
+    }
+    
+    bool ContainsInArray<T>(T[] array, T value)
+    {
+        foreach (var item in array)
+        {
+            if (EqualityComparer<T>.Default.Equals(item, value))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
 }

+ 1 - 0
src/Lua.Unity/Assets/Sandbox/bar.lua

@@ -2,6 +2,7 @@ local t = {}
 
 t.greet = function()
     print("Bar: Hello!")
+    print(debug.traceback())
 end
 
 return t

+ 1 - 0
src/Lua.Unity/Assets/StreamingAssets/streaming.lua

@@ -0,0 +1 @@
+return { f= function()print("loaded from streaming.lua")end }

+ 3 - 0
src/Lua.Unity/Assets/StreamingAssets/streaming.lua.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f05262e5f419479c940d5486b1c58a00
+timeCreated: 1749995150

+ 1 - 0
src/Lua.Unity/Packages/manifest.json

@@ -1,6 +1,7 @@
 {
   "dependencies": {
     "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
+    "com.nuskey8.lua.unity.internal": "file:../../Lua/bin/Debug/netstandard2.1",
     "com.unity.addressables": "2.2.2",
     "com.unity.ai.navigation": "2.0.4",
     "com.unity.collab-proxy": "2.6.0",

+ 0 - 8
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 416b07e9204db499d88a21b05d4165c9
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/.signature.p7s


BIN
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png


+ 0 - 169
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png.meta

@@ -1,169 +0,0 @@
-fileFormatVersion: 2
-guid: 306665d22a68b49868b3a2bca04fbb1a
-TextureImporter:
-  internalIDToNameTable: []
-  externalObjects: {}
-  serializedVersion: 13
-  mipmaps:
-    mipMapMode: 0
-    enableMipMap: 1
-    sRGBTexture: 1
-    linearTexture: 0
-    fadeOut: 0
-    borderMipMap: 0
-    mipMapsPreserveCoverage: 0
-    alphaTestReferenceValue: 0.5
-    mipMapFadeDistanceStart: 1
-    mipMapFadeDistanceEnd: 3
-  bumpmap:
-    convertToNormalMap: 0
-    externalNormalMap: 0
-    heightScale: 0.25
-    normalMapFilter: 0
-    flipGreenChannel: 0
-  isReadable: 0
-  streamingMipmaps: 0
-  streamingMipmapsPriority: 0
-  vTOnly: 0
-  ignoreMipmapLimit: 0
-  grayScaleToAlpha: 0
-  generateCubemap: 6
-  cubemapConvolution: 0
-  seamlessCubemap: 0
-  textureFormat: 1
-  maxTextureSize: 2048
-  textureSettings:
-    serializedVersion: 2
-    filterMode: 1
-    aniso: 1
-    mipBias: 0
-    wrapU: 0
-    wrapV: 0
-    wrapW: 0
-  nPOTScale: 1
-  lightmap: 0
-  compressionQuality: 50
-  spriteMode: 0
-  spriteExtrude: 1
-  spriteMeshType: 1
-  alignment: 0
-  spritePivot: {x: 0.5, y: 0.5}
-  spritePixelsToUnits: 100
-  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
-  spriteGenerateFallbackPhysicsShape: 1
-  alphaUsage: 1
-  alphaIsTransparency: 0
-  spriteTessellationDetail: -1
-  textureType: 0
-  textureShape: 1
-  singleChannelComponent: 0
-  flipbookRows: 1
-  flipbookColumns: 1
-  maxTextureSizeSet: 0
-  compressionQualitySet: 0
-  textureFormatSet: 0
-  ignorePngGamma: 0
-  applyGammaDecoding: 0
-  swizzle: 50462976
-  cookieLightType: 0
-  platformSettings:
-  - serializedVersion: 4
-    buildTarget: DefaultTexturePlatform
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    ignorePlatformSupport: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 4
-    buildTarget: WebGL
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    ignorePlatformSupport: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 4
-    buildTarget: Win64
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    ignorePlatformSupport: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 4
-    buildTarget: iOS
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    ignorePlatformSupport: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 4
-    buildTarget: Standalone
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    ignorePlatformSupport: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 4
-    buildTarget: Android
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    ignorePlatformSupport: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  spriteSheet:
-    serializedVersion: 2
-    sprites: []
-    outline: []
-    customData: 
-    physicsShape: []
-    bones: []
-    spriteID: 
-    internalID: 0
-    vertices: []
-    indices: 
-    edges: []
-    weights: []
-    secondaryTextures: []
-    spriteCustomMetadata:
-      entries: []
-    nameFileIdTable: {}
-  mipmapLimitGroupName: 
-  pSDRemoveMatte: 0
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 24
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec

@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
-  <metadata>
-    <id>LuaCSharp</id>
-    <version>0.3.1</version>
-    <authors>Annulus Games</authors>
-    <license type="expression">MIT</license>
-    <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
-    <icon>Icon.png</icon>
-    <readme>README.md</readme>
-    <projectUrl>https://github.com/AnnulusGames/Lua-CSharp</projectUrl>
-    <description>High performance Lua interpreter implemented in C# for .NET and Unity</description>
-    <copyright>© Annulus Games</copyright>
-    <tags>lua interpreter</tags>
-    <repository type="git" url="https://github.com/AnnulusGames/Lua-CSharp" commit="68c7f5f1e09d07e1f2d6327e95730006ae444a46" />
-    <dependencies>
-      <group targetFramework="net6.0" />
-      <group targetFramework="net8.0" />
-      <group targetFramework=".NETStandard2.1">
-        <dependency id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" exclude="Build,Analyzers" />
-      </group>
-    </dependencies>
-  </metadata>
-</package>

+ 0 - 7
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec.meta

@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 9852f8b1690074e67a6420f320832008
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 519
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md

@@ -1,519 +0,0 @@
-# Lua-CSharp
-
-High performance Lua interpreter implemented in C# for .NET and Unity
-
-![img](docs/Header.png)
-
-[![NuGet](https://img.shields.io/nuget/v/LuaCSharp.svg)](https://www.nuget.org/packages/LuaCSharp)
-[![Releases](https://img.shields.io/github/release/AnnulusGames/Lua-CSharp.svg)](https://github.com/AnnulusGames/Lua-CSharp/releases)
-[![license](https://img.shields.io/badge/LICENSE-MIT-green.svg)](LICENSE)
-
-English | [日本語](README_JA.md)
-
-## Overview
-
-Lua-CSharp is a library that provides a Lua interpreter implemented in C#. By integrating Lua-CSharp, you can easily embed Lua scripts into your .NET applications.
-
-Lua-CSharp leverages the latest C# features, designed with low allocation and high performance in mind. It is optimized to deliver maximum performance when used for interoperation between C# and Lua in C# applications. Below is a benchmark comparison with [MoonSharp](https://github.com/moonsharp-devs/moonsharp/) and [NLua](https://github.com/NLua/NLua):
-
-![img](docs/Benchmark.png)
-
-MoonSharp generally provides good speed but incurs significant allocations due to its design. NLua, being a C-binding implementation, is fast, but introduces substantial overhead when interacting with the C# layer. Lua-CSharp, fully implemented in C#, allows for seamless interaction with C# code without additional overhead. Moreover, it operates reliably in AOT environments since it does not rely on IL generation.
-
-## Features
-
-* Lua 5.2 interpreter implemented in C#
-* Easy-to-use API integrated with async/await
-* Support for exception handling with try-catch
-* High-performance implementation utilizing modern C#
-* Unity support (works with both Mono and IL2CPP)
-
-## Installation
-
-### NuGet packages
-
-To use Lua-CSharp, .NET Standard 2.1 or higher is required. The package can be obtained from NuGet.
-
-### .NET CLI
-
-```ps1
-dotnet add package LuaCSharp
-```
-
-### Package Manager
-
-```ps1
-Install-Package LuaCSharp
-```
-
-### Unity
-
-Lua-CSharp can also be used in Unity (works with both Mono and IL2CPP).
-
-### Requirements
-
-* Unity 2021.3 or higher
-
-### Installation
-
-1. Install [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity).
-2. Open the NuGet window by going to `NuGet > Manage NuGet Packages`, search for the `LuaCSharp` package, and install it.
-
-## Quick Start
-
-By using the `LuaState` class, you can execute Lua scripts from C#. Below is a sample code that evaluates a simple calculation written in Lua.
-
-```cs
-using Lua;
-
-// Create a LuaState
-var state = LuaState.Create();
-
-// Execute a Lua script string with DoStringAsync
-var results = await state.DoStringAsync("return 1 + 1");
-
-// 2
-Console.WriteLine(results[0]);
-```
-
-> [!WARNING]
-> `LuaState` is not thread-safe. Do not access it from multiple threads simultaneously.
-
-## LuaValue
-
-Values in Lua scripts are represented by the `LuaValue` type. The value of a `LuaValue` can be read using `TryRead<T>(out T value)` or `Read<T>()`.
-
-```cs
-var results = await state.DoStringAsync("return 1 + 1");
-
-// double
-var value = results[0].Read<double>();
-```
-
-You can also get the type of the value from the `Type` property.
-
-```cs
-var isNil = results[0].Type == LuaValueType.Nil;
-```
-
-Below is a table showing the type mapping between Lua and C#.
-
-| Lua        | C#                |
-| ---------- | ----------------- |
-| `nil`      | `LuaValue.Nil`    |
-| `boolean`  | `bool`            |
-| `string`   | `string`          |
-| `number`   | `double`, `float` |
-| `table`    | `LuaTable`        |
-| `function` | `LuaFunction`     |
-| `userdata` | `LuaUserData`     |
-| `thread`   | `LuaThread`       |
-
-When creating a `LuaValue` from the C# side, compatible types are implicitly converted into `LuaValue`.
-
-```cs
-LuaValue value;
-value = 1.2;           // double   ->  LuaValue
-value = "foo";         // string   ->  LuaValue
-value = new LuaTable() // LuaTable ->  LuaValue
-```
-
-## LuaTable
-
-Lua tables are represented by the `LuaTable` type. They can be used similarly to `LuaValue[]` or `Dictionary<LuaValue, LuaValue>`.
-
-```cs
-// Create a table in Lua
-var results = await state.DoStringAsync("return { a = 1, b = 2, c = 3 }");
-var table1 = results[0].Read<LuaTable>();
-
-// 1
-Console.WriteLine(table1["a"]);
-
-// Create a table in C#
-results = await state.DoStringAsync("return { 1, 2, 3 }");
-var table2 = results[0].Read<LuaTable>();
-
-// 1 (Note: Lua arrays are 1-indexed)
-Console.WriteLine(table2[1]);
-```
-
-## Global Environment
-
-You can access Lua's global environment through `state.Environment`. This table allows for easy value exchange between Lua and C#.
-
-```cs
-// Set a = 10
-state.Environment["a"] = 10;
-
-var results = await state.DoStringAsync("return a");
-
-// 10
-Console.WriteLine(results[0]);
-```
-
-## Standard Libraries
-
-You can use Lua's standard libraries as well. By calling `state.OpenStandardLibraries()`, the standard library tables are added to the `LuaState`.
-
-```cs
-using Lua;
-using Lua.Standard;
-
-var state = LuaState.Create();
-
-// Add standard libraries
-state.OpenStandardLibraries();
-
-var results = await state.DoStringAsync("return math.pi");
-Console.WriteLine(results[0]); // 3.141592653589793
-```
-
-For more details on standard libraries, refer to the [Lua official manual](https://www.lua.org/manual/5.2/manual.html#6).
-
-> [!WARNING]
-> Lua-CSharp does not support all functions of the standard libraries. For details, refer to the [Compatibility](#compatibility) section.
-
-## Functions
-
-Lua functions are represented by the `LuaFunction` type. With `LuaFunction`, you can call Lua functions from C#, or define functions in C# that can be called from Lua.
-
-### Calling Lua Functions from C#
-
-```lua
--- lua2cs.lua
-
-local function add(a, b)
-    return a + b
-end
-
-return add;
-```
-
-```cs
-var results = await state.DoFileAsync("lua2cs.lua");
-var func = results[0].Read<LuaFunction>();
-
-// Execute the function with arguments
-var funcResults = await func.InvokeAsync(state, new[] { 1, 2 });
-
-// 3
-Console.WriteLine(funcResults[0]);
-```
-
-### Calling C# Functions from Lua
-
-It is possible to create a `LuaFunction` from a lambda expression.
-
-```cs
-// Add the function to the global environment
-state.Environment["add"] = new LuaFunction((context, buffer, ct) =>
-{
-    // Get the arguments using context.GetArgument<T>()
-    var arg0 = context.GetArgument<double>(0);
-    var arg1 = context.GetArgument<double>(1);
-
-    // Write the return value to the buffer
-    buffer.Span[0] = arg0 + arg1;
-
-    // Return the number of values
-    return new(1);
-});
-
-// Execute a Lua script
-var results = await state.DoFileAsync("cs2lua.lua");
-
-// 3
-Console.WriteLine(results[i]);
-```
-
-```lua
--- cs2lua.lua
-
-return add(1, 2)
-```
-
-> [!TIP]  
-> Defining functions with `LuaFunction` can be somewhat verbose. When adding multiple functions, it is recommended to use the Source Generator with the `[LuaObject]` attribute. For more details, see the [LuaObject](#luaobject) section.
-
-## Integration with async/await
-
-`LuaFunction` operates asynchronously. Therefore, you can define a function that waits for an operation in Lua, such as the example below:
-
-```cs
-// Define a function that waits for the given number of seconds using Task.Delay
-state.Environment["wait"] = new LuaFunction(async (context, buffer, ct) =>
-{
-    var sec = context.GetArgument<double>(0);
-    await Task.Delay(TimeSpan.FromSeconds(sec));
-    return 0;
-});
-
-await state.DoFileAsync("sample.lua");
-```
-
-```lua
--- sample.lua
-
-print "hello!"
-
-wait(1.0) -- wait 1 sec
-
-print "how are you?"
-
-wait(1.0) -- wait 1 sec
-
-print "goodbye!"
-```
-
-This code can resume the execution of the Lua script after waiting with await, as shown in the following figure. This is very useful when writing scripts to be incorporated into games.
-
-![img](docs/img1.png)
-
-## Coroutines
-
-Lua coroutines are represented by the `LuaThread` type.
-
-Coroutines can not only be used within Lua scripts, but you can also await Lua-created coroutines from C#.
-
-```lua
--- coroutine.lua
-
-local co = coroutine.create(function()
-    for i = 1, 10 do
-        print(i)
-        coroutine.yield()
-    end
-end)
-
-return co
-```
-
-```cs
-var results = await state.DoFileAsync("coroutine.lua");
-var co = results[0].Read<LuaThread>();
-
-for (int i = 0; i < 10; i++)
-{
-    var resumeResults = await co.ResumeAsync(state);
-
-    // Similar to coroutine.resume(), returns true on success and the return values afterward
-    // 1, 2, 3, 4, ...
-    Console.WriteLine(resumeResults[1]);
-}
-```
-
-## LuaObject
-
-By applying the `[LuaObject]` attribute, you can create custom classes that run within Lua. Adding this attribute to a class that you wish to use in Lua allows the Source Generator to automatically generate the code required for interaction from Lua.
-
-The following is an example implementation of a wrapper class for `System.Numerics.Vector3` that can be used in Lua:
-
-```cs
-using System.Numerics;
-using Lua;
-
-var state = LuaState.Create();
-
-// Add an instance of the defined LuaObject as a global variable
-// (Implicit conversion to LuaValue is automatically defined for classes with the LuaObject attribute)
-state.Environment["Vector3"] = new LuaVector3();
-
-await state.DoFileAsync("vector3_sample.lua");
-
-// Add LuaObject attribute and partial keyword
-[LuaObject]
-public partial class LuaVector3
-{
-    Vector3 vector;
-
-    // Add LuaMember attribute to members that will be used in Lua
-    // The argument specifies the name used in Lua (if omitted, the member name is used)
-    [LuaMember("x")]
-    public float X
-    {
-        get => vector.X;
-        set => vector = vector with { X = value };
-    }
-
-    [LuaMember("y")]
-    public float Y
-    {
-        get => vector.Y;
-        set => vector = vector with { Y = value };
-    }
-
-    [LuaMember("z")]
-    public float Z
-    {
-        get => vector.Z;
-        set => vector = vector with { Z = value };
-    }
-
-    // Static methods are treated as regular Lua functions
-    [LuaMember("create")]
-    public static LuaVector3 Create(float x, float y, float z)
-    {
-        return new LuaVector3()
-        {
-            vector = new Vector3(x, y, z)
-        };
-    }
-
-    // Instance methods implicitly receive the instance (this) as the first argument
-    // In Lua, this is accessed with instance:method() syntax
-    [LuaMember("normalized")]
-    public LuaVector3 Normalized()
-    {
-        return new LuaVector3()
-        {
-            vector = Vector3.Normalize(vector)
-        };
-    }
-}
-```
-
-```lua
--- vector3_sample.lua
-
-local v1 = Vector3.create(1, 2, 3)
--- 1  2  3
-print(v1.x, v1.y, v1.z)
-
-local v2 = v1:normalized()
--- 0.26726123690605164  0.5345224738121033  0.8017836809158325
-print(v2.x, v2.y, v2.z)
-```
-
-The types of fields/properties with the `[LuaMember]` attribute, as well as the argument and return types of methods, must be either `LuaValue` or convertible to/from `LuaValue`.
-
-Return types such as `void`, `Task/Task<T>`, `ValueTask/ValueTask<T>`, `UniTask/UniTask<T>`, and `Awaitable/Awaitable<T>` are also supported.
-
-If the type is not supported, the Source Generator will output a compile-time error.
-
-### LuaMetamethod
-
-By adding the `[LuaMetamethod]` attribute, you can designate a C# method to be used as a Lua metamethod.
-
-Here is an example that adds the `__add`, `__sub`, and `__tostring` metamethods to the `LuaVector3` class:
-
-```cs
-[LuaObject]
-public partial class LuaVector3
-{
-    // The previous implementation is omitted
-
-    [LuaMetamethod(LuaObjectMetamethod.Add)]
-    public static LuaVector3 Add(LuaVector3 a, LuaVector3 b)
-    {
-        return new LuaVector3()
-        {
-            vector = a.vector + b.vector
-        };
-    }
-    
-    [LuaMetamethod(LuaObjectMetamethod.Sub)]
-    public static LuaVector3 Sub(LuaVector3 a, LuaVector3 b)
-    {
-        return new LuaVector3()
-        {
-            vector = a.vector - b.vector
-        };
-    }
-
-    [LuaMetamethod(LuaObjectMetamethod.ToString)]
-    public override string ToString()
-    {
-        return vector.ToString();
-    }
-}
-```
-
-```lua
-local v1 = Vector3.create(1, 1, 1)
-local v2 = Vector3.create(2, 2, 2)
-
-print(v1) -- <1, 1, 1>
-print(v2) -- <2, 2, 2>
-
-print(v1 + v2) -- <3, 3, 3>
-print(v1 - v2) -- <-1, -1, -1>
-```
-
-> [!NOTE]  
-> `__index` and `__newindex` cannot be set as they are used internally by the code generated by `[LuaObject]`.
-
-## Module Loading
-
-In Lua, you can load modules using the `require` function. In regular Lua, modules are managed by searchers within the `package.searchers` function list. In Lua-CSharp, this is replaced by the `ILuaModuleLoader` interface.
-
-```cs
-public interface ILuaModuleLoader
-{
-    bool Exists(string moduleName);
-    ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default);
-}
-```
-
-You can set the `LuaState.ModuleLoader` to change how modules are loaded. By default, the `FileModuleLoader` is set to load modules from Lua files.
-
-You can also combine multiple loaders using `CompositeModuleLoader.Create(loader1, loader2, ...)`.
-
-```cs
-state.ModuleLoader = CompositeModuleLoader.Create(
-    new FileModuleLoader(),
-    new CustomModuleLoader()
-);
-```
-
-Loaded modules are cached in the `package.loaded` table, just like regular Lua. This can be accessed via `LuaState.LoadedModules`.
-
-## Exception Handling
-
-Lua script parsing errors and runtime exceptions throw exceptions that inherit from `LuaException`. You can catch these to handle errors during execution.
-
-```cs
-try
-{
-    await state.DoFileAsync("filename.lua");
-}
-catch (LuaParseException)
-{
-    // Handle parsing errors
-}
-catch (LuaRuntimeException)
-{
-    // Handle runtime exceptions
-}
-```
-
-## Compatibility
-
-Lua-CSharp is designed with integration into .NET in mind, so there are several differences from the C implementation.
-
-### Binary
-
-Lua-CSharp does not support Lua bytecode (tools like `luac` cannot be used). Only Lua source code can be executed.
-
-### Character Encoding
-
-The character encoding used in Lua-CSharp is UTF-16. Since standard Lua assumes a single-byte character encoding, string behavior differs significantly.
-
-For example, in regular Lua, the following code outputs `15`, but in Lua-CSharp, it outputs `5`.
-
-```lua
-local l = string.len("あいうえお")
-print(l)
-```
-
-All string library functions handle strings as UTF-16.
-
-### Garbage Collection
-
-Since Lua-CSharp is implemented in C#, it relies on .NET's garbage collector. As a result, memory management behavior differs from regular Lua.
-
-While `collectgarbage()` is available, it simply calls the corresponding .NET garbage collection method and may not exhibit the same behavior as C's Lua garbage collector.
-
-## License
-
-Lua-CSharp is licensed under the [MIT License](LICENSE).

+ 0 - 7
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md.meta

@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: ca852d91bba6d4f1bb3b51fa9102d995
-TextScriptImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 8
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 8d2121deb53d94ca2a244173798b5010
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 8
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: ebee2cd7f5c8c48e6b9592d28d5c0bb8
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 8
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: c490e8a3c6ca14ba2a2556f3ff493942
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll


+ 0 - 51
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll.meta

@@ -1,51 +0,0 @@
-fileFormatVersion: 2
-guid: 3d3d5d244d2704fde9e99cd90ebd083f
-labels:
-- NuGetForUnity
-- RoslynAnalyzer
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 3
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 1
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-    Any:
-      enabled: 0
-      settings:
-        'Exclude ': 0
-        Exclude Android: 0
-        Exclude CloudRendering: 0
-        Exclude EmbeddedLinux: 0
-        Exclude GameCoreScarlett: 0
-        Exclude GameCoreXboxOne: 0
-        Exclude Linux64: 0
-        Exclude OSXUniversal: 0
-        Exclude PS4: 0
-        Exclude PS5: 0
-        Exclude QNX: 0
-        Exclude ReservedCFE: 0
-        Exclude Switch: 0
-        Exclude VisionOS: 0
-        Exclude WebGL: 0
-        Exclude Win: 0
-        Exclude Win64: 0
-        Exclude WindowsStoreApps: 0
-        Exclude XboxOne: 0
-        Exclude iOS: 0
-        Exclude tvOS: 0
-    Editor:
-      enabled: 0
-      settings:
-        DefaultValueInitialized: true
-    WindowsStoreApps:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 8
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 712703d7889d34e3f80f7e11a115306e
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 8
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 6907bd5f6ac8a4472a0145d853426293
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll


+ 0 - 29
src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll.meta

@@ -1,29 +0,0 @@
-fileFormatVersion: 2
-guid: 867dfdc624be84676a592048e765a7ff
-labels:
-- NuGetForUnity
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 3
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 1
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-    Any:
-      enabled: 1
-      settings: {}
-    Editor:
-      enabled: 0
-      settings:
-        DefaultValueInitialized: true
-    WindowsStoreApps:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 3 - 2
src/Lua.Unity/Packages/nuget-packages/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="LuaCSharp" version="0.3.1" manuallyInstalled="true" />
-  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" />
+  <package id="Microsoft.Bcl.TimeProvider" version="8.0.0" manuallyInstalled="true" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" manuallyInstalled="true" />
 </packages>

+ 6 - 0
src/Lua.Unity/Packages/packages-lock.json

@@ -7,6 +7,12 @@
       "dependencies": {},
       "hash": "242f30ec55ef9c34f1e9953126e6e8350ec3f4c0"
     },
+    "com.nuskey8.lua.unity.internal": {
+      "version": "file:../../Lua/bin/Debug/netstandard2.1",
+      "depth": 0,
+      "source": "local",
+      "dependencies": {}
+    },
     "com.unity.addressables": {
       "version": "2.2.2",
       "depth": 0,

+ 1 - 1
src/Lua.Unity/ProjectSettings/EditorBuildSettings.asset

@@ -9,6 +9,6 @@ EditorBuildSettings:
     path: Assets/Sandbox/SampleScene.unity
     guid: 99c9720ab356a0642a771bea13969a05
   m_configObjects:
-    com.unity.addressableassets: {fileID: 11400000, guid: 5b124f60b731f48fba10f000d3390a42, type: 2}
+    com.unity.addressableassets: {fileID: 11400000, guid: abb3d975b60334895afa04bc9ef91e68, type: 2}
     com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3}
   m_UseUCBPForAssetBundles: 0

+ 52 - 0
src/Lua/CodeAnalysis/Compilation/BomUtility.cs

@@ -0,0 +1,52 @@
+using System.Text;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+static class BomUtility
+{
+    public static ReadOnlySpan<byte> BomUtf8 => [0xEF, 0xBB, 0xBF];
+
+    static ReadOnlySpan<byte> BomUtf16Little => [0xFF, 0xFE];
+
+    static ReadOnlySpan<byte> BomUtf16Big => [0xFE, 0xFF];
+
+    static ReadOnlySpan<byte> BomUtf32Little => [0xFF, 0xFE, 0x00, 0x00];
+
+    /// <summary>
+    ///  Removes the BOM from the beginning of the text and returns the encoding.
+    ///  Supported encodings are UTF-8, UTF-16 (little and big endian), and UTF-32 (little endian).
+    ///  Unknown BOMs are ignored, and the encoding is set to UTF-8 by default.
+    ///  </summary>
+    ///  <param name="text">The text to check for BOM.</param>
+    ///  <param name="encoding">The encoding of the text.</param>
+    ///  <returns>The text without the BOM.</returns>
+    public static ReadOnlySpan<byte> GetEncodingFromBytes(ReadOnlySpan<byte> text, out Encoding encoding)
+    {
+        if (text.StartsWith(BomUtf8))
+        {
+            encoding = Encoding.UTF8;
+            return text.Slice(BomUtf8.Length);
+        }
+
+        if (text.StartsWith(BomUtf16Little))
+        {
+            encoding = Encoding.Unicode;
+            return text.Slice(BomUtf16Little.Length);
+        }
+
+        if (text.StartsWith(BomUtf16Big))
+        {
+            encoding = Encoding.BigEndianUnicode;
+            return text.Slice(BomUtf16Big.Length);
+        }
+
+        if (text.StartsWith(BomUtf32Little))
+        {
+            encoding = Encoding.UTF32;
+            return text.Slice(BomUtf32Little.Length);
+        }
+
+        encoding = Encoding.UTF8;
+        return text;
+    }
+}

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

@@ -0,0 +1,131 @@
+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()
+    {
+        return Position >= length ? ('\0', false) : (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];
+
+    public ReadOnlySpan<char> Span => new(ptr, length);
+
+    public int Length => length;
+}
+
+unsafe struct AssignmentTarget(ref AssignmentTarget previous, ExprDesc exprDesc)
+{
+    public readonly AssignmentTarget* Previous = (AssignmentTarget*)Unsafe.AsPointer(ref previous);
+    public ExprDesc Description = exprDesc;
+}
+
+struct Label
+{
+    public string Name;
+    public int Pc, Line;
+    public int ActiveVariableCount;
+}
+
+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.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);
+    }
+}
+
+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()
+    {
+        return T != F;
+    }
+
+    public readonly bool IsNumeral()
+    {
+        return Kind == Kind.Number && T == Function.NoJump && F == Function.NoJump;
+    }
+
+    public readonly bool IsVariable()
+    {
+        return Kind is >= Kind.Local and <= Kind.Indexed;
+    }
+
+    public readonly bool HasMultipleReturns()
+    {
+        return Kind == Kind.Call || Kind == Kind.VarArg;
+    }
+}
+
+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; }
-    }
-}

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

@@ -0,0 +1,525 @@
+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)]
+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 = 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 = Tail)
+        {
+            LuaTail.CopyTo(new(tail, 6));
+        }
+    }
+
+    public void Validate(ReadOnlySpan<char> name)
+    {
+        fixed (byte* signature = Signature)
+        {
+            if (!LuaSignature.SequenceEqual(new(signature, 4)))
+            {
+                throw new LuaUndumpException($"{name.ToString()}: is not a precompiled chunk");
+            }
+        }
+
+        var major = Version >> 4;
+        var minor = Version & 0xF;
+        if (major != Constants.VersionMajor || minor != Constants.VersionMinor)
+        {
+            throw new LuaUndumpException($"{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 = Tail)
+        {
+            if (!LuaTail.SequenceEqual(new(tail, 6)))
+            {
+                goto ErrIncompatible;
+            }
+        }
+
+        return;
+    ErrIncompatible:
+        throw new LuaUndumpException($"{name.ToString()}: incompatible precompiled chunk");
+    }
+}
+
+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()
+    {
+        Header header = new(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.ChunkName);
+        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)
+    {
+        var 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);
+        }
+    }
+}
+
+unsafe ref struct UndumpState(ReadOnlySpan<byte> span, ReadOnlySpan<char> name, StringInternPool internPool)
+{
+    public ReadOnlySpan<byte> Unread = span;
+    bool otherEndian;
+    int pointerSize;
+    readonly ReadOnlySpan<char> name = name;
+
+    void Throw(string why)
+    {
+        throw new LuaUndumpException($"{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;
+        Span<byte> span = new(&i, sizeof(int));
+        Read(span);
+
+        if (otherEndian)
+        {
+            i = BinaryPrimitives.ReverseEndianness(i);
+        }
+
+        return i;
+    }
+
+    long ReadLong()
+    {
+        long i = 0;
+        Span<byte> span = new(&i, sizeof(long));
+        Read(span);
+
+        if (otherEndian)
+        {
+            i = BinaryPrimitives.ReverseEndianness(i);
+        }
+
+        return i;
+    }
+
+    double ReadDouble()
+    {
+        var i = ReadLong();
+
+        return *(double*)&i;
+    }
+
+    public Prototype Undump()
+    {
+        Header h = default;
+        Span<byte> span = new(&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();
+        var upValueCount = ReadInt();
+        Debug.Assert(upValueCount == upValues.Length, $"upvalue count mismatch: {upValueCount} != {upValues.Length}");
+        foreach (ref var desc in upValues.AsSpan())
+        {
+            var name = ReadString();
+            desc.Name = name;
+        }
+
+        return new(source, lineDefined, lastLineDefined, parameterCount, maxStackSize, isVarArg, constants, code, prototypes, lineInfo, localVariables, upValues);
+    }
+
+
+    void ReadInToIntSpan(Span<int> toWrite)
+    {
+        for (var 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);
+        char[]? charArrayPooled = null;
+        try
+        {
+            var span = arrayPooled.AsSpan(0, len);
+            Read(span);
+
+            var l = ReadByte();
+            Debug.Assert(l == 0);
+            var chars = len <= 128 ? stackalloc char[len * 2] : (charArrayPooled = ArrayPool<char>.Shared.Rent(len * 2));
+            var count = Encoding.UTF8.GetChars(span, chars);
+            return internPool.Intern(chars[..count]);
+        }
+        finally
+        {
+            ArrayPool<byte>.Shared.Return(arrayPooled);
+            if (charArrayPooled != null)
+            {
+                ArrayPool<char>.Shared.Return(charArrayPooled);
+            }
+        }
+    }
+
+    LuaValue[] ReadConstants()
+    {
+        var count = ReadInt();
+        var constants = new LuaValue[count];
+        for (var 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 (var i = 0; i < count; i++)
+        {
+            prototypes[i] = UndumpFunction();
+        }
+
+        return prototypes;
+    }
+
+    LocalVariable[] ReadLocalVariables()
+    {
+        var count = ReadInt();
+        var localVariables = new LocalVariable[count];
+        for (var i = 0; i < count; i++)
+        {
+            var name = ReadString();
+            var startPc = ReadInt();
+            var endPc = ReadInt();
+            localVariables[i] = new() { 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 (var i = 0; i < count; i++)
+        {
+            var isLocal = ReadBool();
+            var index = ReadByte();
+            upValues[i] = new() { IsLocal = isLocal, Index = index };
+        }
+
+        return upValues;
+    }
+}

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

@@ -0,0 +1,1633 @@
+using Lua.Internal;
+using Lua.Runtime;
+using System.Diagnostics;
+using Constants = Lua.Internal.Constants;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+using static Debug;
+using static Instruction;
+using static Constants;
+
+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;
+
+    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();
+        }
+
+        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)
+    {
+        return b == 0 ? 1 : 0;
+    }
+
+
+    public static ExprDesc MakeExpression(Kind kind, int info)
+    {
+        return new() { F = NoJump, T = NoJump, Kind = kind, Info = info };
+    }
+
+
+    public void SemanticError(string message)
+    {
+        P.Scanner.Token = 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)
+    {
+        return 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)
+    {
+        return Encode(CreateABx(op, a, bx));
+    }
+
+
+    public int EncodeAsBx(OpCode op, int a, int sbx)
+    {
+        return EncodeABx(op, a, sbx + MaxArgSBx);
+    }
+
+    public int EncodeExtraArg(int a)
+    {
+        return Encode(CreateAx(OpCode.ExtraArg, a));
+    }
+
+
+    public int EncodeConstant(int r, int constant)
+    {
+        if (constant <= MaxArgBx)
+        {
+            return EncodeABx(OpCode.LoadK, r, constant);
+        }
+
+        var pc = EncodeABx(OpCode.LoadK, r, 0);
+        EncodeExtraArg(constant);
+        return pc;
+    }
+
+    public ExprDesc EncodeString(string s)
+    {
+        return 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())
+        {
+            var loadFalse = NoJump;
+            var loadTrue = NoJump;
+            if (NeedValue(e.T) || NeedValue(e.F))
+            {
+                var jump = NoJump;
+                if (e.Kind != Kind.Jump)
+                {
+                    jump = Jump();
+                }
+
+                loadFalse = EncodeLabel(r, 0, 1);
+                loadTrue = EncodeLabel(r, 1, 0);
+                PatchToHere(jump);
+            }
+
+            var 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);
+        ExprDesc result = new() { 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;
+    }
+
+
+    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 - 1350
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -1,1350 +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);
-        }
-    }
-}

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

@@ -0,0 +1,988 @@
+using Lua.Internal;
+using Lua.Runtime;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using static System.Diagnostics.Debug;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+using static Function;
+using static Scanner;
+using static Constants;
+
+class Parser : IPoolNode<Parser>, IDisposable
+{
+    /// inline
+    internal Scanner Scanner;
+
+    internal int T => Scanner.Token.T;
+
+    internal bool TestNext(int token)
+    {
+        return 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;
+
+    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.Scanner = scanner;
+        return parser;
+    }
+
+    void IDisposable.Dispose()
+    {
+        Release();
+    }
+
+    public void Release()
+    {
+        Scanner.Buffer.Dispose();
+        Scanner = default;
+        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)
+        {
+            var 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()
+    {
+        return Function.EncodeString(CheckName());
+    }
+
+
+    public ExprDesc SingleVariable()
+    {
+        return Function.SingleVariable(CheckName());
+    }
+
+
+    public void LeaveLevel()
+    {
+        Scanner.L.CallCount--;
+    }
+
+
+    public TempBlock EnterLevel()
+    {
+        Scanner.L.CallCount++;
+        CheckLimit(Scanner.L.CallCount, MaxCallCount, "Go levels");
+        return new(Scanner.L);
+    }
+
+    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;
+
+        if (T == TkName && Scanner.LookAhead() == '=')
+        {
+            CheckLimit(h, MaxInt, "items in a constructor");
+            HashField(CheckNameAsExpression());
+        }
+        else if (T == '[')
+        {
+            HashField(Index());
+        }
+        else
+        {
+            e = Expression();
+            CheckLimit(a, MaxInt, "items in a constructor");
+            a++;
+            pending++;
+        }
+
+        return (a, h, pending, e);
+
+        void HashField(ExprDesc k)
+        {
+            h++;
+            CheckNext('=');
+            Function.FlushFieldToConstructor(tableRegister, freeRegisterCount, k, Expression);
+        }
+    }
+
+    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()
+    {
+        switch (T)
+        {
+            case '(':
+                var line = Scanner.LineNumber;
+                Next();
+                var e = Expression();
+                Scanner.CheckMatch(')', '(', line);
+                e = Function.DischargeVariables(e);
+                return e;
+            case TkName:
+                return SingleVariable();
+            default:
+                Scanner.SyntaxError("unexpected symbol");
+                return default;
+        }
+    }
+
+    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), 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)
+    {
+        return op switch
+        {
+            TkNot => OprNot,
+            '-' => OprMinus,
+            '#' => OprLength,
+            _ => OprNoUnary
+        };
+    }
+
+    public static int BinaryOp(int op)
+    {
+        return op switch
+        {
+            '+' => OprAdd,
+            '-' => OprSub,
+            '*' => OprMul,
+            '/' => OprDiv,
+            '%' => OprMod,
+            '^' => OprPow,
+            TkConcat => OprConcat,
+            TkNe => OprNE,
+            TkEq => OprEq,
+            '<' => OprLT,
+            TkLe => OprLE,
+            '>' => OprGT,
+            TkGe => OprGE,
+            TkAnd => OprAnd,
+            TkOr => OprOr,
+            _ => 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)
+    {
+        using var b = EnterLevel();
+        ExprDesc e;
+        var u = UnaryOp(T);
+        if (u != OprNoUnary)
+        {
+            var line = Scanner.LineNumber;
+            Next();
+            (e, _) = SubExpression(UnaryPriority);
+            e = Function.Prefix(u, e, line);
+        }
+        else
+        {
+            e = SimpleExpression();
+        }
+
+        var op = BinaryOp(T);
+        while (op != OprNoBinary && priority[op].Left > limit)
+        {
+            var line = Scanner.LineNumber;
+            Next();
+            e = Function.Infix(op, e);
+            var (e2, next) = SubExpression(priority[op].Right);
+            e = Function.Postfix(op, e, e2, line);
+            op = next;
+        }
+
+        return (e, op);
+    }
+
+    public ExprDesc Expression()
+    {
+        var (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 '['
+        var e = Function.ExpressionToValue(Expression());
+        CheckNext(']');
+        return e;
+    }
+
+    public void Assignment(AssignmentTarget t, int variableCount)
+    {
+        CheckCondition(t.Description.IsVariable(), "syntax error");
+        if (TestNext(','))
+        {
+            var e = SuffixedExpression();
+            if (e.Kind != Kind.Indexed)
+            {
+                Function.CheckConflict(t, e);
+            }
+
+            CheckLimit(variableCount + Scanner.L.CallCount, MaxCallCount, "Go levels");
+            Assignment(new(ref t, 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));
+    }
+
+    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)
+    {
+        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);
+        return;
+
+        void Expr()
+        {
+            var e = Function.ExpressionToNextRegister(Expression());
+            Assert(e.Kind == Kind.NonRelocatable);
+        }
+    }
+
+    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 is TkGoto or 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
+        {
+            ExprDesc e = default;
+            Function.AdjustAssignment(v, 0, e);
+        }
+
+        Function.AdjustLocalVariables(v);
+    }
+
+    public void ExpressionStatement()
+    {
+        var e = SuffixedExpression();
+        if (T == '=' || T == ',')
+        {
+            Assignment(new(ref Unsafe.NullRef<AssignmentTarget>(), 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;
+        using var _ = 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;
+    }
+
+
+    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 internPool = new StringInternPool(4);
+        using var p = Get(new()
+        {
+            R = r,
+            LineNumber = 1,
+            LastLine = 1,
+            LookAheadToken = new(0, TkEos),
+            L = l,
+            Source = name,
+            Buffer = new(r.Length),
+            StringPool = internPool
+        });
+        var f = Function.Get(p, PrototypeBuilder.Get(name));
+        p.Function = f;
+        f.Proto.IsVarArg = true;
+        f.Proto.LineDefined = 0;
+        p.MainFunction();
+        return f.Proto.CreatePrototypeAndRelease();
+    }
+
+    public static void Dump(Prototype prototype, IBufferWriter<byte> writer, bool useLittleEndian = true)
+    {
+        DumpState state = new(writer, useLittleEndian ^ BitConverter.IsLittleEndian);
+        state.Dump(prototype);
+    }
+
+    public static byte[] Dump(Prototype prototype, bool useLittleEndian = true)
+    {
+        ArrayBufferWriter<byte> writer = new();
+        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
+            };
+        }
+
+        using var internPool = new StringInternPool(4);
+        UndumpState state = new(span, name, internPool);
+        return state.Undump();
+    }
+}

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

@@ -0,0 +1,84 @@
+using Lua.Internal;
+using Lua.Runtime;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+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 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(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
+        }
+
+        Prototype p = new(Source, LineDefined, LastLineDefined, ParameterCount, MaxStackSize, IsVarArg, Constants.ToArray(), Code.ToArray(), protoTypes, LineInfo.ToArray(), LocalVariables.ToArray(), UpValues.ToArray());
+        Release();
+        return p;
+    }
+}

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