Browse Source

Merge pull request #8 from AnnulusGames/basic-lib

Add: basic library
Annulus Games 1 year ago
parent
commit
f8fa6d455c

+ 15 - 7
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -9,6 +9,11 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 {
     public static readonly LuaCompiler Default = new();
 
+    public Chunk Compile(string source, string? chunkName = null)
+    {
+        return Compile(LuaSyntaxTree.Parse(source, chunkName), chunkName);
+    }
+
     /// <summary>
     /// Returns a compiled chunk of the syntax tree.
     /// </summary>
@@ -272,11 +277,13 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
                 context.StackPosition = p;
             }
-        }
 
-        if (currentArrayChunkSize > 0)
-        {
-            context.PushInstruction(Instruction.SetList(tableRegisterIndex, (ushort)currentArrayChunkSize, arrayBlock), node.Position);
+            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);
@@ -1022,20 +1029,21 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             var expression = expressions[i];
             var remaining = expressions.Length - i + 1;
             var isLast = i == expressions.Length - 1;
+            var resultCount = isLast ? (minimumCount == -1 ? -1 : remaining) : 1;
 
             if (expression is CallFunctionExpressionNode call)
             {
-                CompileCallFunctionExpression(call, context, false, isLast ? remaining : 1);
+                CompileCallFunctionExpression(call, context, false, resultCount);
                 isLastFunction = isLast;
             }
             else if (expression is CallTableMethodExpressionNode method)
             {
-                CompileTableMethod(method, context, false, isLast ? remaining : 1);
+                CompileTableMethod(method, context, false, resultCount);
                 isLastFunction = isLast;
             }
             else if (expression is VariableArgumentsExpressionNode varArg)
             {
-                CompileVariableArgumentsExpression(varArg, context, isLast ? remaining : 1);
+                CompileVariableArgumentsExpression(varArg, context, resultCount);
                 isLastFunction = isLast;
             }
             else

+ 2 - 2
src/Lua/LuaFunctionExecutionContext.cs

@@ -39,11 +39,11 @@ public readonly record struct LuaFunctionExecutionContext
             var thread = State.CurrentThread;
             if (LuaValue.TryGetLuaValueType(typeof(T), out var type))
             {
-                LuaRuntimeException.BadArgument(State.GetTracebacks(), 1, thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+                LuaRuntimeException.BadArgument(State.GetTracebacks(), index + 1, thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             else
             {
-                LuaRuntimeException.BadArgument(State.GetTracebacks(), 1, thread.GetCurrentFrame().Function.Name, typeof(T).Name, arg.Type.ToString());
+                LuaRuntimeException.BadArgument(State.GetTracebacks(), index + 1, thread.GetCurrentFrame().Function.Name, typeof(T).Name, arg.Type.ToString());
             }
         }
 

+ 1 - 2
src/Lua/LuaState.cs

@@ -66,8 +66,7 @@ public sealed class LuaState
 
     public void Push(LuaValue value)
     {
-        ThrowIfRunning();
-        mainThread.Stack.Push(value);
+        CurrentThread.Stack.Push(value);
     }
 
     public LuaThread CreateThread(LuaFunction function, bool isProtectedMode = true)

+ 74 - 7
src/Lua/LuaTable.cs

@@ -1,5 +1,5 @@
 using System.Runtime.CompilerServices;
-using Lua.Runtime;
+using Lua.Internal;
 
 namespace Lua;
 
@@ -111,7 +111,7 @@ public sealed class LuaTable
             if (index > 0 && index <= array.Length)
             {
                 value = array[index - 1];
-                return true;
+                return value.Type is not LuaValueType.Nil;
             }
         }
 
@@ -133,6 +133,51 @@ public sealed class LuaTable
         return dictionary.ContainsKey(key);
     }
 
+    public KeyValuePair<LuaValue, LuaValue> GetNext(LuaValue key)
+    {
+        var index = -1;
+        if (key.Type is LuaValueType.Nil)
+        {
+            index = 0;
+        }
+        else if (TryGetInteger(key, out var integer) && integer > 0 && integer <= array.Length)
+        {
+            index = integer;
+        }
+
+        if (index != -1)
+        {
+            var span = array.AsSpan(index);
+            for (int i = 0; i < span.Length; i++)
+            {
+                if (span[i].Type is not LuaValueType.Nil)
+                {
+                    return new(index + i + 1, span[i]);
+                }
+            }
+
+            foreach (var pair in dictionary)
+            {
+                return pair;
+            }
+        }
+        else
+        {
+            var foundKey = false;
+            foreach (var pair in dictionary)
+            {
+                if (foundKey) return pair;
+
+                if (pair.Key.Equals(key))
+                {
+                    foundKey = true;
+                }
+            }
+        }
+
+        return default;
+    }
+
     public void Clear()
     {
         dictionary.Clear();
@@ -147,15 +192,37 @@ public sealed class LuaTable
     {
         if (array.Length >= newCapacity) return;
 
-        var newSize = array.Length;
-        if (newSize == 0) newSize = 8;
+        var prevLength = array.Length;
+        var newLength = array.Length;
+        if (newLength == 0) newLength = 8;
+
+        while (newLength < newCapacity)
+        {
+            newLength *= 2;
+        }
+
+        Array.Resize(ref array, newLength);
+
+        using var indexList = new PooledList<(int, LuaValue)>(dictionary.Count);
 
-        while (newSize < newCapacity)
+        // Move some of the elements of the hash part to a newly allocated array
+        foreach (var kv in dictionary)
         {
-            newSize *= 2;
+            if (kv.Key.TryRead<double>(out var d) && MathEx.IsInteger(d))
+            {
+                var index = (int)d;
+                if (index > prevLength && index <= newLength)
+                {
+                    indexList.Add((index, kv.Value));
+                }
+            }
         }
 
-        Array.Resize(ref array, newSize);
+        foreach ((var index, var value) in indexList.AsSpan())
+        {
+            dictionary.Remove(index);
+            array[index - 1] = value;
+        }
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 5 - 5
src/Lua/Runtime/Closure.cs

@@ -7,7 +7,7 @@ public sealed class Closure : LuaFunction
     Chunk proto;
     FastListCore<UpValue> upValues;
 
-    public Closure(LuaState state, Chunk proto)
+    public Closure(LuaState state, Chunk proto, LuaTable? environment = null)
     {
         this.proto = proto;
 
@@ -15,7 +15,7 @@ public sealed class Closure : LuaFunction
         for (int i = 0; i < proto.UpValues.Length; i++)
         {
             var description = proto.UpValues[i];
-            var upValue = GetUpValueFromDescription(state, proto, description);
+            var upValue = GetUpValueFromDescription(state, environment == null ? state.EnvUpValue : UpValue.Closed(state.CurrentThread, environment), proto, description);
             upValues.Add(upValue);
         }
     }
@@ -30,7 +30,7 @@ public sealed class Closure : LuaFunction
         return LuaVirtualMachine.ExecuteClosureAsync(context.State, this, context.State.CurrentThread.GetCurrentFrame(), buffer, cancellationToken);
     }
 
-    static UpValue GetUpValueFromDescription(LuaState state, Chunk proto, UpValueInfo description)
+    static UpValue GetUpValueFromDescription(LuaState state, UpValue envUpValue, Chunk proto, UpValueInfo description)
     {
         if (description.IsInRegister)
         {
@@ -39,11 +39,11 @@ public sealed class Closure : LuaFunction
         }
         else if (description.Index == -1) // -1 is global environment
         {
-            return state.EnvUpValue;
+            return envUpValue;
         }
         else
         {
-            return GetUpValueFromDescription(state, proto.Parent!, proto.Parent!.UpValues[description.Index]);
+            return GetUpValueFromDescription(state, envUpValue, proto.Parent!, proto.Parent!.UpValues[description.Index]);
         }
     }
 }

+ 2 - 0
src/Lua/Runtime/Metamethods.cs

@@ -18,5 +18,7 @@ public static class Metamethods
     public const string Le = "__le";
     public const string Call = "__call";
     public const string Concat = "__concat";
+    public const string Pairs = "__pairs";
+    public const string IPairs = "__ipairs";
     public new const string ToString = "__tostring";
 }

+ 1 - 1
src/Lua/Standard/Base/AssertFunction.cs → src/Lua/Standard/Basic/AssertFunction.cs

@@ -1,4 +1,4 @@
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class AssertFunction : LuaFunction
 {

+ 13 - 0
src/Lua/Standard/Basic/CollectGarbageFunction.cs

@@ -0,0 +1,13 @@
+namespace Lua.Standard.Basic;
+
+public sealed class CollectGarbageFunction : LuaFunction
+{
+    public override string Name => "collectgarbage";
+    public static readonly CollectGarbageFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        GC.Collect();
+        return new(0);
+    }
+}

+ 22 - 0
src/Lua/Standard/Basic/DoFileFunction.cs

@@ -0,0 +1,22 @@
+using Lua.CodeAnalysis.Compilation;
+using Lua.Runtime;
+
+namespace Lua.Standard.Basic;
+
+public sealed class DoFileFunction : LuaFunction
+{
+    public override string Name => "dofile";
+    public static readonly DoFileFunction Instance = new();
+
+    protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<string>(0);
+
+        // do not use LuaState.DoFileAsync as it uses the new LuaFunctionExecutionContext
+        var text = await File.ReadAllTextAsync(arg0, cancellationToken);
+        var fileName = Path.GetFileName(arg0);
+        var chunk = LuaCompiler.Default.Compile(text, fileName);
+
+        return await new Closure(context.State, chunk).InvokeAsync(context, buffer, cancellationToken);
+    }
+}

+ 1 - 1
src/Lua/Standard/Base/ErrorFunction.cs → src/Lua/Standard/Basic/ErrorFunction.cs

@@ -1,4 +1,4 @@
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class ErrorFunction : LuaFunction
 {

+ 2 - 2
src/Lua/Standard/Base/GetMetatableFunction.cs → src/Lua/Standard/Basic/GetMetatableFunction.cs

@@ -1,7 +1,7 @@
 
 using Lua.Runtime;
 
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class GetMetatableFunction : LuaFunction
 {
@@ -11,7 +11,7 @@ public sealed class GetMetatableFunction : LuaFunction
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
         var arg0 = context.ReadArgument(0);
-        
+
         if (arg0.TryRead<LuaTable>(out var table))
         {
             if (table.Metatable == null)

+ 50 - 0
src/Lua/Standard/Basic/IPairsFunction.cs

@@ -0,0 +1,50 @@
+using Lua.Runtime;
+
+namespace Lua.Standard.Basic;
+
+public sealed class IPairsFunction : LuaFunction
+{
+    public override string Name => "ipairs";
+    public static readonly IPairsFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<LuaTable>(0);
+
+        // If table has a metamethod __ipairs, calls it with table as argument and returns the first three results from the call.
+        if (arg0.Metatable != null && arg0.Metatable.TryGetValue(Metamethods.IPairs, out var metamethod))
+        {
+            if (!metamethod.TryRead<LuaFunction>(out var function))
+            {
+                LuaRuntimeException.AttemptInvalidOperation(context.State.GetTracebacks(), "call", metamethod);
+            }
+
+            return function.InvokeAsync(context, buffer, cancellationToken);
+        }
+
+        buffer.Span[0] = new Iterator(arg0);
+        return new(1);
+    }
+
+    class Iterator(LuaTable table) : LuaFunction
+    {
+        int i;
+
+        protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+        {
+            i++;
+            if (table.TryGetValue(i, out var value))
+            {
+                buffer.Span[0] = i;
+                buffer.Span[1] = value;
+            }
+            else
+            {
+                buffer.Span[0] = LuaValue.Nil;
+                buffer.Span[1] = LuaValue.Nil;
+            }
+
+            return new(2);
+        }
+    }
+}

+ 35 - 0
src/Lua/Standard/Basic/LoadFileFunction.cs

@@ -0,0 +1,35 @@
+using Lua.CodeAnalysis.Compilation;
+using Lua.Runtime;
+
+namespace Lua.Standard.Basic;
+
+public sealed class LoadFileFunction : LuaFunction
+{
+    public override string Name => "loadfile";
+    public static readonly LoadFileFunction Instance = new();
+
+    protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        // Lua-CSharp does not support binary chunks, the mode argument is ignored.
+        var arg0 = context.ReadArgument<string>(0);
+        var arg2 = context.ArgumentCount >= 3
+            ? context.ReadArgument<LuaTable>(2)
+            : null;
+
+        // do not use LuaState.DoFileAsync as it uses the new LuaFunctionExecutionContext
+        try
+        {
+            var text = await File.ReadAllTextAsync(arg0, cancellationToken);
+            var fileName = Path.GetFileName(arg0);
+            var chunk = LuaCompiler.Default.Compile(text, fileName);
+            buffer.Span[0] = new Closure(context.State, chunk, arg2);
+            return 1;
+        }
+        catch (Exception ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            return 2;
+        }
+    }
+}

+ 51 - 0
src/Lua/Standard/Basic/LoadFunction.cs

@@ -0,0 +1,51 @@
+using Lua.CodeAnalysis.Compilation;
+using Lua.Runtime;
+
+namespace Lua.Standard.Basic;
+
+public sealed class LoadFunction : LuaFunction
+{
+    public override string Name => "load";
+    public static readonly LoadFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        // Lua-CSharp does not support binary chunks, the mode argument is ignored.
+        var arg0 = context.ReadArgument(0);
+
+        var arg1 = context.ArgumentCount >= 2
+            ? context.ReadArgument<string>(1)
+            : null;
+
+        var arg3 = context.ArgumentCount >= 4
+            ? context.ReadArgument<LuaTable>(3)
+            : null;
+
+        // do not use LuaState.DoFileAsync as it uses the new LuaFunctionExecutionContext
+        try
+        {
+            if (arg0.TryRead<string>(out var str))
+            {
+                var chunk = LuaCompiler.Default.Compile(str, arg1 ?? "chunk");
+                buffer.Span[0] = new Closure(context.State, chunk, arg3);
+                return new(1);
+            }
+            else if (arg0.TryRead<LuaFunction>(out var function))
+            {
+                // TODO: 
+                throw new NotImplementedException();
+            }
+            else
+            {
+                LuaRuntimeException.BadArgument(context.State.GetTracebacks(), 1, Name);
+                return default; // dummy
+            }
+        }
+        catch (Exception ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            return new(2);
+        }
+    }
+}

+ 18 - 0
src/Lua/Standard/Basic/NextFunction.cs

@@ -0,0 +1,18 @@
+namespace Lua.Standard.Basic;
+
+public sealed class NextFunction : LuaFunction
+{
+    public override string Name => "next";
+    public static readonly NextFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<LuaTable>(0);
+        var arg1 = context.ArgumentCount >= 2 ? context.Arguments[1] : LuaValue.Nil;
+
+        var kv = arg0.GetNext(arg1);
+        buffer.Span[0] = kv.Key;
+        buffer.Span[1] = kv.Value;
+        return new(2);
+    }
+}

+ 37 - 0
src/Lua/Standard/Basic/PCallFunction.cs

@@ -0,0 +1,37 @@
+using Lua.Internal;
+
+namespace Lua.Standard.Basic;
+
+public sealed class PCallFunction : LuaFunction
+{
+    public override string Name => "pcall";
+    public static readonly PCallFunction Instance = new();
+
+    protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<LuaFunction>(0);
+
+        try
+        {
+            using var methodBuffer = new PooledArray<LuaValue>(1024);
+
+            var resultCount = await arg0.InvokeAsync(context with
+            {
+                State = context.State,
+                ArgumentCount = context.ArgumentCount - 1,
+                StackPosition = context.StackPosition + 1,
+            }, methodBuffer.AsMemory(), cancellationToken);
+
+            buffer.Span[0] = true;
+            methodBuffer.AsSpan()[..resultCount].CopyTo(buffer.Span[1..]);
+
+            return resultCount + 1;
+        }
+        catch (Exception ex)
+        {
+            buffer.Span[0] = false;
+            buffer.Span[1] = ex.Message;
+            return 2;
+        }
+    }
+}

+ 42 - 0
src/Lua/Standard/Basic/PairsFunction.cs

@@ -0,0 +1,42 @@
+using Lua.Runtime;
+
+namespace Lua.Standard.Basic;
+
+public sealed class PairsFunction : LuaFunction
+{
+    public override string Name => "pairs";
+    public static readonly PairsFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<LuaTable>(0);
+
+        // If table has a metamethod __pairs, calls it with table as argument and returns the first three results from the call.
+        if (arg0.Metatable != null && arg0.Metatable.TryGetValue(Metamethods.Pairs, out var metamethod))
+        {
+            if (!metamethod.TryRead<LuaFunction>(out var function))
+            {
+                LuaRuntimeException.AttemptInvalidOperation(context.State.GetTracebacks(), "call", metamethod);
+            }
+
+            return function.InvokeAsync(context, buffer, cancellationToken);
+        }
+
+        buffer.Span[0] = new Iterator(arg0);
+        return new(1);
+    }
+
+    class Iterator(LuaTable table) : LuaFunction
+    {
+        LuaValue key;
+
+        protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+        {
+            var kv = table.GetNext(key);
+            buffer.Span[0] = kv.Key;
+            buffer.Span[1] = kv.Value;
+            key = kv.Key;
+            return new(2);
+        }
+    }
+}

+ 1 - 1
src/Lua/Standard/Base/PrintFunction.cs → src/Lua/Standard/Basic/PrintFunction.cs

@@ -1,4 +1,4 @@
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class PrintFunction : LuaFunction
 {

+ 6 - 2
src/Lua/Standard/Base/RawEqualFunction.cs → src/Lua/Standard/Basic/RawEqualFunction.cs

@@ -1,5 +1,5 @@
 
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class RawEqualFunction : LuaFunction
 {
@@ -8,6 +8,10 @@ public sealed class RawEqualFunction : LuaFunction
 
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
-        throw new NotImplementedException();
+        var arg0 = context.ReadArgument(0);
+        var arg1 = context.ReadArgument(1);
+
+        buffer.Span[0] = arg0 == arg1;
+        return new(1);
     }
 }

+ 1 - 1
src/Lua/Standard/Base/RawGetFunction.cs → src/Lua/Standard/Basic/RawGetFunction.cs

@@ -1,5 +1,5 @@
 
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class RawGetFunction : LuaFunction
 {

+ 28 - 0
src/Lua/Standard/Basic/RawLenFunction.cs

@@ -0,0 +1,28 @@
+
+namespace Lua.Standard.Basic;
+
+public sealed class RawLenFunction : LuaFunction
+{
+    public override string Name => "rawlen";
+    public static readonly RawLenFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument(0);
+
+        if (arg0.TryRead<LuaTable>(out var table))
+        {
+            buffer.Span[0] = table.ArrayLength;
+        }
+        else if (arg0.TryRead<string>(out var str))
+        {
+            buffer.Span[0] = str.Length;
+        }
+        else
+        {
+            LuaRuntimeException.BadArgument(context.State.GetTracebacks(), 2, Name, [LuaValueType.String, LuaValueType.Table]);
+        }
+
+        return new(1);
+    }
+}

+ 1 - 1
src/Lua/Standard/Base/RawSetFunction.cs → src/Lua/Standard/Basic/RawSetFunction.cs

@@ -1,5 +1,5 @@
 
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class RawSetFunction : LuaFunction
 {

+ 37 - 0
src/Lua/Standard/Basic/SelectFunction.cs

@@ -0,0 +1,37 @@
+namespace Lua.Standard.Basic;
+
+public sealed class SelectFunction : LuaFunction
+{
+    public override string Name => "select";
+    public static readonly SelectFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument(0);
+        
+        if (arg0.TryRead<double>(out var d))
+        {
+            if (!MathEx.IsInteger(d))
+            {
+                throw new LuaRuntimeException(context.State.GetTracebacks(), "bad argument #1 to 'select' (number has no integer representation)");
+            }
+
+            var index = (int)d;
+
+            var span = context.Arguments[index..];
+            span.CopyTo(buffer.Span);
+
+            return new(span.Length);
+        }
+        else if (arg0.TryRead<string>(out var str) && str == "#")
+        {
+            buffer.Span[0] = context.ArgumentCount - 1;
+            return new(1);
+        }
+        else
+        {
+            LuaRuntimeException.BadArgument(context.State.GetTracebacks(), 1, Name, "number", arg0.Type.ToString());
+            return default;
+        }
+    }
+}

+ 2 - 2
src/Lua/Standard/Base/SetMetatableFunction.cs → src/Lua/Standard/Basic/SetMetatableFunction.cs

@@ -1,7 +1,7 @@
 
 using Lua.Runtime;
 
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class SetMetatableFunction : LuaFunction
 {
@@ -12,7 +12,7 @@ public sealed class SetMetatableFunction : LuaFunction
     {
         var arg0 = context.ReadArgument<LuaTable>(0);
         var arg1 = context.ReadArgument(1);
-        
+
         if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
         {
             LuaRuntimeException.BadArgument(context.State.GetTracebacks(), 2, Name, [LuaValueType.Nil, LuaValueType.Table]);

+ 69 - 0
src/Lua/Standard/Basic/ToNumberFunction.cs

@@ -0,0 +1,69 @@
+using System.Globalization;
+
+namespace Lua.Standard.Basic;
+
+public sealed class ToNumberFunction : LuaFunction
+{
+    public override string Name => "tonumber";
+    public static readonly ToNumberFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument(0);
+        var arg1 = context.ArgumentCount >= 2
+            ? (int)context.ReadArgument<double>(1)
+            : 10;
+
+        if (arg1 < 2 || arg1 > 36)
+        {
+            throw new LuaRuntimeException(context.State.GetTracebacks(), "bad argument #2 to 'tonumber' (base out of range)");
+        }
+
+        if (arg0.Type is LuaValueType.Number)
+        {
+            buffer.Span[0] = arg0;
+        }
+        else if (arg0.TryRead<string>(out var str))
+        {
+            if ((arg1 == 10 || arg1 == 16) && str.Length >= 3 && str[0] == '0' && str[1] == 'x')
+            {
+                if (int.TryParse(str.AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
+                {
+                    buffer.Span[0] = result;
+                }
+                else
+                {
+                    buffer.Span[0] = LuaValue.Nil;
+                }
+            }
+            else if (arg0 == 10)
+            {
+                if (double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out double result))
+                {
+                    buffer.Span[0] = result;
+                }
+                else
+                {
+                    buffer.Span[0] = LuaValue.Nil;
+                }
+            }
+            else
+            {
+                try
+                {
+                    buffer.Span[0] = Convert.ToInt64(str, arg1);
+                }
+                catch (FormatException)
+                {
+                    buffer.Span[0] = LuaValue.Nil;
+                }
+            }
+        }
+        else
+        {
+            buffer.Span[0] = LuaValue.Nil;
+        }
+
+        return new(1);
+    }
+}

+ 1 - 1
src/Lua/Standard/Base/ToStringFunction.cs → src/Lua/Standard/Basic/ToStringFunction.cs

@@ -1,4 +1,4 @@
-namespace Lua.Standard.Base;
+namespace Lua.Standard.Basic;
 
 public sealed class ToStringFunction : LuaFunction
 {

+ 27 - 0
src/Lua/Standard/Basic/TypeFunction.cs

@@ -0,0 +1,27 @@
+namespace Lua.Standard.Basic;
+
+public sealed class TypeFunction : LuaFunction
+{
+    public override string Name => "type";
+    public static readonly TypeFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument(0);
+        
+        buffer.Span[0] = arg0.Type switch
+        {
+            LuaValueType.Nil => "nil",
+            LuaValueType.Boolean => "boolean",
+            LuaValueType.String => "string",
+            LuaValueType.Number => "number",
+            LuaValueType.Function => "function",
+            LuaValueType.Thread => "thread",
+            LuaValueType.UserData => "userdata",
+            LuaValueType.Table => "table",
+            _ => throw new NotImplementedException(),
+        };
+
+        return new(1);
+    }
+}

+ 52 - 0
src/Lua/Standard/Basic/XPCallFunction.cs

@@ -0,0 +1,52 @@
+using Lua.Internal;
+
+namespace Lua.Standard.Basic;
+
+public sealed class XPCallFunction : LuaFunction
+{
+    public override string Name => "xpcall";
+    public static readonly XPCallFunction Instance = new();
+
+    protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<LuaFunction>(0);
+        var arg1 = context.ReadArgument<LuaFunction>(1);
+
+        using var methodBuffer = new PooledArray<LuaValue>(1024);
+        methodBuffer.AsSpan().Clear();
+
+        try
+        {
+            var resultCount = await arg0.InvokeAsync(context with
+            {
+                State = context.State,
+                ArgumentCount = context.ArgumentCount - 2,
+                StackPosition = context.StackPosition + 2,
+            }, methodBuffer.AsMemory(), cancellationToken);
+
+            buffer.Span[0] = true;
+            methodBuffer.AsSpan()[..resultCount].CopyTo(buffer.Span[1..]);
+
+            return resultCount + 1;
+        }
+        catch (Exception ex)
+        {
+            methodBuffer.AsSpan().Clear();
+
+            context.State.Push(ex.Message);
+
+            // invoke error handler
+            await arg1.InvokeAsync(context with
+            {
+                State = context.State,
+                ArgumentCount = 1,
+                StackPosition = null,
+            }, methodBuffer.AsMemory(), cancellationToken);
+
+            buffer.Span[0] = false;
+            buffer.Span[1] = ex.Message;
+
+            return 2;
+        }
+    }
+}

+ 16 - 2
src/Lua/Standard/OpenLibExtensions.cs

@@ -1,4 +1,4 @@
-using Lua.Standard.Base;
+using Lua.Standard.Basic;
 using Lua.Standard.Coroutines;
 using Lua.Standard.Mathematics;
 
@@ -12,9 +12,23 @@ public static class OpenLibExtensions
         PrintFunction.Instance,
         RawGetFunction.Instance,
         RawSetFunction.Instance,
+        RawEqualFunction.Instance,
+        RawLenFunction.Instance,
         GetMetatableFunction.Instance,
         SetMetatableFunction.Instance,
-        ToStringFunction.Instance
+        ToNumberFunction.Instance,
+        ToStringFunction.Instance,
+        CollectGarbageFunction.Instance,
+        NextFunction.Instance,
+        IPairsFunction.Instance,
+        PairsFunction.Instance,
+        TypeFunction.Instance,
+        PCallFunction.Instance,
+        XPCallFunction.Instance,
+        DoFileFunction.Instance,
+        LoadFileFunction.Instance,
+        LoadFunction.Instance,
+        SelectFunction.Instance,
     ];
 
     static readonly LuaFunction[] mathFunctions = [

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

@@ -14,4 +14,18 @@ public class TableTests
         Assert.That(table["bar"], Is.EqualTo(new LuaValue(2)));
         Assert.That(table[true], Is.EqualTo(new LuaValue("baz")));
     }
+
+    [Test]
+    public void Test_EnsureCapacity()
+    {
+        var table = new LuaTable(2, 2);
+        table[32] = 10; // hash part
+
+        for (int i = 1; i <= 31; i++)
+        {
+            table[i] = 10;
+        }
+
+        Assert.That(table[32], Is.EqualTo(new LuaValue(10)));
+    }
 }