View Source Lua (Lua v0.0.14)

Lua is an ergonomic interface to Luerl, aiming to be the best way to use Luerl from Elixir.

Features

  • ~LUA sigil for validating Lua code at compile-time
  • deflua macro for exposing Elixir functions to Lua
  • Improved error messages and sandboxing
  • Deep setting/getting variables and state
  • Excellent documentation and guides for working with Luerl

Lua the Elixir library vs Lua the language

When referring to this library, Lua will be stylized as a link.

References to Lua the language will be in plaintext and not linked.

Executing Lua

Lua can be run using the eval!/2 function

    iex> {[4], _} =
    ...>   Lua.eval!(~LUA"""
    ...>   return 2 + 2
    ...>   """)

Exposing Elixir functions to Lua

Lua provides the deflua macro for exposing Elixir functions to Lua

defmodule MyAPI do
  use Lua.API
      
  deflua double(v), do: 2 * v
end
    
lua = Lua.new() |> Lua.load_api(MyAPI)

{[10], _} =
  Lua.eval!(lua, ~LUA"""
  return double(5)
  """)

Calling Lua functions from Elixir

Lua can be used to expose complex functions written in Elixir. In some cases, you may want to call Lua functions from Elixir. This can be achieved with the Lua.call_function!/3 function

defmodule MyAPI do
  use Lua.API, scope: "example"

  deflua foo(value), state do
    Lua.call_function!(state, [:string, :lower], [value])
  end
end

lua = Lua.new() |> Lua.load_api(MyAPI)

{["wow"], _} = Lua.eval!(lua, ~LUA"return example.foo(\"WOW\")")

Modify Lua state from Elixir

You can also use Lua to modify the state of the lua environment inside your Elixir code. Imagine you have a queue module that you want to implement in Elixir, with the queue stored in a global variable

defmodule Queue do
  use Lua.API, scope: "q"
  
  deflua push(v), state do
    # Pull out the global variable "my_queue" from lua
    queue = Lua.get!(state, [:my_queue])
    
    # Call the Lua function table.insert(table, value)
    {[], state} = Lua.call_function!(state, [:table, :insert], [queue, v])
    
    # Return the modified lua state with no return values
    {[], state}
  end
end

lua = Lua.new() |> Lua.load_api(Queue)

{[queue], _} =
  Lua.eval!(lua, """
  my_queue = {}

  q.push("first")
  q.push("second")

  return my_queue
  """)
  
["first", "second"] = Lua.Table.as_list(queue)

Credits

Lua piggy-backs off of Robert Virding's Luerl project, which implements a Lua lexer, parser, and full-blown Lua virtual machine that runs inside the BEAM.

Summary

Functions

Calls a function in Lua's state

Decodes a Lua value from its internal form

Encodes a Lua value into its internal form

Evaluates the Lua script, returning any returned values and the updated Lua environment

Gets a table value in Lua

Inject functions written with the deflua macro into the Lua runtime.

Loads a Lua file into the environment. Any values returned in the global scope are thrown away.

Initializes a Lua VM sandbox

Sandboxes the given path, swapping out the implementation with a function that raises when called

Sets a table value in Lua. Nested keys will create intermediate tables

Sets the path patterns that Luerl will look in when requiring Lua scripts. For example, if you store Lua files in your application's priv directory

Write Lua code that is parsed at compile-time.

Types

@type t() :: %Lua{state: term()}

Functions

Link to this function

call_function!(lua, name, args)

View Source

Calls a function in Lua's state

iex> {[ret], _lua} = Lua.call_function!(Lua.new(), [:string, :lower], ["HELLO ROBERT"])
iex> ret
"hello robert"

References to functions can also be passed

iex> {[ref], lua} = Lua.eval!("return string.lower")
iex> {[ret], _lua} = Lua.call_function!(lua, ref, ["FUNCTION REF"])
iex> ret
"function ref"

This is also useful for executing Lua function's inside of Elixir APIs

defmodule MyAPI do
  use Lua.API, scope: "example"

  deflua foo(value), state do
    Lua.call_function!(state, [:string, :lower], [value])
  end
end

lua = Lua.new() |> Lua.load_api(MyAPI)

{["wow"], _} = Lua.eval!(lua, "return example.foo("WOW")")

Decodes a Lua value from its internal form

iex> {encoded, lua} = Lua.encode!(Lua.new(), %{a: 1})
iex> Lua.decode!(lua, encoded)
[{"a", 1}]

Encodes a Lua value into its internal form

iex> {encoded, _} = Lua.encode!(Lua.new(), %{a: 1})
iex> encoded
{:tref, 14}
Link to this function

eval!(state \\ new(), script)

View Source

Evaluates the Lua script, returning any returned values and the updated Lua environment

iex> {[42], _} = Lua.eval!(Lua.new(), "return 42")

Gets a table value in Lua

iex> state = Lua.set!(Lua.new(), [:hello], "world")
iex> Lua.get!(state, [:hello])
"world"

When a value doesn't exist, it returns nil

iex> Lua.get!(Lua.new(), [:nope])
nil

It can also get nested values

iex> state = Lua.set!(Lua.new(), [:a, :b, :c], "nested")
iex> Lua.get!(state, [:a, :b, :c])
"nested"
Link to this function

load_api(lua, module, scope \\ nil)

View Source

Inject functions written with the deflua macro into the Lua runtime.

See Lua.API for more information on writing api modules

Loads a Lua file into the environment. Any values returned in the global scope are thrown away.

Mimics the functionality of Lua's dofile

Initializes a Lua VM sandbox

iex> Lua.new()

By default, the following Lua functions are sandboxed.

  • [:io]
  • [:file]
  • [:os, :execute]
  • [:os, :exit]
  • [:os, :getenv]
  • [:os, :remove]
  • [:os, :rename]
  • [:os, :tmpname]
  • [:package]
  • [:load]
  • [:loadfile]
  • [:require]
  • [:dofile]
  • [:load]
  • [:loadfile]
  • [:loadstring]

To disable, use the sandboxed option, passing an empty list

iex> Lua.new(sandboxed: [])

Alternatively, you can pass your own list of functions to sandbox. This is equivalent to calling Lua.sandbox/2.

iex> Lua.new(sandboxed: [[:os, :exit]])

Options

  • :sandboxed - list of paths to be sandboxed, e.g. sandboxed: [[:require], [:os, :exit]]
  • :exclude - list of paths to exclude from the sandbox, e.g. exclude: [[:require], [:package]]

Sandboxes the given path, swapping out the implementation with a function that raises when called

iex> lua = Lua.new(sandboxed: [])
iex> Lua.sandbox(lua, [:os, :exit])

Sets a table value in Lua. Nested keys will create intermediate tables

iex> Lua.set!(Lua.new(), [:hello], "World")

It can also set nested values

iex> Lua.set!(Lua.new(), [:a, :b, :c], [])

These table values are availble in lua scripts

iex> lua = Lua.set!(Lua.new(), [:a, :b, :c], "nested!")
iex> {result, _} = Lua.eval!(lua, "return a.b.c")
iex> result
["nested!"]
Link to this function

set_lua_paths(lua, paths)

View Source

Sets the path patterns that Luerl will look in when requiring Lua scripts. For example, if you store Lua files in your application's priv directory:

iex> lua = Lua.new(exclude: [[:package], [:require]])
iex> Lua.set_lua_paths(lua, ["myapp/priv/lua/?.lua", "myapp/lua/?/init.lua"])

Now you can use the Lua require function to import these scripts

Warning

In order to use Lua.set_lua_paths/2, the following functions cannot be sandboxed:

  • [:package]
  • [:require]

By default these are sandboxed, see the :exclude option in Lua.new/1 to allow them.

Link to this macro

sigil_LUA(code, opts)

View Source (macro)

Write Lua code that is parsed at compile-time.

iex> ~LUA"return 2 + 2"
"return 2 + 2"

If the code cannot be lexed and parsed, it raises a Lua.CompilerException

#iex> ~LUA":not_lua"
** (Lua.CompilerException) Failed to compile Lua!