-
Notifications
You must be signed in to change notification settings - Fork 36
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.
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)`);
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)
`);
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