Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Lua/LuaTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

namespace Lua;

public sealed class LuaTable : IEnumerable<KeyValuePair<LuaValue, LuaValue>>
public interface ILuaEnumerable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate files for this interface.

{
bool TryGetNext(LuaValue key, out KeyValuePair<LuaValue, LuaValue> pair);
}

public sealed class LuaTable : IEnumerable<KeyValuePair<LuaValue, LuaValue>>, ILuaEnumerable
{
public LuaTable() : this(8, 8)
{
Expand Down
38 changes: 32 additions & 6 deletions src/Lua/Standard/BasicLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,20 @@ public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, Cancella

public async ValueTask<int> IPairs(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
{
var arg0 = context.GetArgument<LuaTable>(0);
var arg0 = context.GetArgument(0);

LuaTable metatable = default;
if (arg0.TryRead(out LuaTable table))
{
metatable = table.Metatable;
}
else if (arg0.TryRead(out ILuaUserData userdata))
{
metatable = userdata.Metatable;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, use LuaState.TryGetMetatable.

}

// 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 (metatable != null && metatable.TryGetValue(Metamethods.IPairs, out var metamethod))
{
var stack = context.Thread.Stack;
var top = stack.Count;
Expand Down Expand Up @@ -218,10 +228,16 @@ public ValueTask<int> Load(LuaFunctionExecutionContext context, CancellationToke

public ValueTask<int> Next(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
{
var arg0 = context.GetArgument<LuaTable>(0);
var arg0 = context.GetArgument(0);
var arg1 = context.HasArgument(1) ? context.Arguments[1] : LuaValue.Nil;

if (arg0.TryGetNext(arg1, out var kv))
ILuaEnumerable enumerable = default;
if (arg0.TryRead(out LuaTable table))
enumerable = table;
else if (arg0.TryRead(out ILuaUserData userdata) && userdata is ILuaEnumerable)
enumerable = userdata as ILuaEnumerable;

if (enumerable != null && enumerable.TryGetNext(arg1, out var kv))
{
return new(context.Return(kv.Key, kv.Value));
}
Expand All @@ -233,10 +249,20 @@ public ValueTask<int> Next(LuaFunctionExecutionContext context, CancellationToke

public async ValueTask<int> Pairs(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
{
var arg0 = context.GetArgument<LuaTable>(0);
var arg0 = context.GetArgument(0);

LuaTable metatable = default;
if (arg0.TryRead(out LuaTable table))
{
metatable = table.Metatable;
}
else if (arg0.TryRead(out ILuaUserData userdata))
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, use LuaState.TryGetMetatable.

metatable = userdata.Metatable;
}

// 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 (metatable != null && metatable.TryGetValue(Metamethods.Pairs, out var metamethod))
{
var stack = context.Thread.Stack;
var top = stack.Count;
Expand Down
117 changes: 117 additions & 0 deletions tests/Lua.Tests/UserDataPairs/UserDataPairsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (C) 2021-2025 Steffen Itterheim
// Refer to included LICENSE file for terms and conditions.
Comment on lines +1 to +2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not your repository.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry about that, it's an automation in my IDE that adds these


using Lua.Runtime;
using Lua.Standard;
using Lua.Tests.Helpers;

namespace Lua.Tests.UserDataPairs;

public class UserDataPairsTests
{
[TestCase("userdatapairs.lua")]
public async Task Test_UserDataPairs(string file)
{
var state = LuaState.Create();
state.Platform.StandardIO = new TestStandardIO();
state.OpenStandardLibraries();
state.Environment["LuaList"] = new LuaValue(new LuaList<int>());

var path = FileHelper.GetAbsolutePath(file);
Directory.SetCurrentDirectory(Path.GetDirectoryName(path)!);
try
{
await state.DoFileAsync(Path.GetFileName(file));
}
catch (LuaRuntimeException e)
{
var luaTraceback = e.LuaTraceback;
if (luaTraceback == null)
{
throw;
}

var line = luaTraceback.FirstLine;
throw new($"{path}:{line} \n{e.InnerException}\n {e}");
}
}
}


public sealed class LuaList<T> : ILuaUserData, ILuaEnumerable
{
static readonly LuaFunction __len = new(Metamethods.Len, (context, _) =>
{
return new ValueTask<int>(context.Return(3));
});
static readonly LuaFunction __pairs = new(Metamethods.Pairs, (context, _) =>
{
var arg0 = context.GetArgument(0);
return new ValueTask<int>(context.Return(LuaListIterator, arg0, LuaValue.Nil));
});

static readonly LuaFunction LuaListIterator = new LuaFunction("listnext", (context, token) =>
{
var list = context.GetArgument<LuaList<T>>(0);
var key = context.HasArgument(1) ? context.Arguments[1] : LuaValue.Nil;

var index = -1;
if (key.Type is LuaValueType.Nil)
{
index = 0;
}
else if (key.TryRead(out int number) && number > 0 && number < list.ManagedArray.Length)
{
index = number;
}

if (index != -1)
{
return new(context.Return(++index, list.ManagedArray[index - 1]));
}

return new(context.Return(LuaValue.Nil));
});

static LuaTable s_Metatable;
public LuaTable Metatable { get => s_Metatable; set => throw new NotImplementedException(); }

public int[] ManagedArray { get; }
//public Dictionary<string, bool> ManagedDict { get; }
public LuaList()
{
ManagedArray = new [] { 1,2,3,4,5 };
//ManagedDict = new Dictionary<string, bool> {{"TRUE", true}, {"FALSE", false}};

s_Metatable = new LuaTable();
s_Metatable[Metamethods.Len] = __len;
s_Metatable[Metamethods.Pairs] = __pairs;
s_Metatable[Metamethods.IPairs] = __pairs;
}

public bool TryGetNext(LuaValue key, out KeyValuePair<LuaValue, LuaValue> pair)
{
var index = -1;
if (key.Type is LuaValueType.Nil)
{
index = 0;
}
else if (key.TryRead(out int integer) && integer > 0 && integer <= ManagedArray.Length)
{
index = integer;
}

if (index != -1)
{
var span = ManagedArray.AsSpan(index);
for (var i = 0; i < span.Length; i++)
{
pair = new(index + i + 1, span[i]);
return true;
}
}

pair = default;
return false;
}
}
51 changes: 51 additions & 0 deletions tests/Lua.Tests/UserDataPairs/userdatapairs.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
local iterations = 0

print("LuaList pairs:")
for k, v in pairs(LuaList) do
print("List[" .. tostring(k) .. "] = " .. tostring(v))
assert(k == v)
iterations = iterations + 1
end
assert(iterations == 5)


iterations = 0
print("LuaList ipairs:")
for i, v in ipairs(LuaList) do
print("List[" .. tostring(i) .. "] = " .. tostring(v))
assert(i == v)
iterations = iterations + 1
end
assert(iterations == 5)


iterations = 0
print("LuaList next:")
local i, v = next(LuaList, nil)
while i do
print("List[" .. tostring(i) .. "] = " .. tostring(v))
assert(i == v)
iterations = iterations + 1

i, v = next(LuaList, i)
end
assert(iterations == 5)


local t =
{
1, 2, 3, 4, 5,
["some key"] = "some value",
["another key"] = "another value",
[-1000] = -1001,
[_G] = print,
}

print("LuaTable pairs:")
for k, v in pairs(t) do
print(k, v)
end
print("LuaTable ipairs:")
for i, v in ipairs(t) do
print(i, v)
end