Skip to content
JakobOvrum edited this page Aug 19, 2011 · 11 revisions

LuaD Tutorial

(this tutorial is a work in progress)

LuaD is a bridge between the D and Lua programming languages. Unlike many other libraries built on the Lua C API, LuaD doesn't expose the Lua stack - instead, it has wrappers for references to Lua objects, and supports seamlessly and directly converting any D type into a Lua type and vice versa. This makes it very easy to use and encourages a much less error-prone style of programming, as well as boosting productivity by a substantial order. Due to D's powerful generic programming capabilities, performance remains the same as the equivalent using the C API.

LuaD is designed to be easy to use regardless of your knowledge of the Lua stack and the Lua C API - no usage of LuaD assumes any knowledge of the C API, and neither does this tutorial. But LuaD isn't just easy to use. Even if you are familiar with the C API, LuaD can drastically increase productivity and reduce bugs in your Lua handling code.

Typically, only code specifically interacting with a Lua state (see below) needs to use the LuaD API - the rest can be written as regular D code and the glue will be handled by LuaD. Sometimes you want to interact with Lua in ways that aren't clearly expressible in D; types wrapping Lua references are provided for these cases.

The Lua State

From the Lua manual:

Opaque structure that keeps the whole state of a Lua interpreter. The Lua library is fully reentrant: it has no global variables. All information about a state is kept in this structure.

In LuaD, a Lua state is provided by the LuaState class. Using Lua in a program always begins by creating a Lua state:

auto lua = new LuaState;

LuaState is the entry point to all LuaD functionality. It exposes the two basic windows into Lua: the global table and the registry table. It also exposes the ability to run Lua code or Lua scripts in the current instance. To use the LuaD API, import luad.all. Although not very interesting, a complete program could look like this:

import luad.all;

void main()
{
    auto lua = new LuaState;
}

The global table is used for looking up all non-local variables in Lua code. We can fill the global table with the functionality of the Lua standard library by calling the openLibs method. The print function, which writes to the standard output pipe, is one of the introduced globals. Let's expand the above example:

lua.openLibs();
lua.doString(`print("hello, world!")`);

As seen above, doString lets us run Lua code in our instance. Any errors which may have occurred during execution which weren't handled by Lua are thrown as a D exception.

Introducing new entries in a table is done by using the index assign operator, reminiscent of doing the same operation in Lua. The global table is available as the globals property of the LuaState:

lua.globals["message"] = "hello, world!";
lua.doString(`print(message)`);

The globals and registry members of LuaState are of type LuaTable. Through the globals table, we can read and write global variables and perform various other table-specific operations. The get, set, opIndex and opIndexAssign methods of LuaState are forwarded to globals for convenience; the next example is equivalent of the previous one:

lua["message"] = "hello, world!";
lua.doString(`print(message)`);

Type Conversions

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

lua[1] = true;
lua["test"] = 1.5;
lua["globals"] = lua.globals;
lua.doString(`
    assert(test == 1.5)
    assert(globals == _G)
    assert(_G[1] == true)
`);

Functions

Things become more interesting when you start using more complex types, like arrays, function pointers, delegates, structs and class instances (the full type mappings can be found here). As an exercise, lets 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 stdout 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)

Variadic templates are typically used for variadic functions with different parameter types in D (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!
`);

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(2, true) -- OK
    `);
}

LuaObject represents a reference to any Lua value, including nil. It provides the bare minimum functionality 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.

TODO: finish tutorial

Clone this wiki locally