View on GitHub

HsLua

Extend Haskell apps with Lua

Hackage

Overview

HsLua provides the glue to use Lua from within a Haskell app, or the other way around. It provides foreign function interace (FFI) bindings, helper functions, and as well as utilities.

Lua is a small, well-designed, embeddable scripting language. It has become the de-facto default to make programs extensible and is widely used everywhere from servers over games and desktop applications up to security software and embedded devices. This package provides Haskell bindings to Lua, enable coders to embed the language into their programs, and to thereby make them scriptable.

HsLua ships with batteries included and includes the most recent Lua version (i.e., Lua 5.3.5). Cabal flags make it easy to compile against a system-wide Lua installation.

When to use it

You should give HsLua a try if you

HsLua exposes most of Lua’s C API via Haskell functions. It offers improved type-safety when compared to the raw C functions, while also translating Lua errors to Haskell exceptions. Furthermore, HsLua provides type-classes which make interacting with Lua straight-forward and safe.

When not to use it

HsLua is a bad for applications requiring raw speed. Combining two garbage collecting language with different error-conventions requires some acrobatics. HsLua is flexible and (relatively) safe, but this comes at the price of reduced performance. This doesn’t matter for most use-cases, but don’t use HsLua just because Haskell is fast. Sometimes, coding in C and importing the external functions via the Haskell FFI is the better way.

Interacting with Lua

HsLua provides the Lua type to define Lua operations. The operations are executed by calling run. A simple “Hello, World” program, using the Lua print function, is given below:

import Foreign.Lua as Lua

main :: IO ()
main = Lua.run prog
  where
    prog :: Lua ()
    prog = do
      Lua.openlibs  -- load Lua libraries so we can use 'print'
      Lua.callFunc "print" "Hello, World!"

The Lua stack

Lua’s API is stack-centered: most operations involve pushing values to the stack or receiving items from the stack. E.g., calling a function is performed by pushing the function onto the stack, followed by the function arguments in the order they should be passed to the function. The API function call then invokes the function with given numbers of arguments, pops the function and parameters of the stack, and pushes the results.

,----------.
|  arg 3   |
+----------+
|  arg 2   |
+----------+
|  arg 1   |
+----------+                  ,----------.
| function |    call 3 1      | result 1 |
+----------+   ===========>   +----------+
|          |                  |          |
|  stack   |                  |  stack   |
|          |                  |          |

Manually pushing and pulling arguments can become tiresome, so HsLua makes function calling simple by providing callFunc. It uses type-magic to allow different numbers of arguments. Think about it as having the signature

callFunc :: String -> a1 -> a2 -> … -> res

where the arguments a1, a2, … must be of a type which can be pushed to the Lua stack, and the result-type res must be constructable from a value on the Lua stack.

Getting values from and to the Lua stack

Conversion between Haskell and Lua values is governed by two type classes:

-- | A value that can be read from the Lua stack.
class Peekable a where
  -- | Check if at index @n@ there is a convertible Lua value and
  --   if so return it.  Throws a @'LuaException'@ otherwise.
  peek :: StackIndex -> Lua a

and

-- | A value that can be pushed to the Lua stack.
class Pushable a where
  -- | Pushes a value onto Lua stack, casting it into meaningfully
  --   nearest Lua type.
  push :: a -> Lua ()

Many basic data types have instances for these type classes. New instances can be defined for custom types using the functions in Foreign.Lua.Core (also exported in Foreign.Lua).

Internals

Error Handling

HsLua keeps error handling simple and intuitive for users; all of the below should be abstracted away, and users should not have to concern themselves with the details described below.

Errors and the call stack

The method used for error handling by Lua is build around the setjmp and longjmp C functions. This is simple and powerful, but poses some serious problems when combined with Haskell. The setjmp function can jump upwards on the call-stack, possibly skipping frames belonging to the Haskell runtime system (RTS). This has the potential to confuse the RTS, resulting in a program crash. Skipping Haskell frames while unwinding the stack thus has to be avoided at all costs.

We can call Haskell from Lua which calls Lua again etc. At each language boundary we have to check for errors and propagate them properly to the next level in stack. Hslua does this for you, both when returning from Lua to Haskell, and when calling from Haskell into Lua.

HsLua must translate errors

Let’s say we have this call stack: (stack grows upwards)

Haskell function
Lua function
Haskell program

and we want to report an error in the top-most Haskell function. We can’t use lua_error from the Lua C API, because it uses longjmp, which means it skips layers of abstractions, including the Haskell RTS. There’s no way to prevent this longjmp. lua_pcall sets the jump target, but even with lua_pcall it’s not safe. Consider this call stack:

Haskell function which calls lua_error
Lua function, uses pcall
Haskell program

This program jumps to Lua function, skipping Haskell RTS code that would run before Haskell function returns. For this reason we can use lua_pcall (pcall) only for catching errors from Lua, and even in that case we need to make sure there are no Haskell calls between the error-throwing Lua call and our pcall call.

Error to exceptions (and back)

To be able to catch exceptions from Haskell functions in Lua, we need to define a custom error protocol. Currently HsLua does this: error has the same type as Lua’s lua_error, but instead of calling lua_error, it returns two values: A special error value and an error message. HsLua exceptions are caught and converted to Lua errors via this function.

All of the above internals should stay hidden most of the time, as all Haskell functions are wrapped such that error values are transformed into Lua errors behind the scenes.

At this point our call stack is like this:

Lua function (Haskell function returned with error, which we caught)
Haskell program

If we want to further propagate the error message to the Haskell program, then we can just use Lua’s standard error function and use pcall on the Haskell side. Note that if we use error on the Lua side and forget to use pcall in the calling Haskell function, we would be starting to skip layers of abstractions and would get a segfault in the best case. That’s why HsLua wraps all API functions that can potentially fail in custom C functions. Those functions behave idential to the functions they wrap, but catch all errors and return error codes instead. This comes with a serious performance penalty, but using @error@ within Lua should be safe.

The pcall function is not wrapped in additional C code but still safe. The reason it’s safe is because the lua_pcall C function is calling the Lua function using Lua C API, and when the called Lua function calls error it longjmps to lua_pcall C function, without skipping any layers of abstraction. lua_pcall then returns to Haskell.

Other resources