Inline::Lua
This is a Raku module which allows execution of Lua code from Raku code
with inspiration, APIs, and techniques from Inline::Perl5 and Inline::Python.
Synopsis
use Inline::Lua;
my $L = Inline::Lua.new;
my $code = q:to/END/;
local args = {...}
local n = 0
for i = 1, args[1] do
n = n + i
end
return n
END
my $arg = 1e8;
my $sum;
$sum = $L.run: $code, $arg;
# OR
$L.run: "function sum (...)\n $code\n end";
$sum = $L.call: 'sum', $arg;
# OR
my &sum = $L.get-global: 'sum';
$sum = sum $arg;
say $sum;
# TODO show off tables
Requirements
Any Rakudo backend with a NativeCall implementation is expected to work, but
testing has only been done under MoarVM on x86-64 Linux.
Compatible with Lua 5.1 and LuaJIT. Support for other versions of Lua is
planned.
Status
Both Lua 5.1 and LuaJIT are supported. Evaluating Lua code works. A LuaJIT demo
game split across several files with OpenGL and SDL FFI bindings was even
successfully tested. LuaJIT can be explicitly enabled or disabled, but by
default will be auto-detected (which adds a little to loading time, regardless
of precompilation).
Any number of values can be passed to and returned from Lua. Simple values of
boolean, nil, number, string, and light userdata all work. Most common object
types work as well: table, function, full userdata, and cdata. Tables can be
accessed as arrays, hashes, objects, and roles. Functions work as values, subs,
and methods. Any Lua object may be called or indexed which supports it, even
via metatable behaviors. Full userdata as well as LuaJIT cdata also works for
metatable access or passing back in to another Lua call. See Values further
down.
Accessing referenced objects, table fields, and global variables can be done
from Raku without calling Lua code. Named global tables can be used as roles.
To Do
The API is incomplete, and error reporting is crude. The "Inline::" part is
arguably NYI, as the present interface is solely object-oriented.
There is no way to pass Raku-native constructs to Lua code, other than simple
copy conversion of values; directly accessing Raku data structures and calling
Raku code from Lua is not implemented.
Metatables are respected for calling and indexing semantics, however they
cannot yet be accessed directly nor are Lua operator overloads exposed in Raku.
Both light and full userdata are supported for passing and returning, but the
only way to create one from Raku is to pass in a Pointer. In particular, direct
support for Raku-native binary types like Buf/Blob does not exist.
Composing roles from Lua objects doesn't work well when multiple Inline::Lua
instances are in use. This is because it would be extremely difficult for the
user to provide the Lua table object itself at composition time and still have
their own code survive precompilation, so the table is specified as a name to
look up at runtime as a global variable. When using multiple instances, the
named global is looked up in the most recently created Inline::Lua instance,
which is stored in the Inline::Lua.default-lua class attribute.
No provisions are made for growing Lua's stack beyond its initial size (which
defaults to 20). Therefore, passing deeply-nested data structures to Lua may
result in an overflow.
Values
Inline::Lua currently allows passing and returning any number of boolean,
number, string, nil, table, function, light or full userdata, and cdata values
according to the following table.
Lua from Raku to Raku
nil * where {!.defined} Any
boolean Bool Bool
number Numeric Num
string Stringy Str
light userdata Pointer Pointer[void]
table/ any( Positional, Inline::Lua::Object; any of:
function/ Associative, Inline::Lua::Table
full userdata/ Inline::Lua::Object ) Inline::Lua::Function
cdata Inline::Lua::Userdata
Inline::Lua::Cdata
Not counting floating-point values like NaN, nil is the only "undefined" value
in Lua. All undefined Raku values will be translated as nil, and when it is
returned from Lua it will yield Any.
Numbers in default Lua are C doubles, and so are coerced to some type of Num
(possibly a variation of it like num64) when passing to and from Lua. Accessing
table keys (below) also applies this coercion.
Tables are exposed as Inline::Lua::Table instances, which can be accessed
directly with hash or array subscripts (including slicing), converted to a
.hash or .list, or used as an object (or role via LuaParent). See Usage below
for details. Arrays and hashes can also be passed in to Lua, and will be
translated as a newly-created table.
In contrast to tables acting as arrays, multiple return values (not packed in a
table) from Lua will result in an ordinary Raku list instead of an
Inline::Lua::Table.
Lua functions as values themselves are returned as Inline::Lua::Function
objects, which can be called like any other anonymous routine in Raku,
including assigning it to an &-sigiled variable to be able to call it with
ordinary-looking sub call syntax.
Full userdata and LuaJIT cdata types are exposed as Inline::Lua::Userdata and
Inline::Lua::Cdata. Light userdata is a simple pointer value (not an object),
and is passed to and from Raku as a NativeCall Pointer.
Metatables in Lua allow any object type to define behaviors for calling,
indexing, comparing, and calculating operators, even when the type in question
does not usually support such operations. This is allowed for by the object
types, meaning e.g. a ::Table may also function as a Raku Callable (though the
call will fail if there isn't a corresponding metatable handler).
A new Raku object is created for each Lua value being returned, making the
::Object types useless for identity comparison on the Raku side (e.g. === on
the same Lua table returned from separate calls will be False).
Inline::Lua::Objects can of course be passed back in to Lua, and represent the
same referenced Lua object which they were originally attached to.
Usage
For illustrative purposes, the signatures shown here may differ from the actual
implementation. Undocumented differences also include parameters meant for
internal use and experimental features.
Inline::Lua
Represents a Lua instance with its own global environment and internal stack.
Multiple Inline::Lua instances may be used, though passing ::Objects between
different instances is not supported, and using LuaParent does not work well
with multiple instances (both of which are described further down).
method new (:$lua, :$auto, :$lib)
Creates, initializes, and returns a new Inline::Lua instance with the standard
Lua libraries loaded.
Lua version is switched per Inline::Lua instance automatically, by trying JIT
first and falling back to standard Lua 5.1. To skip autodetection and use
LuaJIT explicitly, pass :lua<JIT>. To disable the auto-detection without using
LuaJIT, either pass another version (currently only 5.1), or pass :!auto to use
the non-JIT default.
To point to a specific library instead of only trying the standard name and
library paths, :lib can be passed to specify a more explicit name/path. In this
case all auto-detection is disabled and :lua is ignored.
method run (Str:D $code, *@args)
Compiles $code, runs it with @args, and returns any resulting value(s).
method call (Str:D $name, *@args)
Calls the named global function with @args, and returns any resulting value(s).
method get-global (Str:D $name)
Returns the value stored in the named global variable.
method set-global (Str:D $name, $value)
Sets the value of the named global variable.
Inline::Lua::Object
Base role for values which Lua regards as "objects". This role manages
references and allows the object to be pushed on to the Lua stack, none of
which is part of the intended public interface. None of the object types except
table can be meaningfully instantiated from Raku at this time, rather they
usually result from a value being returned to Raku from Lua.
All Lua objects also support what are called metatables, which hold callbacks
that may allow any object to respond to calls like a function, key access like
a table (which is also used to implement inheritance), and concatenation,
arithmetic, and comparison like a value. Inline::Lua::Objects support
metatable-backed indexing and invocation, but do not perform any Raku operator
overloading. In particular this means that /all/ Inline::Lua::Objects may be
used as a Raku hash, array, object, role, or routine, and include the
Positional, Associative, and Callable roles, even if it is not an object type
which directly supports such features.
To keep this documentation understandable, the expected features are documented
under e.g. ::Function and ::Table, even when such features also work on any
object via metatables. Some methods, however, are truly generic to all object
types regardless of metatable, and so are documented directly below.
method length ()
Returns Lua's idea of the "length" of the object. Without a metatable, this
will return 0 for objects other than table, full userdata, or cdata. Note that
this is not precisely the same as Raku's idea of length, as documented under
Inline::Lua::Table.elems() .
method ptr ()
Returns a NativeCall Pointer[void] to the object.
Inline::Lua::Function
A Lua function which can be used directly from Raku. It can be executed like
any other Routine, and is often stored in an &-sigiled variable to call like a
named Raku sub. Parameters are exposed in Raku as slurpy positionals with no
current regard for the actual parameter list of the Lua function.
Inline::Lua::Table
A table in Lua represents the concepts which Raku regards as variously arrays,
hashes, objects, classes, roles, and more. Therefore this type attempts to
provide an interface as all of these things. Underneath, however, calls to a
::Table perform the operation on the referenced Lua table, not a Raku copy.
All the usual Positional and Associative subscripts work on a ::Table including
slicing and possibly various adverbs (untested).
When accessed as a hash, a ::Table appears as an object hash (:{} or
Hash[Mu,Any] in Raku code), in keeping with the semantics of Lua tables. All
numeric keys will be Num (or possibly some precision and/or native variant
thereof) because default Lua handles any number as a C double. Numeric values
used as hash subscripts to a ::Table will be automatically coerced to a num.
Positional indices, on the other hand, are treated as integers in Raku as
always. Lua tables use 1-based indexing when treated as an array, while Raku
uses zero-based indexing. When accessed as an array, the index is offset
accordingly. In other words, $table[0] is the same element as $table{1}.
A method call which cannot be resolved by the ::Table object is attempted as a
method call or attribute access on the table by the usual Lua OO conventions,
allowing a table to be seemlessly used as an object from Raku code, as long as
required method and attribute names don't overlap with any existing methods in
Inline::Lua::Table's inheritance tree. For ways around this limitation, see
.invoke(), .obj(), and LuaParent, below.
Unlike objects behaving as tables via metatable-backed indexing, an actual
::Table can be iterated over and its full set of keys and values can be known.
This means that tables can accept list assignments in Raku (though they
intentionally do not themselves flatten in list context), and they also are
able to provide the .list, .hash and related methods. While these
iteration-backed features only work on ::Table itself, table-like array and
hash subscripting is supported for all ::Objects with suitable metamethods, as
well as the OO features described later in this class like .invoke and .obj.
method new (Inline::Lua:D :$lua!)
Creates a new empty table in the given Inline::Lua instance and returns it.
method hash ()
method keys ()
method values ()
method kv ()
method pairs ()
These methods return a shallow copy of the table which is independent of the
original Lua object. The structure returned is the same as the corresponding
Hash methods, with the exception that .hash returns an object hash
(Hash[Mu,Any]) instead of Raku's default (Hash[Mu,Str]). This difference is
entirely transparent if the values are stored via ordinary hash assignment
(e.g. my %results = some-lua-func().hash), since the keys will be coerced to
strings when being assigned to %results.
method list ()
Returns a shallow copy of the positional portion of the table, which is
independent of the original Lua object.
method elems ()
method end ()
Lua has a nondeterministic notion of where the end of a sparse array is, while
Raku always considers the end to be after the defined element with the highest
index, which always includes any holes within the array. Raku arguably doesn't
even support arrays with missing elements so much as arrays with undefined
elements, unless using a type of hash instead, as Lua does. To compensate, a
bit of slower but far more correct code iterates over all the table's keys to
find the highest defined whole number key, instead of Lua's length operation.
This is also done when the end needs to be found for other operations like
.list or slicing/indexing with Whatever ([*]) and WhateverCode ([*-1]).
method invoke ($method, Bool:D :$call = True, *@args)
Calls the named method using the table as the invocant, also passing @args, and
returns the result. Notably, this is currently the only 100% guaranteed way to
call a Lua method on a ::Table object which might be masked out by a Raku
method.
Besides a method name string, $method can also be an Inline::Lua::Function (or
even any other Raku Callable object) which will be called directly instead of
retrieving the method by name from the table. Passing a Callable directly is
mainly intended to allow a method to be looked up before hand to skip the table
key lookup, value return, and associated marshalling overhead of .invoke
without rearranging the calling code by allowing method names and method
objects to be used interchangably.
Since it is ubiquitous in Raku to expose attributes via accessors, calling
.invoke with the name of something which contains a non-function value will
return the value attached to that table key, effectively acting as an implicit
accessor. When acting as an accessor, @args is ignored.
If it is intended to retrieve an Inline::Lua::Function object rather than
calling it, :!call can be passed. This also applies to ordinary-looking
"$table.attr" method calls. This is also necessary to call non-method functions
in a table, since .invoke will pass the table itself as the first argument
whether the function is actually a method or not, because there is no reliable
way to tell the difference in Lua. To ensure a function is not called, you can
also access the function via a hash subscript instead of a method call on the
same object, which always returns the value, function or otherwise.
method obj ()
Returns an Inline::Lua::WrapperObj instance for the object; see directly below.
Inline::Lua::WrapperObj
To ease method name conflicts, this class exposes a Lua object as a Raku
object, but it does not inherit or compose anything besides Any and Mu.
Invocation works as previously described under ::Table, attempting Raku methods
before Lua, just with fewer attributes and methods to get in the way (meaning
it lacks all other features and methods in this document). A ::WrapperObj can
be passed back to Lua just as if the associated ::Object had been passed.
has $.inline-lua-object
The actual ::Object for this ::WrapperObj instance, and the only non-default
name to conflict with Lua attributes and methods (thus the slightly-awkward
identifier).
Inline::Lua::Userdata
Full userdata is supported for passing and returning. This class doesn't do
much else directly but supports metatable behaviors like any
Inline::Lua::Object, or might be used for passing its .ptr() to other Raku
NativeCall code.
Inline::Lua::Cdata
LuaJIT cdata is supported in the same way as ::Userdata, directly above.
LuaParent
To use a table as a role, assign the table to a global variable (e.g. with
.set-global), and use it with the LuaParent role.
role MyRole does LuaParent['global-table'] { ... }
To use a table as a class, LuaParent can be instantiated via .new(). To inherit
from a table, inherit from a class which composes the role, as inheriting
directly from a parameterized role doesn't seem to work. Using LuaParent with
multiple instances of Inline::Lua is unlikely to work correctly; see To Do
further above.
https://github.com/raydiak/Inline-Lua
raydiak@cyberuniverses.com
raydiak on #raku on irc.libera.chat