Luax
provides a safe, higher-level interface over Lua. Where the
Lua module returns raw values and status codes, Luax returns Maybe and
Result types, manages stack cleanup internally, and provides macros that
reduce boilerplate for common patterns.
Safe stack access. The maybe-get-* family type-checks before reading and
returns Nothing on mismatch, so you never get garbage from reading the wrong
type:
(Lua.push-int lua 42)
(match (Luax.maybe-get-int lua -1)
(Maybe.Just n) (IO.println &(fmt "got %d" n))
(Maybe.Nothing) (IO.errorln "not a number"))
(match (Luax.maybe-get-string lua -1)
(Maybe.Just _) () ; won't match — it's an int
(Maybe.Nothing) (IO.println "correctly rejected"))
(Lua.pop lua 1)
Globals. set-*-global and get-*-global handle the push/pop cycle for
you. The getters return Maybe and clean up the stack regardless of success:
(Luax.set-int-global lua "score" 100)
(Luax.set-bool-global lua "debug" true)
(match (Luax.get-int-global lua "score")
(Maybe.Just n) (IO.println &(fmt "score: %d" n))
(Maybe.Nothing) (IO.errorln "missing"))
Table fields. get-*-field reads a field from a table already on the stack
and pops the field value afterward, so you can read multiple fields in sequence
without manual stack management. set-*-field handles the index shift from
pushing the value:
(Lua.get-global lua (cstr "player"))
(match (Luax.get-string-field lua -1 "name")
(Maybe.Just name) (IO.println &name)
(Maybe.Nothing) (IO.errorln "no name"))
(match (Luax.get-int-field lua -1 "hp")
(Maybe.Just hp) (IO.println &(fmt "hp: %d" hp))
(Maybe.Nothing) (IO.errorln "no hp"))
(Luax.set-int-field lua -1 "hp" 0)
(Lua.pop lua 1)
Calling functions. call-fn looks up a global function, pushes
arguments, calls it, and reads the result — returning (Error msg) if the
function is undefined or raises a Lua error:
(match (Luax.call-fn lua add Lua.get-int
(Lua.push-int 3) (Lua.push-int 4))
(Result.Success v) (IO.println &(fmt "3 + 4 = %d" v))
(Result.Error e) (IO.errorln &e))
Building tables. make-table creates a table, sets fields,
and assigns it to a global in one expression:
(Luax.make-table lua point
(x (Lua.push-int 10))
(y (Lua.push-int 20)))
; point is now a Lua global with fields x=10, y=20
Code execution. do-in wraps Lua.do-string with
Result error handling:
(match (Luax.do-in lua "x = 1 + nil")
(Result.Success _) ()
(Result.Error e) (IO.println &(fmt "caught: %s" &e)))
call-fn
Macro
(call-fn lua name getter :rest push-exprs)
Call the Lua function name and read the result with getter.
Arguments are passed as push expressions—the lua state argument is inserted
automatically. Returns (Error msg) if the function is undefined or if the
call raises a Lua error.
(Luax.call-fn lua add Lua.get-int
(Lua.push-int 3)
(Lua.push-int 4))
do-in
(Fn [(Ref Lua a), (Ref String b)] (Result String String))
(do-in lua code)
Compile and execute the Lua string code. Returns (Success "")
on success or (Error msg) with the Lua error message on failure. Wraps
Lua.do-string with Result handling.
get-carp-str
(Fn [(Ref Lua a), Int] String)
(get-carp-str lua index)
Read the value at index as a Carp String, using Lua's
tostring coercion. Returns an empty string if the conversion fails.
get-string-global
(Fn [(Ref Lua a), (Ref String b)] (Maybe String))
(get-string-global lua name)
Fetch the global name as a Carp String. Returns Nothing if the global is nil or not a string. Pops the global from the stack internally.
make-table
Macro
(make-table lua name :rest field-specs)
Create a Lua table with the given fields and assign it to the
global name. Each field spec is (field-name (push-expr)), where the lua
state argument is inserted into the push expression automatically.
(Luax.make-table lua player
(name (Lua.push-carp-str "Ada"))
(hp (Lua.push-int 100)))
maybe-get-userdata
(Fn [(Ref Lua a), Int] (Maybe (Ptr ())))
(maybe-get-userdata lua index)
Read the value at index as a userdata pointer,
returning Nothing if it is not a full or light userdata. Leaves the stack
unchanged.
set-string-global
(Fn [(Ref Lua a), (Ref String b), (Ref String c)] ())
(set-string-global lua name value)
Push value and assign it to the global name.