Integrating with Lua
Whilst all of Lua’s standard libraries are defined in Urn, you may wish to use other libraries. The easiest way to do
this is require
the module like Lua, then declare a series of globals indexing it:
(define bit32
:hidden
(require "bit32"))
(define & (.> bit32 :band))
(define ~ (.> bit32 :bnot))
;; Etc...
This is a perfectly acceptable solution, though sometimes there is a neater way to do the same thing. Enter
define-native
.
Simply exporting symbols
define-native
allows you to declare an object which is exported in an external file. There are a couple of ways of
structuring the external file, so we’ll start with the simplest. First create a file named bit32.lisp
:
(define-native &)
(define-native bnot)
;; Etc...
We’ll also need to create another file named bit32.lib.lua
. This is loaded when compiling and included in the compiled
code. Here you need to export every symbol that your library will use:
local bit32 = require "bit32"
return {
['&'] = bit32.band,
['bnot'] = bit32.bnot,
-- Etc...
}
As we’re using symbols, this gets rather verbose. However, in simpler cases, you can often just return the library
directly. As the file is included verbatim, you can run any code you require, such as checking for bit32
, bitop
,
etc… However, this is also a big disadvantage as the file size increases significantly, especially detrimental when
only a couple of functions are used. Instead, you can use .meta.lua
files.
Meta files
Instead of declaring an entire library to export, .meta.lua
files describe each variable, providing information about
what it should compile to. To use it, first you’ll need to delete your bit32.lib.lua
file. Now you’ll need to write a
metadata file. Thankfully, some of this can be automated by Urn:
bin/urn.lua --gen-native=bit32 bit32.lisp
This should emit a bit32.meta.lua
file:
local bit32 = bit32 or {}
return {
["&"] = { tag = "var", contents = "bit32[\"&\"]", value = bit32["&"], },
["bnot"] = { tag = "var", contents = "bit32.bnot", value = bit32.bnot, },
;; Etc...
}
You’ll need to change bit32[\"&\"]
and bit32["&"]
to bit32.band
for obvious reasons. Now when you compile
something using bit32
, you’ll only get declarations for symbols you need.
One other thing we can change, is to mark these symbols as “pure”. This means the constant folder may evaluate usages of
this function at compile time, replacing (& 2 3)
with 2
:
local bit32 = bit32 or {}
return {
["&"] = { tag = "var", contents = "bit32[\"&\"]", value = bit32["&"], pure = true, },
["bnot"] = { tag = "var", contents = "bit32.bnot", value = bit32.bnot, pure = true },
;; Etc...
}
This is done for most of Lua’s basic operators, and many of the string
and math
functions.
Specialising code
Whilst the existing file should be “good enough” for most needs, sometimes it might be useful to generate more
specialised code: for instance our bit32
functions could be replaced with Lua 5.3’s &
and ~
ops. To do that, we’ll
change the tag
field to be expr
instead, and specify a contents
and count
property.
return {
['&'] = { tag = "expr", contents = "(${1} & ${2})", value = bit32.band, pure = true },
['bnot'] = { tag = "expr", contents = "(~${1})", value = bit32.bnot, pure = true },
;; Etc...
}
Here contents
defines a template, with each ${n}
being replaced with the nth argument. Urn will only use this
template when the exact number of argument are specified, otherwise it will use an automatically generated wrapper
function.
Of course, we could change our value
fields to also use the binary operators.