Skip to content

Tutorial_Conversions

JakobOvrum edited this page Feb 16, 2013 · 7 revisions

LuaD Tutorial - Type Conversions

Previous chapter - the Lua State (chapter index)

In the previous section, string was the type of both the key and value in the call to the index assign operator. LuaD supports any D type for operations like this, some shown below:

lua[1] = true;
lua["test"] = 1.5;
lua["globals"] = lua.globals;
lua["array"] = [2];
lua.doString(`
    -- note that _G is a standard loopback reference to the global table
    assert(test == 1.5)
    assert(globals == _G)
    assert(_G[1] == true)
    assert(array[1] == 2)
`);

Getting Lua values into D is just as easy:

lua.doString(`
    test = 1.5
    _G[1] = true
    array = {2}
`);

assert(lua.get!double("test") == 1.5);
assert(lua.get!bool(1));
assert(lua.get!(int[])("array")[0] == 2);

The get function of LuaState actually just forwards to globals.get. With most types, it returns copies of the data. Hence, if we want to operate on a value and have the changes reflected in Lua, the code would look like this:

lua.doString(`foo = 1`);

int foo = lua.get!int("foo");
++foo;

lua.doString(`
    assert(foo == 1) -- 'foo' is still 1 on this side
`);

lua["foo"] = foo;

lua.doString(`
    assert(foo == 2) -- updated
`);

Some Lua types are not as easily expressible in D. For example, how do we get a large table like _G into D from Lua? We sure don't want to make a copy of it, and we'd like to be able to mutate it and see the effects immediately in Lua. This leads us back to the LuaTable reference wrapper type:

auto t = lua.get!LuaTable("_G");
assert(t == lua.globals);

// Introduce a new global
lua.doString(`foo = "bar"`);

auto foo = t.get!string("foo");
assert(foo == "bar"); // 't' is a reference to the global table, not a copy!

Wchar and dchar

You know how I said earlier that LuaD supports any D type? I lied. Exceptions are made for the types wchar and dchar and derivatives such as their array equivalents (e.g. wstring, dstring): they are explicitly rejected at compile-time. All Lua strings are 8-bit with no assumptions made about their encoding. This makes them a good fit for UTF-8 strings, represented in D by arrays of char (e.g. string). However, wchar and dchar are 16 and 32 bits respectively, making conversion to a Lua type ambiguous. The ambiguity must be solved by the programmer, for example by choosing to transcode to UTF-8 first:

wstring str = "foobar";
lua["str"] = to!string(str);

The rationale is that transcoding is an expensive operation, both in terms of computational complexity and actual time, which shouldn't be silently hidden away in the LuaD library. Also, depending on what the strings are given to Lua for, it may make more sense to push them as tables of numbers (representing code points) or as binary data; there is no clear default solution.

However, apart from these two types, any type combination is allowed. We will take a look at these various types later in the tutorial.

Functions

Things become more interesting when you start using more complex types, like functions. As an exercise, let's provide our own print function instead of using the Lua standard library. Lua's print takes a variable amount of arguments and writes them all to the standard output pipe separated by the tab character:

import std.stdio;
import luad.all;

void print(const(char)[][] params...)
{
    if(params.length > 0)
    {
        foreach(param; params[0..$-1])
            write(param, '\t');
        write(params[$-1]);
    }
    writeln();
}

void main()
{
    auto lua = new LuaState;
    lua["print"] = &print;
    lua.doString(`print("hello", "world!")`);
}

Our print function works as well in D as it does in Lua. One problem remains - Lua's print function actually accepts Lua values of any type, not just strings:

print(2)

Output:

luad.error.LuaErrorException@..\luad\state.d(52): [string "print(2)"]:1: bad argument #1 to 'print' (string expected, got number)

In D, variadic templates are typically used for variadic functions with differing parameter types (e.g. writeln). However, templates are instantiated at compile-time, making them inaccessible from Lua. We could use a specific template instance as our print function:

lua["print"] = &writeln!int;
lua.doString(`
    print(2) -- OK
    print(true) -- Error: bad argument #1 to 'print' (number expected, got boolean)
`);

But as we can see, this does not help us with our goal. In comes the LuaD reference wrappers:

void print(LuaObject[] params...)
{
    if(params.length > 0)
    {
        foreach(param; params[0..$-1])
            write(param.toString(), '\t');
        write(params[$-1].toString());
    }
    writeln();
}

void main()
{
    auto lua = new LuaState;
    lua["print"] = &print;
    lua.doString(`
        print("hello", "world!") -- OK
        print(true, 2) -- OK
    `);
}

LuaObject represents a reference to any Lua value, including nil. It provides the bare minimum functionality that is applicable to all Lua types; such as the toString method, which creates a text representation of the referenced value exactly like Lua's tostring function does. Our print function is now identical in functionality to the one provided in Lua's standard library.

What about the other way around, using Lua functions in D? Let's say we wanted to use the Lua standard library function os.execute. Here's the documentation on it from the Lua manual:

os.execute ([command])

This function is equivalent to the C function system. It passes command to be executed by an operating system shell. It returns a status code, which is system-dependent. If command is absent, then it returns nonzero if a shell is available and zero otherwise.

The most straight-forward approach is to fetch it as a D delegate:

auto execute = lua.get!(int delegate(in char[] command))("os", "execute");

int statusCode = execute("echo hello, world!");

writeln(statusCode == 0? "success!" : "failed!");

This also shows that you can use get to conveniently retrieve values nested deeper than one level. This is very important for efficiency too; getting each LuaTable in the link manually results in an unnecessary amount of boilerplate dealing with creating and releasing table references.

You can also use the reference wrapper type LuaFunction. This type is convenient, but also indispensable in various situations, such as when the behavior of the Lua function depends on the types of the arguments passed:

lua.doString(`
    function func(a)
        if type(a) == "number" then
            return a * 2
        elseif type(a) == "string" then
            return "hello, " .. a
        end

        error("unsupported argument type!")
    end
`);

auto func = lua.get!LuaFunction("func");

int integerResult = func.call!int(2);
assert(integerResult == 4);

string stringResult = func.call!string("world!");
assert(stringResult == "hello, world!");

We can also take a look at what happens if we let our Lua function raise an error, such as with this code:

func.call(true);
luad.error.LuaErrorException@..\luad\state.d(51): [string "..."]:9: unsupported argument type!

With the default constructor of LuaState, Lua errors are converted to D exceptions when crossing into D. You can catch LuaErrorException like you would with any other exception.

(This chapter is incomplete, contributions much appreciated)

Clone this wiki locally