diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 00000000..ffdced0f
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,9 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(dotnet build:*)",
+ "Bash(dotnet run:*)"
+ ],
+ "deny": []
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index 4eb70ebd..dc9de310 100644
--- a/.editorconfig
+++ b/.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
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..48f1a012
--- /dev/null
+++ b/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
\ No newline at end of file
diff --git a/Lua.sln b/Lua.sln
deleted file mode 100644
index 1de7b2f0..00000000
--- a/Lua.sln
+++ /dev/null
@@ -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
diff --git a/Lua.slnx b/Lua.slnx
new file mode 100644
index 00000000..2a19ac9d
--- /dev/null
+++ b/Lua.slnx
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index d7cbe390..f28dd8fb 100644
--- a/README.md
+++ b/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).
\ No newline at end of file
diff --git a/sandbox/Benchmark/AddBenchmark.cs b/sandbox/Benchmark/AddBenchmark.cs
index 0c0a9221..99af7b39 100644
--- a/sandbox/Benchmark/AddBenchmark.cs
+++ b/sandbox/Benchmark/AddBenchmark.cs
@@ -22,10 +22,11 @@ public void Setup()
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(0) + context.GetArgument(1);
- return new(1);
+ var a = context.GetArgument(0);
+ var b = context.GetArgument(1);
+ return new(context.Return(a + b));
});
core.MoonSharpState.Globals["add"] = (Func)Add;
core.NLuaState.RegisterFunction("add", typeof(AddBenchmark).GetMethod(nameof(Add), BindingFlags.Static | BindingFlags.Public));
diff --git a/sandbox/Benchmark/Benchmark.csproj b/sandbox/Benchmark/Benchmark.csproj
index 6d963f3e..9c85d71b 100644
--- a/sandbox/Benchmark/Benchmark.csproj
+++ b/sandbox/Benchmark/Benchmark.csproj
@@ -1,20 +1,20 @@
-
- Exe
- net8.0
- enable
- enable
-
+
+ Exe
+ net8.0
+ enable
+ enable
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
diff --git a/sandbox/Benchmark/BenchmarkCore.cs b/sandbox/Benchmark/BenchmarkCore.cs
index 115bbd3e..4f7d7ee4 100644
--- a/sandbox/Benchmark/BenchmarkCore.cs
+++ b/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
diff --git a/sandbox/Benchmark/HookedBenchmark.cs b/sandbox/Benchmark/HookedBenchmark.cs
new file mode 100644
index 00000000..141d3f08
--- /dev/null
+++ b/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 Benchmark_LuaCSharp_String()
+ {
+ await core.LuaCSharpState.DoStringAsync(core.SourceText, buffer);
+ return buffer[0];
+ }
+}
\ No newline at end of file
diff --git a/sandbox/Benchmark/InterpreterSteps.cs b/sandbox/Benchmark/InterpreterSteps.cs
index 59ed0bb4..fc32ee9f 100644
--- a/sandbox/Benchmark/InterpreterSteps.cs
+++ b/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();
- 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 void CreateState()
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, []);
}
}
\ No newline at end of file
diff --git a/sandbox/Benchmark/NBodyBenchmark.cs b/sandbox/Benchmark/NBodyBenchmark.cs
index fb601453..930079aa 100644
--- a/sandbox/Benchmark/NBodyBenchmark.cs
+++ b/sandbox/Benchmark/NBodyBenchmark.cs
@@ -37,7 +37,7 @@ public DynValue Benchmark_MoonSharp_File()
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);
diff --git a/sandbox/Benchmark/hooked.lua b/sandbox/Benchmark/hooked.lua
new file mode 100644
index 00000000..1055c5b7
--- /dev/null
+++ b/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)
\ No newline at end of file
diff --git a/sandbox/ConsoleApp1/ConsoleApp1.csproj b/sandbox/ConsoleApp1/ConsoleApp1.csproj
index 8153f6a6..cf0ae141 100644
--- a/sandbox/ConsoleApp1/ConsoleApp1.csproj
+++ b/sandbox/ConsoleApp1/ConsoleApp1.csproj
@@ -1,21 +1,21 @@
-
-
-
- Analyzer
- false
-
-
+
+
+
+ Analyzer
+ false
+
+
-
- Exe
- net8.0
- enable
- enable
-
- false
- Generated
-
+
+ Exe
+ net9.0
+ 13
+ enable
+ enable
+ false
+ Generated
+
diff --git a/sandbox/ConsoleApp1/LVec3.cs b/sandbox/ConsoleApp1/LVec3.cs
index 040ae42d..9a97f843 100644
--- a/sandbox/ConsoleApp1/LVec3.cs
+++ b/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()
diff --git a/sandbox/ConsoleApp1/Program.cs b/sandbox/ConsoleApp1/Program.cs
index 10905080..f547886b 100644
--- a/sandbox/ConsoleApp1/Program.cs
+++ b/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(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 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();
+ }
}
\ No newline at end of file
diff --git a/sandbox/ConsoleApp1/test.lua b/sandbox/ConsoleApp1/test.lua
index b708245b..dce57bf6 100644
--- a/sandbox/ConsoleApp1/test.lua
+++ b/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[
+"
\ No newline at end of file
diff --git a/sandbox/ConsoleApp2/.gitignore b/sandbox/ConsoleApp2/.gitignore
new file mode 100644
index 00000000..5e1a3804
--- /dev/null
+++ b/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
diff --git a/sandbox/ConsoleApp2/ConsoleApp2.csproj b/sandbox/ConsoleApp2/ConsoleApp2.csproj
new file mode 100644
index 00000000..47978586
--- /dev/null
+++ b/sandbox/ConsoleApp2/ConsoleApp2.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net9.0
+ 13
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/sandbox/ConsoleApp2/Program.cs b/sandbox/ConsoleApp2/Program.cs
new file mode 100644
index 00000000..46878825
--- /dev/null
+++ b/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();
+ 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();
+ 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/sandbox/JitTest/.gitignore b/sandbox/JitTest/.gitignore
new file mode 100644
index 00000000..b34b42fc
--- /dev/null
+++ b/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/
diff --git a/sandbox/JitTest/JitTest.csproj b/sandbox/JitTest/JitTest.csproj
new file mode 100644
index 00000000..a6272705
--- /dev/null
+++ b/sandbox/JitTest/JitTest.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net9.0
+ 13
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/JitTest/Program.cs b/sandbox/JitTest/Program.cs
new file mode 100644
index 00000000..574ea406
--- /dev/null
+++ b/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);
+}
\ No newline at end of file
diff --git a/sandbox/JitTest/db.lua b/sandbox/JitTest/db.lua
new file mode 100644
index 00000000..e48b203e
--- /dev/null
+++ b/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"
diff --git a/sandbox/JitTest/events.lua b/sandbox/JitTest/events.lua
new file mode 100644
index 00000000..b3e5c412
--- /dev/null
+++ b/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 aOp(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
+
+
diff --git a/sandbox/JitTest/test.lua b/sandbox/JitTest/test.lua
new file mode 100644
index 00000000..2426a766
--- /dev/null
+++ b/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)
\ No newline at end of file
diff --git a/src/Lua.SourceGenerator/Comparer.cs b/src/Lua.SourceGenerator/Comparer.cs
index 352a82a9..34214ac5 100644
--- a/src/Lua.SourceGenerator/Comparer.cs
+++ b/src/Lua.SourceGenerator/Comparer.cs
@@ -2,7 +2,7 @@
namespace Lua.SourceGenerator;
-internal sealed class Comparer : IEqualityComparer<(GeneratorAttributeSyntaxContext, Compilation)>
+sealed class Comparer : IEqualityComparer<(GeneratorAttributeSyntaxContext, Compilation)>
{
public static readonly Comparer Instance = new();
diff --git a/src/Lua.SourceGenerator/Lua.SourceGenerator.csproj b/src/Lua.SourceGenerator/Lua.SourceGenerator.csproj
index c23cce55..bc34304f 100644
--- a/src/Lua.SourceGenerator/Lua.SourceGenerator.csproj
+++ b/src/Lua.SourceGenerator/Lua.SourceGenerator.csproj
@@ -1,23 +1,23 @@
-
- netstandard2.0
- 12
- enable
- enable
- true
- cs
- true
- false
- true
- false
- true
-
+
+ netstandard2.0
+ 12
+ enable
+ enable
+ true
+ cs
+ true
+ false
+ true
+ false
+ true
+
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs b/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs
index 4772b3ef..53e527c5 100644
--- a/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs
+++ b/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 metaDict)
{
try
@@ -75,24 +82,24 @@ static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolRefere
(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();
- 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 @@ static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolRefere
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 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation,
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 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation,
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 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation,
}
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 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation,
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 @@ static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builde
{
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(context.Return(result));");
}
builder.AppendLine(");");
@@ -242,9 +313,9 @@ static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builde
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 @@ static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder bui
{
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 @@ static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder bui
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(context.Return());");
}
builder.AppendLine(");");
@@ -304,7 +391,7 @@ static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder bui
return true;
}
- static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, HashSet metamethodSet, in SourceProductionContext context)
+ static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, HashSet metamethodSet, in SourceProductionContext context)
{
builder.AppendLine();
@@ -315,7 +402,7 @@ static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, Symbo
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 @@ static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, Symbo
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 @@ static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, Symbo
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 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada
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 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada
}
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 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada
}
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 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada
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(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(context.Return());", false);
}
}
+
builder.AppendLine(");");
builder.AppendLine();
}
@@ -453,6 +567,7 @@ static bool TryEmitMetatable(CodeBuilder builder, IEnumerable 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 @@ public TypeMetadata(TypeDeclarationSyntax syntax, INamedTypeSymbol symbol, Symbo
.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))
diff --git a/src/Lua.SourceGenerator/Utilities/CodeBuilder.cs b/src/Lua.SourceGenerator/Utilities/CodeBuilder.cs
index cae5f3bf..4bd3eacd 100644
--- a/src/Lua.SourceGenerator/Utilities/CodeBuilder.cs
+++ b/src/Lua.SourceGenerator/Utilities/CodeBuilder.cs
@@ -2,7 +2,7 @@
namespace Lua.SourceGenerator;
-internal sealed class CodeBuilder
+sealed class CodeBuilder
{
public ref struct IndentScope
{
@@ -11,7 +11,11 @@ public ref struct IndentScope
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 @@ public ref struct BlockScope
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 @@ public void Dispose()
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 @@ public void AppendByteArrayString(byte[] bytes)
{
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 @@ public void IncreaseIndent()
public void DecreaseIndent()
{
if (indentLevel > 0)
+ {
indentLevel--;
+ }
}
public void BeginBlock()
diff --git a/src/Lua.SourceGenerator/Utilities/RoslynAnalyzerExtensions.cs b/src/Lua.SourceGenerator/Utilities/RoslynAnalyzerExtensions.cs
index 9fc8c677..6b0d8e24 100644
--- a/src/Lua.SourceGenerator/Utilities/RoslynAnalyzerExtensions.cs
+++ b/src/Lua.SourceGenerator/Utilities/RoslynAnalyzerExtensions.cs
@@ -2,19 +2,17 @@
namespace Lua.SourceGenerator;
-internal static class RoslynAnalyzerExtensions
+static class RoslynAnalyzerExtensions
{
public static AttributeData? FindAttribute(this IEnumerable attributeDataList, string typeName)
{
return attributeDataList
- .Where(x => x.AttributeClass?.ToDisplayString() == typeName)
- .FirstOrDefault();
+ .FirstOrDefault(x => x.AttributeClass?.ToDisplayString() == typeName);
}
public static AttributeData? FindAttributeShortName(this IEnumerable attributeDataList, string typeName)
{
return attributeDataList
- .Where(x => x.AttributeClass?.Name == typeName)
- .FirstOrDefault();
+ .FirstOrDefault(x => x.AttributeClass?.Name == typeName);
}
}
\ No newline at end of file
diff --git a/src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs b/src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs
index ebe457ae..5c3de88c 100644
--- a/src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs
+++ b/src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs
@@ -2,7 +2,7 @@
namespace Lua.SourceGenerator;
-internal static class SymbolExtensions
+static class SymbolExtensions
{
public static bool ContainsAttribute(this ISymbol symbol, INamedTypeSymbol attribtue)
{
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/src/Lua.Unity/Assets/AddressableAssetsData/AddressableAssetSettings.asset
index 43ab1777..7f1b0391 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/AddressableAssetSettings.asset
+++ b/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
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset b/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset
index a2d46b31..76e26e44 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset
+++ b/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}
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset.meta b/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset.meta
index 22b6929d..3467d861 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset.meta
+++ b/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:
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset b/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset
index 751d5144..633910b9 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset
+++ b/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
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset b/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset
index 456d2bd9..dcf8e40f 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset
+++ b/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
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset b/src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset
index 73b3da48..be0f83c8 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset
+++ b/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
diff --git a/src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset.meta b/src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset.meta
index 9a8c6b05..169c5367 100644
--- a/src/Lua.Unity/Assets/AddressableAssetsData/DefaultObject.asset.meta
+++ b/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:
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs b/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs
new file mode 100644
index 00000000..74993177
--- /dev/null
+++ b/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++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs.meta
new file mode 100644
index 00000000..ff1aea7e
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacAssetEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7cb5a3afb8f64363995fb96ba5587c8f
+timeCreated: 1749868556
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs b/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs
new file mode 100644
index 00000000..91cb3534
--- /dev/null
+++ b/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();
+ if (icon == null)
+ {
+ icon = Resources.Load("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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs.meta
new file mode 100644
index 00000000..ef867930
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Editor/LuacImporter.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d8f9fbd84f8449649b27628ba85a3217
+timeCreated: 1749868371
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/Resources.meta b/src/Lua.Unity/Assets/Lua.Unity/Editor/Resources.meta
new file mode 100644
index 00000000..b12f0622
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Editor/Resources.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1d9c4ac6bedc4e7d8f4d5b10742b8e85
+timeCreated: 1749868505
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/LuaAssetIcon.png b/src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png
similarity index 100%
rename from src/Lua.Unity/Assets/Lua.Unity/Editor/LuaAssetIcon.png
rename to src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Editor/LuaAssetIcon.png.meta b/src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png.meta
similarity index 100%
rename from src/Lua.Unity/Assets/Lua.Unity/Editor/LuaAssetIcon.png.meta
rename to src/Lua.Unity/Assets/Lua.Unity/Editor/Resources/LuaAssetIcon.png.meta
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/AddressablesModuleLoader.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/AddressablesModuleLoader.cs
index cc833494..e1f9aeda 100644
--- a/src/Lua.Unity/Assets/Lua.Unity/Runtime/AddressablesModuleLoader.cs
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/AddressablesModuleLoader.cs
@@ -12,7 +12,7 @@ namespace Lua.Unity
{
public sealed class AddressablesModuleLoader : ILuaModuleLoader
{
- readonly Dictionary cache = new();
+ readonly Dictionary cache = new();
public bool Exists(string moduleName)
{
@@ -26,7 +26,8 @@ public async ValueTask LoadAsync(string moduleName, CancellationToken
{
if (cache.TryGetValue(moduleName, out var asset))
{
- return new LuaModule(moduleName, asset.text);
+
+ return asset .GetModule( moduleName);
}
var asyncOperation = Addressables.LoadAssetAsync(moduleName);
@@ -38,7 +39,7 @@ public async ValueTask LoadAsync(string moduleName, CancellationToken
}
cache.Add(moduleName, asset);
- return new LuaModule(moduleName, asset.text);
+ return asset .GetModule( moduleName);
}
}
internal static class AsyncOperationHandleExtensions
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAsset.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAsset.cs
index d83f4ec2..f370e397 100644
--- a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAsset.cs
+++ b/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);
+ }
}
}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs
new file mode 100644
index 00000000..540835e5
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs.meta
new file mode 100644
index 00000000..e7494dcc
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaAssetBase.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 68f5e96db703412a9d5023a6874176c9
+timeCreated: 1749868448
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs
new file mode 100644
index 00000000..39fe47e1
--- /dev/null
+++ b/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 ExecuteAsync(this LuaState state, LuaAssetBase luaAssetBase, string name, Memory 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 ExecuteAsync(this LuaState state, LuaAssetBase luaAssetBase, string name, CancellationToken cancellationToken = default)
+ {
+ return state.ExecuteAsync(luaAssetBase, name, cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs.meta
new file mode 100644
index 00000000..a2150095
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuaThreadAssetExtensions.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f82c28987dc44385a582e3eb37501ee8
+timeCreated: 1750856924
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs
new file mode 100644
index 00000000..766edb2e
--- /dev/null
+++ b/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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs.meta
new file mode 100644
index 00000000..ac6718e1
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/LuacAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b8303e0c721c4fc3829ce6b681f6dae0
+timeCreated: 1749869951
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/ResourcesModuleLoader.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/ResourcesModuleLoader.cs
index 438b2feb..2f12fb2c 100644
--- a/src/Lua.Unity/Assets/Lua.Unity/Runtime/ResourcesModuleLoader.cs
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/ResourcesModuleLoader.cs
@@ -4,18 +4,18 @@
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
-
namespace Lua.Unity
{
public sealed class ResourcesModuleLoader : ILuaModuleLoader
{
- readonly Dictionary cache = new();
+ readonly Dictionary cache = new();
public bool Exists(string moduleName)
{
+ Debug.Log(moduleName);
if (cache.TryGetValue(moduleName, out _)) return true;
- var asset = Resources.Load(moduleName);
+ var asset = Resources.Load(moduleName);
if (asset == null) return false;
cache.Add(moduleName, asset);
@@ -26,10 +26,10 @@ public async ValueTask LoadAsync(string moduleName, CancellationToken
{
if (cache.TryGetValue(moduleName, out var asset))
{
- return new LuaModule(moduleName, asset.text);
+ return asset.GetModule(moduleName);
}
- var request = Resources.LoadAsync(moduleName);
+ var request = Resources.LoadAsync(moduleName);
await request;
if (request.asset == null)
@@ -37,10 +37,9 @@ public async ValueTask LoadAsync(string moduleName, CancellationToken
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
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs
new file mode 100644
index 00000000..d3358752
--- /dev/null
+++ b/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 environmentVariables = null,bool allowToQuitOnExitCall = false)
+ {
+ EnvironmentVariables = environmentVariables ?? new Dictionary();
+ AllowToQuitOnExitCall = allowToQuitOnExitCall;
+ }
+
+
+ public bool AllowToQuitOnExitCall { get; }
+ public Dictionary 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;
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs.meta
new file mode 100644
index 00000000..dfb38b72
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityApplicationOsEnvironment.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2cf920ff7f0f4cff9b392d71c4656d6e
+timeCreated: 1749869194
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs b/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs
new file mode 100644
index 00000000..52eb3b86
--- /dev/null
+++ b/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 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() { }
+ }
+}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs.meta b/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs.meta
new file mode 100644
index 00000000..8f5431c9
--- /dev/null
+++ b/src/Lua.Unity/Assets/Lua.Unity/Runtime/UnityStandardIO.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 8ba8000b21dc41c4a80569aa46d8a7be
+timeCreated: 1749868964
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Sandbox/Resources/foo.lua b/src/Lua.Unity/Assets/Sandbox/Resources/foo.lua
index 283d139f..b33ab418 100644
--- a/src/Lua.Unity/Assets/Sandbox/Resources/foo.lua
+++ b/src/Lua.Unity/Assets/Sandbox/Resources/foo.lua
@@ -4,4 +4,5 @@ t.greet = function()
print("Foo: Hello!")
end
+print(debug.traceback())
return t
diff --git a/src/Lua.Unity/Assets/Sandbox/Resources/test.luac b/src/Lua.Unity/Assets/Sandbox/Resources/test.luac
new file mode 100644
index 00000000..c1f03e43
Binary files /dev/null and b/src/Lua.Unity/Assets/Sandbox/Resources/test.luac differ
diff --git a/src/Lua.Unity/Assets/Sandbox/SampleScene.unity b/src/Lua.Unity/Assets/Sandbox/SampleScene.unity
index 1974db83..2d6944ac 100644
--- a/src/Lua.Unity/Assets/Sandbox/SampleScene.unity
+++ b/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
diff --git a/src/Lua.Unity/Assets/Sandbox/Sandbox.cs b/src/Lua.Unity/Assets/Sandbox/Sandbox.cs
index 3c6f5115..da8a3bfb 100644
--- a/src/Lua.Unity/Assets/Sandbox/Sandbox.cs
+++ b/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(0));
return new(0);
@@ -25,10 +35,12 @@ await state.DoStringAsync(
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 @@ await state.DoStringAsync(
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[] array, T value)
+ {
+ foreach (var item in array)
+ {
+ if (EqualityComparer.Default.Equals(item, value))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/Sandbox/bar.lua b/src/Lua.Unity/Assets/Sandbox/bar.lua
index 105c619e..c2eb0f23 100644
--- a/src/Lua.Unity/Assets/Sandbox/bar.lua
+++ b/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
diff --git a/src/Lua.Unity/Assets/StreamingAssets/streaming.lua b/src/Lua.Unity/Assets/StreamingAssets/streaming.lua
new file mode 100644
index 00000000..9baedb16
--- /dev/null
+++ b/src/Lua.Unity/Assets/StreamingAssets/streaming.lua
@@ -0,0 +1 @@
+return { f= function()print("loaded from streaming.lua")end }
\ No newline at end of file
diff --git a/src/Lua.Unity/Assets/StreamingAssets/streaming.lua.meta b/src/Lua.Unity/Assets/StreamingAssets/streaming.lua.meta
new file mode 100644
index 00000000..688e8dbe
--- /dev/null
+++ b/src/Lua.Unity/Assets/StreamingAssets/streaming.lua.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f05262e5f419479c940d5486b1c58a00
+timeCreated: 1749995150
\ No newline at end of file
diff --git a/src/Lua.Unity/Packages/manifest.json b/src/Lua.Unity/Packages/manifest.json
index 7d0fc868..fa23bb24 100644
--- a/src/Lua.Unity/Packages/manifest.json
+++ b/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",
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1.meta
deleted file mode 100644
index 545bae74..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 416b07e9204db499d88a21b05d4165c9
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/.signature.p7s b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/.signature.p7s
deleted file mode 100644
index da43b7a1..00000000
Binary files a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/.signature.p7s and /dev/null differ
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png
deleted file mode 100644
index 765536b6..00000000
Binary files a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png and /dev/null differ
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png.meta
deleted file mode 100644
index 9bec59b7..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/Icon.png.meta
+++ /dev/null
@@ -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:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec
deleted file mode 100644
index 43e75ddd..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- LuaCSharp
- 0.3.1
- Annulus Games
- MIT
- https://licenses.nuget.org/MIT
- Icon.png
- README.md
- https://github.com/AnnulusGames/Lua-CSharp
- High performance Lua interpreter implemented in C# for .NET and Unity
- © Annulus Games
- lua interpreter
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec.meta
deleted file mode 100644
index adf12c90..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/LuaCSharp.nuspec.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 9852f8b1690074e67a6420f320832008
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md
deleted file mode 100644
index 72dfba95..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md
+++ /dev/null
@@ -1,519 +0,0 @@
-# Lua-CSharp
-
-High performance Lua interpreter implemented in C# for .NET and Unity
-
-
-
-[](https://www.nuget.org/packages/LuaCSharp)
-[](https://github.com/AnnulusGames/Lua-CSharp/releases)
-[](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):
-
-
-
-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(out T value)` or `Read()`.
-
-```cs
-var results = await state.DoStringAsync("return 1 + 1");
-
-// double
-var value = results[0].Read();
-```
-
-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`.
-
-```cs
-// Create a table in Lua
-var results = await state.DoStringAsync("return { a = 1, b = 2, c = 3 }");
-var table1 = results[0].Read();
-
-// 1
-Console.WriteLine(table1["a"]);
-
-// Create a table in C#
-results = await state.DoStringAsync("return { 1, 2, 3 }");
-var table2 = results[0].Read();
-
-// 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();
-
-// 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()
- var arg0 = context.GetArgument(0);
- var arg1 = context.GetArgument(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(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.
-
-
-
-## 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();
-
-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`, `ValueTask/ValueTask`, `UniTask/UniTask`, and `Awaitable/Awaitable` 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 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).
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md.meta
deleted file mode 100644
index a31538a9..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/README.md.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: ca852d91bba6d4f1bb3b51fa9102d995
-TextScriptImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers.meta
deleted file mode 100644
index 34ab4ad7..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 8d2121deb53d94ca2a244173798b5010
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet.meta
deleted file mode 100644
index 5254452d..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: ebee2cd7f5c8c48e6b9592d28d5c0bb8
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs.meta
deleted file mode 100644
index eb579750..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: c490e8a3c6ca14ba2a2556f3ff493942
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll
deleted file mode 100644
index fb88a0ae..00000000
Binary files a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll and /dev/null differ
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll.meta
deleted file mode 100644
index 53482f57..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/analyzers/dotnet/cs/Lua.SourceGenerator.dll.meta
+++ /dev/null
@@ -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:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib.meta
deleted file mode 100644
index 17d0cf8b..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 712703d7889d34e3f80f7e11a115306e
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1.meta
deleted file mode 100644
index 214344b2..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 6907bd5f6ac8a4472a0145d853426293
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll
deleted file mode 100644
index f0d4f8cb..00000000
Binary files a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll and /dev/null differ
diff --git a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll.meta b/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll.meta
deleted file mode 100644
index 43927c6c..00000000
--- a/src/Lua.Unity/Packages/nuget-packages/InstalledPackages/LuaCSharp.0.3.1/lib/netstandard2.1/Lua.dll.meta
+++ /dev/null
@@ -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:
diff --git a/src/Lua.Unity/Packages/nuget-packages/packages.config b/src/Lua.Unity/Packages/nuget-packages/packages.config
index fbdd0288..40a17ddf 100644
--- a/src/Lua.Unity/Packages/nuget-packages/packages.config
+++ b/src/Lua.Unity/Packages/nuget-packages/packages.config
@@ -1,5 +1,6 @@
-
-
+
+
+
\ No newline at end of file
diff --git a/src/Lua.Unity/Packages/packages-lock.json b/src/Lua.Unity/Packages/packages-lock.json
index f89d3a31..f4bdbb17 100644
--- a/src/Lua.Unity/Packages/packages-lock.json
+++ b/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,
diff --git a/src/Lua.Unity/ProjectSettings/EditorBuildSettings.asset b/src/Lua.Unity/ProjectSettings/EditorBuildSettings.asset
index be6bb434..2cb387b5 100644
--- a/src/Lua.Unity/ProjectSettings/EditorBuildSettings.asset
+++ b/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
diff --git a/src/Lua/CodeAnalysis/Compilation/BomUtility.cs b/src/Lua/CodeAnalysis/Compilation/BomUtility.cs
new file mode 100644
index 00000000..bac21c6f
--- /dev/null
+++ b/src/Lua/CodeAnalysis/Compilation/BomUtility.cs
@@ -0,0 +1,52 @@
+using System.Text;
+
+namespace Lua.CodeAnalysis.Compilation;
+
+static class BomUtility
+{
+ public static ReadOnlySpan BomUtf8 => [0xEF, 0xBB, 0xBF];
+
+ static ReadOnlySpan BomUtf16Little => [0xFF, 0xFE];
+
+ static ReadOnlySpan BomUtf16Big => [0xFE, 0xFF];
+
+ static ReadOnlySpan BomUtf32Little => [0xFF, 0xFE, 0x00, 0x00];
+
+ ///
+ /// 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.
+ ///
+ /// The text to check for BOM.
+ /// The encoding of the text.
+ /// The text without the BOM.
+ public static ReadOnlySpan GetEncodingFromBytes(ReadOnlySpan 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;
+ }
+}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/Declarements.cs b/src/Lua/CodeAnalysis/Compilation/Declarements.cs
new file mode 100644
index 00000000..b72a9064
--- /dev/null
+++ b/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 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
+{
+ public Block? Previous;
+ public int FirstLabel, FirstGoto;
+ public int ActiveVariableCount;
+ public bool HasUpValue, IsLoop;
+ Block() { }
+
+ ref Block? IPoolNode.NextNode => ref Previous;
+
+ static LinkedPool 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
+}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/Descriptions.cs b/src/Lua/CodeAnalysis/Compilation/Descriptions.cs
deleted file mode 100644
index e3313e45..00000000
--- a/src/Lua/CodeAnalysis/Compilation/Descriptions.cs
+++ /dev/null
@@ -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 Name { get; init; }
- public required int Index { get; init; }
- public required byte RegisterIndex { get; init; }
- }
-
- public readonly record struct GotoDescription
- {
- public required ReadOnlyMemory Name { get; init; }
- public required int JumpInstructionIndex { get; init; }
- }
-
- public record struct BreakDescription
- {
- public required int Index { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/Dump.cs b/src/Lua/CodeAnalysis/Compilation/Dump.cs
new file mode 100644
index 00000000..4b0ad1e7
--- /dev/null
+++ b/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 LuaSignature => "\eLua"u8;
+ public static ReadOnlySpan 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 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 writer, bool reversedEndian)
+{
+ public readonly IBufferWriter Writer = writer;
+ Span unWritten;
+
+ void Write(ReadOnlySpan 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(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 v)
+ {
+ WriteInt(v.Length);
+ if (IsReversedEndian)
+ {
+ foreach (var i in v)
+ {
+ var reversed = BinaryPrimitives.ReverseEndianness(i);
+ Write(new(&reversed, 4));
+ }
+ }
+ else
+ {
+ Write(MemoryMarshal.Cast(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 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());
+ break;
+ }
+ }
+ }
+
+ void WritePrototypes(ReadOnlySpan prototypes)
+ {
+ WriteInt(prototypes.Length);
+ foreach (var p in prototypes)
+ {
+ DumpFunction(p);
+ }
+ }
+
+ void WriteLocalVariables(ReadOnlySpan localVariables)
+ {
+ WriteInt(localVariables.Length);
+ foreach (var v in localVariables)
+ {
+ WriteString(v.Name);
+ WriteInt(v.StartPc);
+ WriteInt(v.EndPc);
+ }
+ }
+
+ void WriteUpValues(ReadOnlySpan upValues)
+ {
+ WriteInt(upValues.Length);
+ foreach (var u in upValues)
+ {
+ WriteBool(u.IsLocal);
+ WriteByte((byte)u.Index);
+ }
+ }
+}
+
+unsafe ref struct UndumpState(ReadOnlySpan span, ReadOnlySpan name, StringInternPool internPool)
+{
+ public ReadOnlySpan Unread = span;
+ bool otherEndian;
+ int pointerSize;
+ readonly ReadOnlySpan 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 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 span = new(&i, sizeof(int));
+ Read(span);
+
+ if (otherEndian)
+ {
+ i = BinaryPrimitives.ReverseEndianness(i);
+ }
+
+ return i;
+ }
+
+ long ReadLong()
+ {
+ long i = 0;
+ Span 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 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(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 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.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.Shared.Rent(len * 2));
+ var count = Encoding.UTF8.GetChars(span, chars);
+ return internPool.Intern(chars[..count]);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(arrayPooled);
+ if (charArrayPooled != null)
+ {
+ ArrayPool.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;
+ }
+}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/Function.cs b/src/Lua/CodeAnalysis/Compilation/Function.cs
new file mode 100644
index 00000000..596f137b
--- /dev/null
+++ b/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
+{
+ public readonly Dictionary 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 pool;
+
+ ref Function? IPoolNode.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 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($" 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 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!;
+ }
+}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs b/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs
deleted file mode 100644
index 5996deff..00000000
--- a/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs
+++ /dev/null
@@ -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 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 instructions;
- FastListCore instructionPositions;
-
- // constants
- Dictionary constantIndexMap = new(16);
- FastListCore constants;
-
- // functions
- Dictionary, int> functionMap = new(32, Utf16StringMemoryComparer.Default);
- FastListCore functions;
-
- // upvalues
- FastListCore upvalues;
- FastListCore localVariables;
-
- // loop
- FastListCore breakQueue;
- FastListCore gotoQueue;
-
- ///
- /// Maximum local stack size
- ///
- public byte MaxStackPosition { get; set; }
-
- ///
- /// Chunk name (for debug)
- ///
- public string? ChunkName { get; set; }
-
- ///
- /// Level of nesting of while, repeat, and for loops
- ///
- public int LoopLevel { get; set; }
-
- ///
- /// Number of parameters
- ///
- public int ParameterCount { get; set; }
-
- ///
- /// Weather the function has variable arguments
- ///
- public bool HasVariableArguments { get; set; }
-
- ///
- /// Line number where the function is defined
- ///
- public int LineDefined { get; set; }
-
- ///
- /// Last line number where the function is defined
- ///
- public int LastLineDefined { get; set; }
-
- ///
- /// Parent scope context
- ///
- public ScopeCompilationContext? ParentScope { get; private set; }
-
- ///
- /// Top-level scope context
- ///
- public ScopeCompilationContext Scope { get; }
-
- ///
- /// Instructions
- ///
- public Span Instructions => instructions.AsSpan();
-
- ///
- /// Push the new instruction.
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void PushInstruction(in Instruction instruction, in SourcePosition position)
- {
- instructions.Add(instruction);
- instructionPositions.Add(position);
- }
-
- ///
- /// Push or merge the new instruction.
- ///
- [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);
- }
-
- ///
- /// Gets the index of the constant from the value, or if the constant is not registered it is added and its index is returned.
- ///
- 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 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 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 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 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;
- }
-
- ///
- /// Resets the values held in the context.
- ///
- 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;
- }
-
- ///
- /// Returns the context object to the pool.
- ///
- public void Dispose()
- {
- ParentScope = null;
- Pool.Return(this);
- }
-}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs b/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs
deleted file mode 100644
index e9118086..00000000
--- a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs
+++ /dev/null
@@ -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
-{
- public static readonly LuaCompiler Default = new();
-
- public Chunk Compile(string source, string? chunkName = null)
- {
- return Compile(LuaSyntaxTree.Parse(source, chunkName), chunkName);
- }
-
- ///
- /// Returns a compiled chunk of the syntax tree.
- ///
- 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.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 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(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 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(out var d1))
- {
- value = -d1;
- return true;
- }
-
- break;
- case UnaryOperator.Not:
- if (unaryNodeValue.TryRead(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(out var d1) && rightValue.TryRead(out var d2))
- {
- value = d1 + d2;
- return true;
- }
- }
- break;
- case BinaryOperator.Subtraction:
- {
- if (leftValue.TryRead(out var d1) && rightValue.TryRead(out var d2))
- {
- value = d1 - d2;
- return true;
- }
- }
- break;
- case BinaryOperator.Multiplication:
- {
- if (leftValue.TryRead(out var d1) && rightValue.TryRead(out var d2))
- {
- value = d1 * d2;
- return true;
- }
- }
- break;
- case BinaryOperator.Division:
- {
- if (leftValue.TryRead(out var d1) && rightValue.TryRead(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);
- }
-
- ///
- /// Compiles a conditional boolean branch: if true (or false), the next instruction added is skipped.
- ///
- /// Condition node
- /// Context
- /// If true, generates an instruction sequence that skips the next instruction if the condition is false.
- /// Position of the test instruction
- 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);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Lua/CodeAnalysis/Compilation/Parser.cs b/src/Lua/CodeAnalysis/Compilation/Parser.cs
new file mode 100644
index 00000000..6680058d
--- /dev/null
+++ b/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, 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 ActiveVariables;
+ internal FastListCore