diff --git a/.editorconfig b/.editorconfig index 4eb70ebd..2ee71769 100644 --- a/.editorconfig +++ b/.editorconfig @@ -104,7 +104,7 @@ 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 @@ -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/Lua.sln b/Lua.sln index 1de7b2f0..61acb3b7 100644 --- a/Lua.sln +++ b/Lua.sln @@ -12,6 +12,9 @@ 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}" + ProjectSection(SolutionItems) = preProject + sandbox\ConsoleApp2\.gitignore = sandbox\ConsoleApp2\.gitignore + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "sandbox\ConsoleApp1\ConsoleApp1.csproj", "{718A361C-AAF3-45A4-84D4-8C4FB6BB374E}" EndProject @@ -19,6 +22,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "sandbox\Benchm EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lua.SourceGenerator", "src\Lua.SourceGenerator\Lua.SourceGenerator.csproj", "{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp2", "sandbox\ConsoleApp2\ConsoleApp2.csproj", "{474D977A-946F-49F1-9293-F7F78E0F6A01}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JitTest", "sandbox\JitTest\JitTest.csproj", "{BF479E2C-AAF5-4128-B236-EC7D6092E1F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +55,14 @@ Global {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 + {474D977A-946F-49F1-9293-F7F78E0F6A01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {474D977A-946F-49F1-9293-F7F78E0F6A01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {474D977A-946F-49F1-9293-F7F78E0F6A01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {474D977A-946F-49F1-9293-F7F78E0F6A01}.Release|Any CPU.Build.0 = Release|Any CPU + {BF479E2C-AAF5-4128-B236-EC7D6092E1F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF479E2C-AAF5-4128-B236-EC7D6092E1F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF479E2C-AAF5-4128-B236-EC7D6092E1F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF479E2C-AAF5-4128-B236-EC7D6092E1F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6E33BFBC-E51F-493E-9AF0-30C1100F5B5D} = {18A64E25-9557-457B-80AE-A6EFE853118D} @@ -55,5 +70,7 @@ Global {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} + {474D977A-946F-49F1-9293-F7F78E0F6A01} = {33883F28-679F-48AD-8E64-3515C7BDAF5A} + {BF479E2C-AAF5-4128-B236-EC7D6092E1F8} = {33883F28-679F-48AD-8E64-3515C7BDAF5A} EndGlobalSection EndGlobal 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/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..b98f2c62 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.TopLevelAccess.Call(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..fca0431f 100644 --- a/sandbox/ConsoleApp1/ConsoleApp1.csproj +++ b/sandbox/ConsoleApp1/ConsoleApp1.csproj @@ -1,21 +1,22 @@  - - - - Analyzer - false - - + + + + Analyzer + false + + - - Exe - net8.0 - enable - enable + + Exe + net8.0 + 13 + enable + enable - false - Generated - + false + Generated + diff --git a/sandbox/ConsoleApp1/LVec3.cs b/sandbox/ConsoleApp1/LVec3.cs index 040ae42d..63aa7b49 100644 --- a/sandbox/ConsoleApp1/LVec3.cs +++ b/sandbox/ConsoleApp1/LVec3.cs @@ -30,10 +30,7 @@ public float Z [LuaMember("create")] public static LVec3 Create(float x, float y, float z) { - return new LVec3() - { - value = new Vector3(x, y, z) - }; + return new LVec3() { value = new Vector3(x, y, z) }; } public override string ToString() diff --git a/sandbox/ConsoleApp1/Program.cs b/sandbox/ConsoleApp1/Program.cs index 10905080..c0b196de 100644 --- a/sandbox/ConsoleApp1/Program.cs +++ b/sandbox/ConsoleApp1/Program.cs @@ -1,38 +1,41 @@ using System.Runtime.CompilerServices; -using Lua.CodeAnalysis.Syntax; -using Lua.CodeAnalysis.Compilation; using Lua.Runtime; using Lua; using Lua.Standard; - +using System.Text.RegularExpressions; +using System; +using System.IO; +using System.Text; 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))); + }); +string source = ""; try { - var source = File.ReadAllText(GetAbsolutePath("test.lua")); + source = File.ReadAllText(GetAbsolutePath("test.lua")); - var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua"); Console.WriteLine("Source Code " + new string('-', 50)); - var debugger = new DisplayStringSyntaxVisitor(); - Console.WriteLine(debugger.GetDisplayString(syntaxTree)); + Console.WriteLine(source); - var chunk = LuaCompiler.Default.Compile(syntaxTree, "test.lua"); + var closure = state.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); + var count = await state.TopLevelAccess.RunAsync(closure); Console.WriteLine("Result " + new string('-', 50)); - - for (int i = 0; i < resultCount; i++) + using var results = state.TopLevelAccess.ReadReturnValues(count); + for (int i = 0; i < count; i++) { Console.WriteLine(results[i]); } @@ -41,9 +44,18 @@ } 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 +65,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 +90,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 +98,52 @@ 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..52afe590 --- /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..853c04da --- /dev/null +++ b/sandbox/ConsoleApp2/Program.cs @@ -0,0 +1,71 @@ +using Lua.Runtime; +using Lua; +using Lua.Standard; +using System; + +var state = LuaState.Create(); +state.OpenStandardLibraries(); +{ + var closure = state.Load("return function (a,b,...) print('a : '..a..' b :'..'args : ',...) end", "simple"); + using var threadLease = state.MainThread.RentUseThread(); + var access = threadLease.Thread.TopLevelAccess; + { + var count = await access.RunAsync(closure,0); + var results = access.ReadReturnValues(count); + for (int 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); + results = access.ReadReturnValues(count); + for (int 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 coroutineLease = state.MainThread.RentCoroutine(f); + var coroutine = coroutineLease.Thread; + { + var stack =new LuaStack(); + stack.PushRange("a", "b", "c", "d", "e"); + + for (int 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 (int 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..2eca573b --- /dev/null +++ b/sandbox/JitTest/JitTest.csproj @@ -0,0 +1,20 @@ + + + + Exe + net9.0 + 13 + enable + enable + + + + + + + + + + + + diff --git a/sandbox/JitTest/Program.cs b/sandbox/JitTest/Program.cs new file mode 100644 index 00000000..d661688a --- /dev/null +++ b/sandbox/JitTest/Program.cs @@ -0,0 +1,63 @@ +// See https://aka.ms/new-console-template for more information + + +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(); +{ + await luaState.DoFileAsync((GetAbsolutePath("db.lua"))); + await luaState.DoFileAsync((GetAbsolutePath("events.lua"))); +} + +var closure = luaState.Load(File.ReadAllBytes(GetAbsolutePath("test.lua")),"test.lua"); + +for (int i = 0; i < 1000; i++) +{ + await luaState.TopLevelAccess.RunAsync(closure); + luaState.MainThread.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); +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/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..11a86312 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 @@ -82,17 +89,17 @@ static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolRefere 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,7 +113,7 @@ 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(); @@ -133,7 +140,9 @@ 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, 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))) @@ -162,7 +171,9 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation, } 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 +188,18 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation, } PARAMETERS: - foreach (var typeSymbol in method.Symbol.Parameters - .Select(x => x.Type)) + for (int index = 0; index < method.Symbol.Parameters.Length; index++) { + IParameterSymbol? 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 +217,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 +231,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 +261,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 +281,44 @@ 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.Thread, $""'{{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.Thread, $""'{{key}}' cannot overwrite."");"); } } @@ -292,11 +326,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.Thread, $""'{{key}}' not found."");"); } } - builder.AppendLine("return new(0);"); + builder.AppendLine("return new global::System.Threading.Tasks.ValueTask(context.Return());"); } builder.AppendLine(");"); @@ -304,7 +338,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 +349,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 +368,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 +380,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()) { @@ -359,10 +393,20 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);"); index++; } + + bool hasCancellationToken = false; - foreach (var parameter in methodMetadata.Symbol.Parameters) + for (int i = 0; i < methodMetadata.Symbol.Parameters.Length; i++) { - var isParameterLuaValue = SymbolEqualityComparer.Default.Equals(parameter.Type, references.LuaValue); + IParameterSymbol? 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 +418,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 +429,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 +443,54 @@ 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 +512,7 @@ static bool TryEmitMetatable(CodeBuilder builder, IEnumerable 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..aacb9b7f --- /dev/null +++ b/src/Lua/CodeAnalysis/Compilation/Declarements.cs @@ -0,0 +1,117 @@ +using Lua.Internal; +using System.Runtime.CompilerServices; + +namespace Lua.CodeAnalysis.Compilation; + +unsafe struct TextReader(char* ptr, int length) +{ + public int Position; + + public (char, bool) Read() + { + if (Position >= length) return ('\0', false); + return (ptr[Position++], true); + } + + public bool TryRead(out char c) + { + if (Position >= length) + { + c = '\0'; + return false; + } + + c = ptr[Position++]; + return true; + } + + public char Current => ptr[Position]; + + public ReadOnlySpan Span => new(ptr, length); + public int Length => length; +} + +internal unsafe struct AssignmentTarget(ref AssignmentTarget previous, ExprDesc exprDesc) +{ + public readonly AssignmentTarget* Previous = (AssignmentTarget*)Unsafe.AsPointer(ref previous); + public ExprDesc Description = exprDesc; +} + +internal struct Label +{ + public string Name; + public int Pc, Line; + public int ActiveVariableCount; +} + +internal class Block : IPoolNode +{ + 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(); + } + + block.Previous = previous; + block.FirstLabel = firstLabel; + block.FirstGoto = firstGoto; + block.ActiveVariableCount = activeVariableCount; + block.HasUpValue = hasUpValue; + block.IsLoop = isLoop; + + + return block; + } + + public void Release() + { + Previous = null; + Pool.TryPush(this); + } +} + +internal struct ExprDesc +{ + public Kind Kind; + public int Index; + public int Table; + public Kind TableType; + public int Info; + public int T, F; + public double Value; + public readonly bool HasJumps() => T != F; + + public readonly bool IsNumeral() => Kind == Kind.Number && T == Function.NoJump && F == Function.NoJump; + + public readonly bool IsVariable() => Kind.Local <= Kind && Kind <= Kind.Indexed; + + public readonly bool HasMultipleReturns() => Kind == Kind.Call || Kind == Kind.VarArg; +} + +internal enum Kind +{ + Void = 0, + Nil = 1, + True = 2, + False = 3, + Constant = 4, + Number = 5, + NonRelocatable = 6, + Local = 7, + UpValue = 8, + Indexed = 9, + Jump = 10, + Relocatable = 11, + Call = 12, + VarArg = 13 +} \ 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..3b74d0f6 --- /dev/null +++ b/src/Lua/CodeAnalysis/Compilation/Dump.cs @@ -0,0 +1,485 @@ +using Lua.Internal; +using Lua.Runtime; +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Lua.CodeAnalysis.Compilation; + +[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal unsafe struct Header +{ + public static ReadOnlySpan 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"); + } +} + +internal 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() + { + var header = new Header(BitConverter.IsLittleEndian ^ IsReversedEndian); + Write(new(&header, Header.Size)); + } + + public void Dump(Prototype prototype) + { + if (unWritten.Length == 0) + { + unWritten = Writer.GetSpan(Header.Size + 32); + } + + DumpHeader(); + DumpFunction(prototype); + } + + + void DumpFunction(Prototype prototype) + { + WriteInt(prototype.LineDefined); //4 + WriteInt(prototype.LastLineDefined); //4 + WriteByte((byte)prototype.ParameterCount); //1 + WriteByte((byte)prototype.MaxStackSize); //1 + WriteByte((byte)(prototype.HasVariableArguments ? 1 : 0)); //1 + WriteIntSpanWithLength(MemoryMarshal.Cast(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) + { + long 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); + } + } +} + +internal unsafe ref struct UnDumpState(ReadOnlySpan span, ReadOnlySpan name) +{ + 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; + var span = new Span(&i, sizeof(int)); + Read(span); + + if (otherEndian) i = BinaryPrimitives.ReverseEndianness(i); + + return i; + } + + long ReadLong() + { + long i = 0; + var span = new Span(&i, sizeof(long)); + Read(span); + + if (otherEndian) i = BinaryPrimitives.ReverseEndianness(i); + return i; + } + + double ReadDouble() + { + long i = ReadLong(); + + return *(double*)(&i); + } + + public Prototype UnDump() + { + Header h = default; + var span = new Span(&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(); + + 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 (int i = 0; i < toWrite.Length; i++) + { + toWrite[i] = ReadInt(); + } + } + + + string ReadString() + { + var len = pointerSize == 4 ? ReadInt() : (int)ReadLong(); + if (len == 0) return ""; + len--; + var arrayPooled = ArrayPool.Shared.Rent(len); + try + { + var span = arrayPooled.AsSpan(0, len); + Read(span); + + var l = ReadByte(); + Debug.Assert(l == 0); + var str = Encoding.UTF8.GetString(span); + return str; + } + finally + { + ArrayPool.Shared.Return(arrayPooled); + } + } + + LuaValue[] ReadConstants() + { + var count = ReadInt(); + var constants = new LuaValue[count]; + for (int i = 0; i < count; i++) + { + var type = (LuaValueType)ReadByte(); + switch (type) + { + case LuaValueType.Nil: break; + case LuaValueType.Boolean: + constants[i] = (ReadByte() == 1); + break; + case LuaValueType.Number: + constants[i] = (ReadDouble()); + break; + case LuaValueType.String: + constants[i] = (ReadString()); + break; + } + } + + return constants; + } + + Prototype[] ReadPrototypes() + { + var count = ReadInt(); + var prototypes = count != 0 ? new Prototype[count] : []; + for (int i = 0; i < count; i++) + { + prototypes[i] = UnDumpFunction(); + } + + return prototypes; + } + + LocalVariable[] ReadLocalVariables() + { + var count = ReadInt(); + var localVariables = new LocalVariable[count]; + for (int i = 0; i < count; i++) + { + var name = ReadString(); + var startPc = ReadInt(); + var endPc = ReadInt(); + localVariables[i] = new() { Name = name, StartPc = startPc, EndPc = endPc }; + } + + return localVariables; + } + + UpValueDesc[] ReadUpValues() + { + var count = ReadInt(); + Debug.Assert(count < 100, $" too many upvalues :{count}"); + var upValues = new UpValueDesc[count]; + for (int i = 0; i < count; i++) + { + var isLocal = ReadBool(); + var index = ReadByte(); + upValues[i] = new() { 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..9af82995 --- /dev/null +++ b/src/Lua/CodeAnalysis/Compilation/Function.cs @@ -0,0 +1,1541 @@ +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; + +internal 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 Function(); + } + + f.P = p; + f.Proto = proto; + return f; + } + + internal void Release() + { + Previous = null; + ConstantLookup.Clear(); + JumpPc = NoJump; + Proto = null!; + LastTarget = 0; + FreeRegisterCount = 0; + ActiveVariableCount = 0; + FirstLocal = 0; + pool.TryPush(this); + } + + public const int OprMinus = 0; + + public const int OprNot = 1; + + public const int OprLength = 2; + + public const int OprNoUnary = 3; + public const int NoJump = -1; + + public const int NoRegister = MaxArgA; + + public const int MaxLocalVariables = 200; + + + public const int OprAdd = 0; + + public const int OprSub = 1; + + public const int OprMul = 2; + + public const int OprDiv = 3; + + public const int OprMod = 4; + + public const int OprPow = 5; + + public const int OprConcat = 6; + + public const int OprEq = 7; + + public const int OprLT = 8; + + public const int OprLE = 9; + + public const int OprNE = 10; + + public const int OprGT = 11; + + public const int OprGE = 12; + + public const int OprAnd = 13; + + public const int OprOr = 14; + + public const int OprNoBinary = 15; + + public void OpenFunction(int line) + { + var newProto = PrototypeBuilder.Get(P.Scanner.Source); + newProto.Source = P.Scanner.Source; + newProto.MaxStackSize = 2; + newProto.LineDefined = line; + + Proto.PrototypeList.Add(newProto); + var f = Get(P, Proto.PrototypeList[^1]); + f.Previous = this; + f.FirstLocal = P.ActiveVariables.Length; + P.Function = f; + + P.Function.EnterBlock(false); + } + + public ExprDesc CloseFunction() + { + var e = P.Function.Previous!.ExpressionToNextRegister(MakeExpression(Kind.Relocatable, Previous!.EncodeABx(OpCode.Closure, 0, Previous!.Proto.PrototypeList.Length - 1))); + P.Function.ReturnNone(); + P.Function.LeaveBlock(); + Assert(P.Function.Block == null); + var f = P.Function; + P.Function = f.Previous; + f.Release(); + return e; + } + + public void EnterBlock(bool isLoop) + { + var b = Block.Get(Block, P.ActiveLabels.Length, P.PendingGotos.Length, ActiveVariableCount, false, isLoop); + Block = b; + Assert(FreeRegisterCount == ActiveVariableCount); + } + + public void UndefinedGotoError(Label g) + { + if (Scanner.IsReserved(g.Name)) + { + SemanticError($"<{g.Name}> at line {g.Line} not inside a loop"); + } + else + { + SemanticError($"no visible label '{g.Name}' for 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) => b == 0 ? 1 : 0; + + + public static ExprDesc MakeExpression(Kind kind, int info) => new() { F = NoJump, T = NoJump, Kind = kind, Info = info }; + + + public void SemanticError(string message) + { + P.Scanner.Token = default; + P.Scanner.SyntaxError(message); + } + + public void BreakLabel() => FindGotos(MakeLabel("break", 0)); + + [Conditional("DEBUG")] + public void Unreachable() => Assert(false); + + + public ref Instruction Instruction(ExprDesc e) => ref Proto.CodeList[e.Info]; + + + [Conditional("DEBUG")] + public void AssertEqual(int a, int b) => Assert(a == b, $"{a} != {b}"); + + + public int Encode(Instruction i) + { + Assert(Proto.CodeList.Length == Proto.LineInfoList.Length); + DischargeJumpPc(); + Proto.CodeList.Add(i); + Proto.LineInfoList.Add(P.Scanner.LastLine); + return Proto.CodeList.Length - 1; + } + + public void DropLastInstruction() + { + Assert(Proto.CodeList.Length == Proto.LineInfoList.Length); + Proto.CodeList.Pop(); + Proto.LineInfoList.Pop(); + } + + public int EncodeABC(OpCode op, int a, int b, int c) + { + return Encode(CreateABC(op, a, b, c)); + } + + public int EncodeABx(OpCode op, int a, int bx) => Encode(CreateABx(op, a, bx)); + + + public int EncodeAsBx(OpCode op, int a, int sbx) => EncodeABx(op, a, sbx + MaxArgSBx); + + public int EncodeExtraArg(int a) => Encode(CreateAx(OpCode.ExtraArg, a)); + + + public int EncodeConstant(int r, int constant) + { + if (constant <= MaxArgBx) + return EncodeABx(OpCode.LoadK, r, constant); + var pc = EncodeABx(OpCode.LoadK, r, 0); + EncodeExtraArg(constant); + return pc; + } + + public ExprDesc EncodeString(string s) => MakeExpression(Kind.Constant, StringConstant(s)); + + + public void LoadNil(int from, int n) + { + if (Proto.CodeList.Length > LastTarget) // no jumps to current position + { + ref var previous = ref Proto.CodeList[^1]; + if (previous.OpCode == OpCode.LoadNil) + { + var pf = previous.A; + var pl = previous.A + previous.B; + var l = from + n - 1; + if (pf <= from && from <= pl + 1 || from <= pf && pf <= l + 1) // can connect both + { + from = Math.Min(from, pf); + l = Math.Max(l, pl); + previous.A = from; + previous.B = l - from; + return; + } + } + } + + EncodeABC(OpCode.LoadNil, from, n - 1, 0); + } + + public int Jump() + { + Assert(IsJumpListWalkable(JumpPc)); + var jumpPc = JumpPc; + JumpPc = NoJump; + return Concatenate(EncodeAsBx(OpCode.Jmp, 0, NoJump), jumpPc); + } + + public void JumpTo(int target) + { + PatchList(Jump(), target); + } + + public void ReturnNone() + { + EncodeABC(OpCode.Return, 0, 1, 0); + } + + public void SetMultipleReturns(ExprDesc e) + { + SetReturns(e, MultipleReturns); + } + + public void Return(ExprDesc e, int resultCount) + { + if (e.HasMultipleReturns()) + { + SetMultipleReturns(e); + if (e.Kind == Kind.Call && resultCount == 1) + { + Instruction(e).OpCode = OpCode.TailCall; + Assert(Instruction(e).A == ActiveVariableCount); + } + + EncodeABC(OpCode.Return, ActiveVariableCount, MultipleReturns + 1, 0); + } + else if (resultCount == 1) + EncodeABC(OpCode.Return, ExpressionToAnyRegister(e).Info, 1 + 1, 0); + else + { + ExpressionToNextRegister(e); + Assert(resultCount == FreeRegisterCount - ActiveVariableCount); + EncodeABC(OpCode.Return, ActiveVariableCount, resultCount + 1, 0); + } + } + + public int ConditionalJump(OpCode op, int a, int b, int c) + { + EncodeABC(op, a, b, c); + return Jump(); + } + + public void FixJump(int pc, int dest) + { + Assert(IsJumpListWalkable(pc)); + Assert(dest != NoJump); + var offset = dest - (pc + 1); + if (Math.Abs(offset) > MaxArgSBx) + P.Scanner.SyntaxError("control structure too long"); + Proto.CodeList[pc].SBx = (offset); + } + + public int Label() + { + LastTarget = Proto.CodeList.Length; + return LastTarget; + } + + public int Jump(int pc) + { + Assert(IsJumpListWalkable(pc)); + var offset = Proto.CodeList[pc].SBx; + if (offset != NoJump) + return pc + 1 + offset; + return NoJump; + } + + public bool IsJumpListWalkable(int list) + { + if (list == NoJump) + return true; + if (list < 0 || list >= Proto.CodeList.Length) + return false; + var offset = Proto.CodeList[list].SBx; + return offset == NoJump || IsJumpListWalkable(list + 1 + offset); + } + + public ref Instruction JumpControl(int pc) + { + if (pc >= 1 && TestTMode(Proto.CodeList[pc - 1].OpCode)) + return ref Proto.CodeList[pc - 1]; + return ref Proto.CodeList[pc]; + } + + public bool NeedValue(int list) + { + Assert(IsJumpListWalkable(list)); + for (; list != NoJump; list = Jump(list)) + { + if (JumpControl(list).OpCode != OpCode.TestSet) + return true; + } + + return false; + } + + public bool PatchTestRegister(int node, int register) + { + ref var i = ref JumpControl(node); + if (i.OpCode != OpCode.TestSet) + return false; + if (register != NoRegister && register != i.B) + i.A = register; + else + i = CreateABC(OpCode.Test, i.B, 0, i.C); + return true; + } + + public void RemoveValues(int list) + { + Assert(IsJumpListWalkable(list)); + for (; list != NoJump; list = Jump(list)) + { + PatchTestRegister(list, NoRegister); + } + } + + public void PatchListHelper(int list, int target, int register, int defaultTarget) + { + Assert(IsJumpListWalkable(list)); + + while (list != NoJump) + { + var next = Jump(list); + if (PatchTestRegister(list, register)) + { + FixJump(list, target); + } + else + { + FixJump(list, defaultTarget); + } + + list = next; + } + } + + public void DischargeJumpPc() + { + Assert(IsJumpListWalkable(JumpPc)); + PatchListHelper(JumpPc, Proto.CodeList.Length, NoRegister, Proto.CodeList.Length); + JumpPc = NoJump; + } + + public void PatchList(int list, int target) + { + if (target == Proto.CodeList.Length) + { + PatchToHere(list); + } + else + { + Assert(target < Proto.CodeList.Length); + PatchListHelper(list, target, NoRegister, target); + } + } + + public void PatchClose(int list, int level) + { + Assert(IsJumpListWalkable(list)); + level++; + for (int next; list != NoJump; list = next) + { + next = Jump(list); + Assert(Proto.CodeList[list].OpCode == OpCode.Jmp && Proto.CodeList[list].A == 0 || Proto.CodeList[list].A >= level); + Proto.CodeList[list].A = level; + } + } + + public void PatchToHere(int list) + { + Assert(IsJumpListWalkable(list)); + Assert(IsJumpListWalkable(JumpPc)); + Label(); + JumpPc = Concatenate(JumpPc, list); + Assert(IsJumpListWalkable(JumpPc)); + } + + public int Concatenate(int l1, int l2) + { + Assert(IsJumpListWalkable(l1)); + + if (l2 == NoJump) return l1; + if (l1 == NoJump) + { + return l2; + } + + var list = l1; + for (var next = Jump(list); next != NoJump;) + { + (list, next) = (next, Jump(next)); + } + + FixJump(list, l2); + return l1; + } + + public int AddConstant(LuaValue k, LuaValue v) + { + if (ConstantLookup.TryGetValue(k, out var index) && Proto.ConstantsList[index] == v) + { + return index; + } + + index = Proto.ConstantsList.Length; + ConstantLookup[k] = index; + Proto.ConstantsList.Add(v); + return index; + } + + public unsafe int NumberConstant(double n) + { + if (n == 0.0 || double.IsNaN(n)) + { + return AddConstant(*(long*)&n, n); + } + + return AddConstant(n, n); + } + + public void CheckStack(int n) + { + n += FreeRegisterCount; + if (n >= MaxStack) + { + P.Scanner.SyntaxError("function or expression too complex"); + } + else if (n > Proto.MaxStackSize) + { + Proto.MaxStackSize = n; + } + } + + public void ReserveRegisters(int n) + { + CheckStack(n); + FreeRegisterCount += n; + } + + public void FreeRegister(int r) + { + if (!IsConstant(r) && r >= ActiveVariableCount) + { + FreeRegisterCount--; + AssertEqual(r, FreeRegisterCount); + } + } + + public void FreeExpression(ExprDesc e) + { + if (e.Kind == Kind.NonRelocatable) + { + FreeRegister(e.Info); + } + } + + public int StringConstant(string s) + { + return AddConstant(s, s); + } + + public int BooleanConstant(bool b) + { + return AddConstant(b, b); + } + + public int NilConstant() + { + return AddConstant(default, default); + } + + public void SetReturns(ExprDesc e, int resultCount) + { + if (e.Kind == Kind.Call) + { + Instruction(e).C = resultCount + 1; + } + else if (e.Kind == Kind.VarArg) + { + Instruction(e).B = resultCount + 1; + Instruction(e).A = FreeRegisterCount; + ReserveRegisters(1); + } + } + + public ExprDesc SetReturn(ExprDesc e) + { + if (e.Kind == Kind.Call) + { + e.Kind = Kind.NonRelocatable; + e.Info = Instruction(e).A; + } + else if (e.Kind == Kind.VarArg) + { + Instruction(e).B = 2; + e.Kind = Kind.Relocatable; + } + + return e; + } + + public ExprDesc DischargeVariables(ExprDesc e) + { + switch (e.Kind) + { + case Kind.Local: + e.Kind = Kind.NonRelocatable; + break; + case Kind.UpValue: + e.Kind = Kind.Relocatable; + e.Info = EncodeABC(OpCode.GetUpVal, 0, e.Info, 0); + break; + case Kind.Indexed: + FreeRegister(e.Index); + { + if (e.TableType == Kind.Local) + { + FreeRegister(e.Table); + e.Kind = Kind.Relocatable; + e.Info = EncodeABC(OpCode.GetTable, 0, e.Table, e.Index); + } + else + { + e.Kind = Kind.Relocatable; + e.Info = EncodeABC(OpCode.GetTabUp, 0, e.Table, e.Index); + } + } + break; + case Kind.VarArg: + case Kind.Call: + e = SetReturn(e); + break; + } + + return e; + } + + public ExprDesc DischargeToRegister(ExprDesc e, int r) + { + e = DischargeVariables(e); + switch (e.Kind) + { + case Kind.Nil: + LoadNil(r, 1); + break; + case Kind.False: + EncodeABC(OpCode.LoadBool, r, 0, 0); + break; + case Kind.True: + EncodeABC(OpCode.LoadBool, r, 1, 0); + break; + case Kind.Constant: + EncodeConstant(r, e.Info); + break; + case Kind.Number: + EncodeConstant(r, NumberConstant(e.Value)); + break; + case Kind.Relocatable: + Instruction(e).A = r; + break; + case Kind.NonRelocatable: + if (r != e.Info) + { + EncodeABC(OpCode.Move, r, e.Info, 0); + } + + break; + default: + Assert(e.Kind == Kind.Void || e.Kind == Kind.Jump); + return e; + } + + e.Kind = Kind.NonRelocatable; + e.Info = r; + return e; + } + + public ExprDesc DischargeToAnyRegister(ExprDesc e) + { + if (e.Kind != Kind.NonRelocatable) + { + ReserveRegisters(1); + e = DischargeToRegister(e, FreeRegisterCount - 1); + } + + return e; + } + + public int EncodeLabel(int a, int b, int jump) + { + Label(); + return EncodeABC(OpCode.LoadBool, a, b, jump); + } + + public ExprDesc ExpressionToRegister(ExprDesc e, int r) + { + e = DischargeToRegister(e, r); + if (e.Kind == Kind.Jump) + { + e.T = Concatenate(e.T, e.Info); + } + + if (e.HasJumps()) + { + int loadFalse = NoJump; + int loadTrue = NoJump; + if (NeedValue(e.T) || NeedValue(e.F)) + { + int jump = NoJump; + if (e.Kind != Kind.Jump) jump = Jump(); + loadFalse = EncodeLabel(r, 0, 1); + loadTrue = EncodeLabel(r, 1, 0); + PatchToHere(jump); + } + + int end = Label(); + PatchListHelper(e.F, end, r, loadFalse); + PatchListHelper(e.T, end, r, loadTrue); + } + + e.F = e.T = NoJump; + e.Info = r; + e.Kind = Kind.NonRelocatable; + return e; + } + + public ExprDesc ExpressionToNextRegister(ExprDesc e) + { + e = DischargeVariables(e); + FreeExpression(e); + ReserveRegisters(1); + return ExpressionToRegister(e, FreeRegisterCount - 1); + } + + public ExprDesc ExpressionToAnyRegister(ExprDesc e) + { + e = DischargeVariables(e); + if (e.Kind == Kind.NonRelocatable) + { + if (!e.HasJumps()) + return e; + if (e.Info >= ActiveVariableCount) + { + return ExpressionToRegister(e, e.Info); + } + } + + return ExpressionToNextRegister(e); + } + + public ExprDesc ExpressionToAnyRegisterOrUpValue(ExprDesc e) + { + if (e.Kind != Kind.UpValue || e.HasJumps()) + { + e = ExpressionToAnyRegister(e); + } + + return e; + } + + public ExprDesc ExpressionToValue(ExprDesc e) + { + if (e.HasJumps()) return ExpressionToAnyRegister(e); + return DischargeVariables(e); + } + + public (ExprDesc, int) ExpressionToRegisterOrConstant(ExprDesc e) + { + e = ExpressionToValue(e); + switch (e.Kind) + { + case Kind.True: + case Kind.False: + if (Proto.ConstantsList.Length <= MaxIndexRK) + { + e.Info = BooleanConstant(e.Kind == Kind.True); + e.Kind = Kind.Constant; + return (e, AsConstant(e.Info)); + } + + break; + case Kind.Nil: + if (Proto.ConstantsList.Length <= MaxIndexRK) + { + e.Info = NilConstant(); + e.Kind = Kind.Constant; + return (e, AsConstant(e.Info)); + } + + break; + case Kind.Number: + e.Info = NumberConstant(e.Value); + e.Kind = Kind.Constant; + goto case Kind.Constant; + case Kind.Constant: + if (e.Info <= MaxIndexRK) + { + return (e, AsConstant(e.Info)); + } + + break; + } + + e = ExpressionToAnyRegister(e); + return (e, e.Info); + } + + public void StoreVariable(ExprDesc v, ExprDesc e) + { + switch (v.Kind) + { + case Kind.Local: + FreeExpression(e); + ExpressionToRegister(e, v.Info); + return; + case Kind.UpValue: + e = ExpressionToAnyRegister(e); + EncodeABC(OpCode.SetUpVal, e.Info, v.Info, 0); + break; + case Kind.Indexed: + var r = 0; + (e, r) = ExpressionToRegisterOrConstant(e); + EncodeABC(v.TableType == Kind.Local ? OpCode.SetTable : OpCode.SetTabUp, v.Table, v.Index, r); + + break; + default: + Unreachable(); + break; + } + + FreeExpression(e); + } + + public ExprDesc Self(ExprDesc e, ExprDesc key) + { + e = ExpressionToAnyRegister(e); + var r = e.Info; + FreeExpression(e); + var result = new ExprDesc { Info = FreeRegisterCount, Kind = Kind.NonRelocatable }; // base register for opSelf + ReserveRegisters(2); // function and 'self' produced by opSelf + (key, var k) = ExpressionToRegisterOrConstant(key); + EncodeABC(OpCode.Self, result.Info, r, k); + FreeExpression(key); + return result; + } + + public void InvertJump(int pc) + { + ref var i = ref JumpControl(pc); + Assert(TestTMode(i.OpCode) && i.OpCode is not (OpCode.TestSet or OpCode.Test)); + i.A = Not(i.A); + } + + public int JumpOnCondition(ExprDesc e, int cond) + { + if (e.Kind == Kind.Relocatable) + { + var i = Instruction(e); + if (i.OpCode == OpCode.Not) + { + DropLastInstruction(); // remove previous opNot + return ConditionalJump(OpCode.Test, i.B, 0, Not(cond)); + } + } + + e = DischargeToAnyRegister(e); + FreeExpression(e); + return ConditionalJump(OpCode.TestSet, NoRegister, e.Info, cond); + } + + public ExprDesc GoIfTrue(ExprDesc e) + { + var pc = NoJump; + e = DischargeVariables(e); + switch (e.Kind) + { + case Kind.Jump: + InvertJump(e.Info); + pc = e.Info; + break; + case Kind.Constant: + case Kind.Number: + case Kind.True: + break; + default: + pc = JumpOnCondition(e, 0); + break; + } + + e.F = Concatenate(e.F, pc); + PatchToHere(e.T); + e.T = NoJump; + return e; + } + + public ExprDesc GoIfFalse(ExprDesc e) + { + var pc = NoJump; + e = DischargeVariables(e); + switch (e.Kind) + { + case Kind.Jump: + pc = e.Info; + break; + case Kind.Nil: + case Kind.False: + break; + default: + pc = JumpOnCondition(e, 1); + break; + } + + e.T = Concatenate(e.T, pc); + PatchToHere(e.F); + e.F = NoJump; + return e; + } + + public ExprDesc EncodeNot(ExprDesc e) + { + e = DischargeVariables(e); + switch (e.Kind) + { + case Kind.Nil: + case Kind.False: + e.Kind = Kind.True; + break; + case Kind.Constant: + case Kind.Number: + case Kind.True: + e.Kind = Kind.False; + break; + case Kind.Jump: + InvertJump(e.Info); + break; + case Kind.Relocatable: + case Kind.NonRelocatable: + e = DischargeToAnyRegister(e); + FreeExpression(e); + e.Info = EncodeABC(OpCode.Not, 0, e.Info, 0); + e.Kind = Kind.Relocatable; + break; + default: + Unreachable(); + break; + } + + (e.T, e.F) = (e.F, e.T); + RemoveValues(e.F); + RemoveValues(e.T); + return e; + } + + public ExprDesc Indexed(ExprDesc t, ExprDesc k) + { + Assert(!t.HasJumps()); + var r = MakeExpression(Kind.Indexed, 0); + r.Table = t.Info; + var (_, i) = ExpressionToRegisterOrConstant(k); + r.Index = i; + if (t.Kind == Kind.UpValue) + r.TableType = Kind.UpValue; + else + { + Assert(t.Kind == Kind.NonRelocatable || t.Kind == Kind.Local); + r.TableType = Kind.Local; + } + + return r; + } + + + private static double Arith(OpCode op, double v1, double v2) + { + switch (op) + { + case OpCode.Add: + return v1 + v2; + case OpCode.Sub: + return v1 - v2; + case OpCode.Mul: + return v1 * v2; + case OpCode.Div: + return v1 / v2; + case OpCode.Mod: + return v1 - Math.Floor(v1 / v2) * v2; + case OpCode.Pow: + return Math.Pow(v1, v2); + case OpCode.Unm: + return -v1; + } + + throw new("not an arithmetic op code (" + op + ")"); + } + + public static (ExprDesc, bool) FoldConstants(OpCode op, ExprDesc e1, ExprDesc e2) + { + if (!e1.IsNumeral() || !e2.IsNumeral()) + return (e1, false); + if ((op == OpCode.Div || op == OpCode.Mod) && e2.Value == 0.0) + return (e1, false); + e1.Value = Arith(op, e1.Value, e2.Value); + return (e1, true); + } + + public ExprDesc EncodeArithmetic(OpCode op, ExprDesc e1, ExprDesc e2, int line) + { + var (e, folded) = FoldConstants(op, e1, e2); + if (folded) + return e; + var o2 = 0; + if (op != OpCode.Unm && op != OpCode.Len) + { + (e2, o2) = ExpressionToRegisterOrConstant(e2); + } + + (e1, var o1) = ExpressionToRegisterOrConstant(e1); + if (o1 > o2) + { + FreeExpression(e1); + FreeExpression(e2); + } + else + { + FreeExpression(e2); + FreeExpression(e1); + } + + e1.Info = EncodeABC(op, 0, o1, o2); + e1.Kind = Kind.Relocatable; + FixLine(line); + return e1; + } + + public ExprDesc Prefix(int op, ExprDesc e, int line) + { + switch (op) + { + case OprMinus: + if (e.IsNumeral()) + { + e.Value = -e.Value; + return e; + } + + return EncodeArithmetic(OpCode.Unm, ExpressionToAnyRegister(e), MakeExpression(Kind.Number, 0), line); + case OprNot: + return EncodeNot(e); + case OprLength: + return EncodeArithmetic(OpCode.Len, ExpressionToAnyRegister(e), MakeExpression(Kind.Number, 0), line); + } + + throw new("unreachable"); + } + + public ExprDesc Infix(int op, ExprDesc e) + { + switch (op) + { + case OprAnd: + e = GoIfTrue(e); + break; + case OprOr: + e = GoIfFalse(e); + break; + case OprConcat: + e = ExpressionToNextRegister(e); + break; + case OprAdd: + case OprSub: + case OprMul: + case OprDiv: + case OprMod: + case OprPow: + if (!e.IsNumeral()) + (e, _) = ExpressionToRegisterOrConstant(e); + break; + default: + (e, _) = ExpressionToRegisterOrConstant(e); + break; + } + + return e; + } + + public ExprDesc EncodeComparison(OpCode op, int cond, ExprDesc e1, ExprDesc e2) + { + (e1, var o1) = ExpressionToRegisterOrConstant(e1); + (e2, var o2) = ExpressionToRegisterOrConstant(e2); + FreeExpression(e2); + FreeExpression(e1); + if (cond == 0 && op != OpCode.Eq) + { + (o1, o2, cond) = (o2, o1, 1); + } + + return MakeExpression(Kind.Jump, ConditionalJump(op, cond, o1, o2)); + } + + public ExprDesc Postfix(int op, ExprDesc e1, ExprDesc e2, int line) + { + switch (op) + { + case OprAnd: + Assert(e1.T == NoJump); + e2 = DischargeVariables(e2); + e2.F = Concatenate(e2.F, e1.F); + return e2; + case OprOr: + Assert(e1.F == NoJump); + e2 = DischargeVariables(e2); + e2.T = Concatenate(e2.T, e1.T); + return e2; + case OprConcat: + e2 = ExpressionToValue(e2); + if (e2.Kind == Kind.Relocatable && Instruction(e2).OpCode == OpCode.Concat) + { + Assert(e1.Info == Instruction(e2).B - 1); + FreeExpression(e1); + Instruction(e2).B = (e1.Info); + return MakeExpression(Kind.Relocatable, e2.Info); + } + + return EncodeArithmetic(OpCode.Concat, e1, ExpressionToNextRegister(e2), line); + case OprAdd: + case OprSub: + case OprMul: + case OprDiv: + case OprMod: + case OprPow: + return EncodeArithmetic((OpCode)(op - OprAdd + (byte)OpCode.Add), e1, e2, line); + case OprEq: + case OprLT: + case OprLE: + return EncodeComparison((OpCode)(op - OprEq + (byte)OpCode.Eq), 1, e1, e2); + case OprNE: + case OprGT: + case OprGE: + return EncodeComparison((OpCode)(op - OprNE + (byte)OpCode.Eq), 0, e1, e2); + default: + throw new("unreachable"); + } + } + + public void FixLine(int line) => Proto.LineInfoList[Proto.CodeList.Length - 1] = line; + + + public void SetList(int @base, int elementCount, int storeCount) + { + Assert(storeCount != 0); + if (storeCount == MultipleReturns) + { + storeCount = 0; + } + + var c = (elementCount - 1) / ListItemsPerFlush + 1; + if (c <= MaxArgC) + { + EncodeABC(OpCode.SetList, @base, storeCount, c); + } + else if (c <= MaxArgAx) + { + EncodeABC(OpCode.SetList, @base, storeCount, 0); + EncodeExtraArg(c); + } + else + { + P.Scanner.SyntaxError("constructor too long"); + } + + FreeRegisterCount = @base + 1; + } + + public unsafe void CheckConflict(AssignmentTarget tv, ExprDesc e) + { + var extra = FreeRegisterCount; + var conflict = false; + var t = &tv; + while (t != null) + { + ref var d = ref t->Description; + if (d.Kind == Kind.Indexed) + { + if (d.TableType == e.Kind && d.Table == e.Info) + { + conflict = true; + d.Table = extra; + d.TableType = Kind.Local; + } + + if (e.Kind == Kind.Local && d.Index == e.Info) + { + conflict = true; + d.Index = extra; + } + } + + t = t->Previous; + } + + if (conflict) + { + if (e.Kind == Kind.Local) + { + EncodeABC(OpCode.Move, extra, e.Info, 0); + } + else + { + EncodeABC(OpCode.GetUpVal, extra, e.Info, 0); + } + + ReserveRegisters(1); + } + } + + public void AdjustAssignment(int variableCount, int expressionCount, ExprDesc e) + { + var extra = variableCount - expressionCount; + if (e.HasMultipleReturns()) + { + extra++; + if (extra < 0) + { + extra = 0; + } + + SetReturns(e, extra); + if (extra > 1) + { + ReserveRegisters(extra - 1); + } + } + else + { + if (expressionCount > 0) + { + ExpressionToNextRegister(e); + } + + if (extra > 0) + { + var r = FreeRegisterCount; + ReserveRegisters(extra); + LoadNil(r, extra); + } + } + } + + public int MakeUpValue(string name, ExprDesc e) + { + P.CheckLimit(Proto.UpValuesList.Length + 1, MaxUpValue, "upvalues"); + Proto.UpValuesList.Add(new() { Name = name, IsLocal = e.Kind == Kind.Local, Index = e.Info }); + return Proto.UpValuesList.Length - 1; + } + + public static (ExprDesc, bool) SingleVariableHelper(Function? f, string name, bool b) + { + static Block owningBlock(Block b1, int level) + { + while (b1.ActiveVariableCount > level) + { + b1 = b1.Previous!; + } + + return b1; + } + + ; + + static (int, bool) find(Function f, string name) + { + for (var i = f.ActiveVariableCount - 1; i >= 0; i--) + { + if (name == f.LocalVariable(i).Name) + { + return (i, true); + } + } + + return (0, false); + } + + ; + + static (int, bool) findUpValue(Function f, string name) + { + for (var i = 0; i < f.Proto.UpValuesList.Length; i++) + { + if (f.Proto.UpValuesList[i].Name == name) + { + return (i, true); + } + } + + return (0, false); + } + + ; + + if (f == null) + { + return default; + } + + var (v, found) = find(f, name); + if (found) + { + var e = MakeExpression(Kind.Local, v); + if (!b) + { + owningBlock(f.Block, v).HasUpValue = true; + } + + return (e, true); + } + + (v, found) = findUpValue(f, name); + if (found) + { + return (MakeExpression(Kind.UpValue, v), true); + } + + { + (var e, found) = SingleVariableHelper(f.Previous, name, false); + if (!found) + { + return (e, found); + } + + return (MakeExpression(Kind.UpValue, f.MakeUpValue(name, e)), true); + } + } + + public ExprDesc SingleVariable(string name) + { + var (e, found) = SingleVariableHelper(this, name, true); + if (!found) + { + (e, found) = SingleVariableHelper(this, "_ENV", true); + Assert(found && (e.Kind == Kind.Local || e.Kind == Kind.UpValue)); + e = Indexed(e, EncodeString(name)); + } + + return e; + } + + public (int pc, ExprDesc t) OpenConstructor() + { + var pc = EncodeABC(OpCode.NewTable, 0, 0, 0); + var t = ExpressionToNextRegister(MakeExpression(Kind.Relocatable, pc)); + return (pc, t); + } + + public void FlushFieldToConstructor(int tableRegister, int freeRegisterCount, ExprDesc k, Func 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 index e9118086..b56c6122 100644 --- a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs +++ b/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs @@ -1,1350 +1,28 @@ -using Lua.Internal; -using Lua.CodeAnalysis.Syntax; -using Lua.CodeAnalysis.Syntax.Nodes; -using Lua.Runtime; +using Lua.Runtime; -namespace Lua.CodeAnalysis.Compilation; - -public sealed class LuaCompiler : ISyntaxNodeVisitor +namespace Lua.CodeAnalysis.Compilation { - 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); - } + public static class LuaCompiler + { + /// + /// Lua bytecode signature. If the bytes start with this signature, they are considered as Lua bytecode. + /// + public static ReadOnlySpan LuaByteCodeSignature => Header.LuaSignature; + + /// + /// Converts a Lua bytecode to a Prototype object. + /// + /// binary bytecode + /// chunk name + /// + public static Prototype UnDump(ReadOnlySpan span, ReadOnlySpan name) => Parser.UnDump(span, name); + + /// + /// Converts a Prototype object to a Lua bytecode. + /// + /// Prototype object + /// true if the bytecode should be in little endian format, false if it should be in big endian format + /// binary bytecode + public static byte[] Dump(Prototype prototype, bool useLittleEndian = true) => Parser.Dump(prototype, useLittleEndian); } } \ 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..6295a569 --- /dev/null +++ b/src/Lua/CodeAnalysis/Compilation/Parser.cs @@ -0,0 +1,961 @@ +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; + +internal class Parser : IPoolNode, IDisposable +{ + /// inline + internal Scanner Scanner; + + internal int T => Scanner.Token.T; + internal bool TestNext(int token) => Scanner.TestNext(token); + internal void Next() => Scanner.Next(); + + internal Function Function = null!; + internal FastListCore ActiveVariables; + internal FastListCore