After looking through various Lua interpreters for C#, it seems that only one is truly pure c# - MoonSharp. LuaInterpreter (defunct from 2009) which later became NLua depends on one of two other c# librariers KeraLua or another lib, and requires a customized lua52.dll (you can not use the one from lua.org, and). They have a bug report which is closed that says look at the readme for the download location of their customized lua52.dll, however it is absent. You are forced to download these libs from various sources and pray they work together in addition have a multi-file distribution which may have/cause compatibility issues with other programs due to several lua52.dll variations on the end users computer (assuming they will use more than just your program).
The one shining beacon of light on NLua is it's apparently popularity, however the project has not received any significant update in several years. MoonSharp on the other hand appears to be completely self-contained, yet is lacking in documentation for common tasks such as loading a table that was built with lua and working with it.
I have come up with the following code based on the singular example they provided on Git, and then duplicated on their site at moonsharp.org (whichever came first, i am unsure, but having 1 example is not sufficient) :
using System;
using System.IO;
using MoonSharp.Interpreter;
class Foo {
function Bar( string accountName, string warcraftPath ) {
string datastore = Path.Combine(warcraftPath, "WTF", "Account", accountName, "SavedVariables", "DataStore_Containers.lua";
DynValue table = Script.RunString( File.ReadAllText( datastore ) );
Console.WriteLine( table.Table.Keys.Count().ToString() );
}
}
Results in the following (code in picture is slightly different as I adjusted the pasted code here for cleanliness and to make it easier for you to reproduce the problem using the table data in the pastebin link below.)
The table I am trying to read looks like the following (simplified had to paste on pastebin due to the size exceeding 30,000 characters):
World of Warcraft - Datastore_Containers Lua table sample data
I sort of have something sort of working, it's a bit hackish, but doesn't seem to be away to loop through the values or explicitly get the subtables / values or key of the value.
Script s = new Script(CoreModules.Preset_Complete);
// hacked by appending ' return DataStore_ContainersDB ' to return the table as DoString seems to only work to run a function expecting a result to be returned.
DynValue dv = s.DoString(luaTable + "\nreturn DataStore_ContainersDB;");
Table t = dv.Table;
foreach(var v in t.Keys)
{
Console.WriteLine( v.ToPrintString() );
}
The problem is that there doesn't seem to be any way for me to enter the sub-table result sets or to explicitly access those like t["global"]
or t.global
.
Managed to hack and slash my way through this and come up with a working solution although it is fairly rudimentary (possible someone could take this concept and make accessing of the sub data more reasonable:
Script s = new Script(CoreModules.Preset_Complete);
DynValue dv = s.DoString(luaTable + "\nreturn DataStore_ContainersDB;");
Table t = dv.Table;
Table global;
global = t.Get("global").ToObject<Table>().Get("Characters").ToObject<Table>();
foreach (var key in global.Keys)
{
Console.WriteLine( key.ToString() );
}
The library MoonSharp appears to require and depend heavily upon the Script
class which is the premise by which all other methods operate. The DoString
method requires a return result or the DynValue
will always be void/null . DynValue
appears to be the base global handler for the entire Lua process which can handle methods (aka, that lua string could contain several methods which DynValue would expose and allow them to be called in C# returning the response as other DynValue's)
So if you wish to load a lua file that ONLY contains date in Lua's table format, you MUST append a return with the table name as the last line. This is why you see :
"\nreturn DataStore_ContainersDB;"
... as the table name is called "DataStore_ContainersDB"
Next, the result must be loaded into a fresh Table
object, as DynValue is not an actual table but a class construct to hold all the various formats available (methods, tables, etc).
After it is in a Table format, you can now work with it by calling the key/value pair by the key name, number, or DynValue. In my case, since I know the original key names, I call straight through to the Table where the key names exist which I do not know and would like to work with.
Table.Get( Key )
Since this returns a DynValue, we must then convert/load the object as a table again which is made convenient using the .ToObject<>
method.
The foreach
loop I supplied then loops through the keys available in the sub-table located at : global > Characters > *
... which I then write the key name out to the console using key.ToString()
If there is other sub-tables, in this example (as there are), you can traverse to unknown ones using the same concept in the foreach
loop by expanding on it like this :
foreach (var key in global.Keys)
{
if(IsTable(global.Get(key.String)))
{
Console.WriteLine("-------" + key.ToPrintString() + "-------");
Table characterData = global.Get(key.String).ToObject<Table>();
foreach (var characterDataField in characterData.Keys)
{
if( !IsTable(characterData.Get(characterDataField.String)))
{
Console.WriteLine(string.Format("{0} = {1}", characterDataField.ToPrintString(), characterData.Get(characterDataField.String).ToPrintString()));
}
else
{
Console.WriteLine(string.Format("{0} = {1}", characterDataField.ToPrintString(), "Table[]"));
}
}
Console.WriteLine("");
}
}
... and here is the method I wrote to quickly check if the data is a table or not . This is the IsTable()
method used in the above foreach
example.
private static bool IsTable(DynValue table)
{
switch (table.Type)
{
case DataType.Table:
return true;
case DataType.Boolean:
case DataType.ClrFunction:
case DataType.Function:
case DataType.Nil:
case DataType.Number:
case DataType.String:
case DataType.TailCallRequest:
case DataType.Thread:
case DataType.Tuple:
case DataType.UserData:
case DataType.Void:
case DataType.YieldRequest:
break;
}
return false;
}
I have done what I could to make this workable, however, as stated before, I do see room for improving the recursion of this. Checking the data type on every subobject, and then loading it just feels very redundant and seems like this could be simplified.
I am open to other solutions on this question, ideally in the form of some enhancement that would make this not so clunky to use.