Lua (Lua v1.0.0-rc.0)
View SourceLua is an Elixir-native Lua 5.3 virtual machine. It runs Lua code on the BEAM
with no Erlang or C dependencies, and exposes an ergonomic Elixir API for
embedding Lua scripting in your application.
Features
- Pure-Elixir implementation of Lua 5.3 — lexer, parser, register-based VM, and standard library — running directly on the BEAM
~LUAsigil for validating (and optionally pre-compiling) Lua code at compile timedefluamacro for exposing Elixir functions to Lua- Beautiful error messages and stack traces
- Sandboxing of stdlib paths
- Deep setting/getting of variables and state from Elixir
- Pattern-matching, metatables, varargs, multiple returns,
_G/_ENV,string/table/math/debuglibraries, and thestring.find/match/gmatch/gsubpattern engine
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!("return 2 + 2")Compile-time validation
Use the ~LUA sigil to parse and validate your Lua code at compile time
iex> import Lua, only: [sigil_LUA: 2]
#iex> {[4], _} = Lua.eval!(~LUA[return 2 +])
** (Lua.CompilerException) Failed to compile Lua!Using the c modifier transforms your Lua code into a Lua.Chunk.t/0 at compile-time,
which will speed up execution at runtime since the Lua no longer needs to be parsed
iex> import Lua, only: [sigil_LUA: 2]
iex> {[4], _} = Lua.eval!(~LUA[return 2 + 2]c)Exposing Elixir functions to Lua
The simplest way to expose an Elixir function to Lua is using the Lua.set!/3 function
import Lua, only: [sigil_LUA: 2]
lua =
Lua.set!(Lua.new(), [:sum], fn args ->
[Enum.sum(args)]
end)
{[10], _} = Lua.eval!(lua, ~LUA[return sum(1, 2, 3, 4)]c)For easily expressing APIs, Lua provides the deflua macro for exposing Elixir functions to Lua
defmodule MyAPI do
use Lua.API
deflua double(v), do: 2 * v
end
import Lua, only: [sigil_LUA: 2]
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
import Lua, only: [sigil_LUA: 2]
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
import Lua, only: [sigil_LUA: 2]
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)Accessing private state from Elixir
When building applications with Lua, you may find yourself in need of propagating extra context for use in your APIs. For instance, you may want to access information about the current user who executed the Lua script, an API key, or something else that is private and should not be available to the Lua code. For this, we have the Lua.put_private/3, Lua.get_private/2, and Lua.delete_private/2 functions.
For example, imagine you wanted to allow the user to access information about themselves
defmodule User do
defstruct [:name]
end
defmodule UserAPI do
use Lua.API, scope: "user"
deflua name(), state do
user = Lua.get_private!(state, :user)
{[user.name], state}
end
end
user = %User{name: "Robert Virding"}
lua = Lua.new() |> Lua.put_private(:user, user) |> Lua.load_api(UserAPI)
{["Hello Robert Virding"], _lua} = Lua.eval!(lua, ~LUA"""
return "Hello " .. user.name()
""")This allows you to have simple, expressive APIs that access context that is unavailable to the Lua code.
Encoding and Decoding data
When working with Lua, you may want to inject data of various types into the runtime. Some values, such as integers, have the same representation inside the VM as they do in Elixir and do not require encoding. Other values, such as maps, are represented inside the VM as tables and must be encoded first. Arbitrary Elixir terms can be passed across the boundary using a {:userdata, any()} tuple.
Values may be encoded with Lua.encode!/2.
| Elixir type | Internal VM type | Requires encoding? |
|---|---|---|
nil | nil | no |
boolean() | boolean() | no |
number() | number() | no |
binary() | binary() | no |
atom() | binary() | yes |
map() | {:tref, integer()} | yes |
{:userdata, any()} | {:udref, integer()} | yes |
(any()) -> any() | {:native_func, fun} | yes |
(any(), Lua.t()) -> any() | {:native_func, fun} | yes |
list(any()) | list(VM type) | maybe (if any of its values require encoding) |
Userdata
There are situations where you want to pass around a reference to some Elixir datastructure, such as a struct. In these situations, you can use a {:userdata, any()} tuple.
defmodule Thing do
defstruct [:value]
end
{encoded, lua} = Lua.encode!(Lua.new(), {:userdata, %Thing{value: "1234"}})
lua = Lua.set!(lua, [:foo], encoded)
{[{:userdata, %Thing{value: "1234"}}], _} = Lua.eval!(lua, "return foo")Trying to deference userdata inside a Lua program will result in an exception.
Credits
Lua started as an ergonomic Elixir wrapper around Robert Virding's
Luerl project. As of 1.0.0 this library
is a full Elixir-native reimplementation of the Lua 5.3 lexer, parser, and
virtual machine, with a public API designed to feel idiomatic from Elixir.
Luerl deserves credit as the prior art that made this possible — its design
informed many of the decisions in the new VM, and we benchmark against it.
Summary
Functions
Calls a function in Lua's state
The raising variant of call_function/3
Decodes a Lua value from its internal form
Decodes a list of encoded values
Deletes a key from private storage
Encodes a Lua value into its internal form
Encodes a list of values into a list of encoded value
Evaluates the Lua script, returning any returned values and the updated Lua environment
Gets a table value in Lua
Gets a private value in storage for use in Elixir functions
Gets a private value in storage for use in Elixir functions, raises if the key doesn't exist
Inject functions written with the deflua macro into the Lua runtime.
Loads string or Lua.Chunk.t/0 into state so that it can be
evaluated via eval!/2
Loads a Lua file into the environment. Any values returned in the global scope are thrown away.
Initializes a Lua VM sandbox
Parses a chunk of Lua code into a Lua.Chunk.t/0, which then can
be loaded via load_chunk!/2 or run via eval!.
Puts a private value in storage for use in Elixir functions
Sandboxes the given path, swapping out the implementation with a function that raises when called
Sets a table value in Lua. Nested keys will allocate intermediate tables
Sets the path patterns that the VM 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.
Returns the underlying VM tag tuple for a display struct returned by
Lua.eval!/2 in decode: false mode. Returns
values unchanged if they are not display structs, so it is safe to
apply unconditionally to any value flowing back from eval.
Types
Functions
Calls a function in Lua's state
iex> {:ok, [ret], _lua} = Lua.call_function(Lua.new(), [:string, :lower], ["HELLO ROBERT"])
iex> ret
"hello robert"
iex> lua = Lua.new()
iex> lua = Lua.set!(lua, [:double], fn [val] -> [val * 2] end)
iex> {:ok, [_ret], _lua} = Lua.call_function(lua, [:double], [5])References to functions can also be passed
iex> {[ref], lua} = Lua.eval!(Lua.new(), "return string.lower", decode: false)
iex> {:ok, [ret], _lua} = Lua.call_function(lua, ref, ["FUNCTION REF"])
iex> ret
"function ref"
iex> {[ref], lua} = Lua.eval!(Lua.new(), "return function(x) return x end", decode: false)
iex> {:ok, [ret], _lua} = Lua.call_function(lua, ref, [42])
iex> ret
42
The raising variant of call_function/3
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
Decodes a Lua value from its internal form
iex> {encoded, lua} = Lua.encode!(Lua.new(), %{a: 1})
iex> Lua.decode!(lua, encoded)
[{"a", 1}]
Decodes a list of encoded values
Useful for decoding all function arguments in a deflua
iex> {encoded, lua} = Lua.encode_list!(Lua.new(), [1, %{a: 2}, true])
iex> Lua.decode_list!(lua, encoded)
[1, [{"a", 2}], true]
Deletes a key from private storage
iex> lua = Lua.new() |> Lua.put_private(:api_key, "1234")
iex> lua = Lua.delete_private(lua, :api_key)
iex> Lua.get_private(lua, :api_key)
:error
Encodes a Lua value into its internal form
<!-- Old Luerl implementation returned specific tref IDs: {encoded, _} = Lua.encode!(Lua.new(), %{a: 1}); encoded => {:tref, 14} -->
iex> {encoded, _} = Lua.encode!(Lua.new(), %{a: 1})
iex> match?({:tref, _}, encoded)
true
Encodes a list of values into a list of encoded value
Useful for encoding lists of return values
iex> {[1, {:tref, _}, true], _} = Lua.encode_list!(Lua.new(), [1, %{a: 2}, true])
Evaluates the Lua script, returning any returned values and the updated Lua environment
iex> {[42], _} = Lua.eval!(Lua.new(), "return 42")eval!/2 can also evaluate chunks by passing instead of a script. As a
performance optimization, it is recommended to call load_chunk!/2 if you
will be executing a chunk many times, but it is not necessary.
iex> {[4], _} = Lua.eval!(~LUA[return 2 + 2]c)Options
:decode- (defaulttrue) By default, all values returned from Lua scripts are decoded.This may not be desirable if you need to modify a table reference or access a function call. Pass `decode: false` as an option to return encoded values:source- (default"<eval>") Source name attached to the compiled chunk. Surfaces inruntime errors as `at <source>:<line>:` and in stack traces. Pick a name that identifies where this script came from (e.g. `"my_script.lua"`).
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])
nilIt can also get nested values
iex> state = Lua.set!(Lua.new(), [:a, :b, :c], "nested")
iex> Lua.get!(state, [:a, :b, :c])
"nested"Options
:decode- (defaulttrue) - By default, values are decoded
Gets a private value in storage for use in Elixir functions
iex> lua = Lua.new() |> Lua.put_private(:api_key, "1234")
iex> Lua.get_private(lua, :api_key)
{:ok, "1234"}
Gets a private value in storage for use in Elixir functions, raises if the key doesn't exist
iex> lua = Lua.new() |> Lua.put_private(:api_key, "1234")
iex> Lua.get_private!(lua, :api_key)
"1234"
Inject functions written with the deflua macro into the Lua runtime.
See Lua.API for more information on writing api modules
Options
:scope- (optional) scope, overriding whatever is provided inuse Lua.API, scope: ...:data- (optional) - data to be passed to the Lua.API.install/3 callback
Loads string or Lua.Chunk.t/0 into state so that it can be
evaluated via eval!/2
Strings can be loaded as chunks, which are parsed and loaded
iex> {%Lua.Chunk{}, %Lua{}} = Lua.load_chunk!(Lua.new(), "return 2 + 2")Or a pre-compiled chunk can be loaded as well. In the old Luerl-backed implementation,
loaded chunks were marked as loaded so they wouldn't be re-loaded on each eval!/2 call.
With the new VM, chunks hold a compiled prototype and don't need a separate loading step.
iex> {%Lua.Chunk{}, %Lua{}} = Lua.load_chunk!(Lua.new(), ~LUA[return 2 + 2]c)
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, :stdin][:io, :stdout][:io, :stderr][:io, :read][:io, :write][:io, :open][:io, :close][:io, :lines][:io, :popen][:io, :tmpfile][:io, :output][:io, :input][:io, :flush][:io, :type][:file][:os, :execute][:os, :exit][:os, :getenv][:os, :remove][:os, :rename][:os, :tmpname][:package][:load][:loadfile][:require][:dofile][: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]]:debug- (defaultfalse) whentrue, internal Lua VM frames are preserved in stack traces instead of being pruned. Useful when debugging library bugs.
Parses a chunk of Lua code into a Lua.Chunk.t/0, which then can
be loaded via load_chunk!/2 or run via eval!.
This function is particularly useful for checking Lua code for syntax
erorrs and warnings at runtime. If you would like to just load a chunk,
use load_chunk!/1 instead.
iex> {:ok, %Lua.Chunk{}} = Lua.parse_chunk("local foo = 1")Errors found during parsing will be returned as a list of formatted strings
<!-- Old Luerl error format: Lua.parse_chunk("local foo =;") returned {:error, ["Line 1: syntax error before: ';'"]} -->
iex> {:error, [msg]} = Lua.parse_chunk("local foo =;")
iex> msg =~ "Expected expression"
true
Puts a private value in storage for use in Elixir functions
iex> Lua.new() |> Lua.put_private(:api_key, "1234")
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 allocate 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!"]Lua.set!/3 can also be used to expose Elixir functions
iex> lua = Lua.set!(Lua.new(), [:sum], fn args -> [Enum.sum(args)] end)
iex> {[10], _lua} = Lua.eval!(lua, "return sum(1, 2, 3, 4)")Functions can also take a second argument for the state of Lua
iex> lua =
...> Lua.set!(Lua.new(), [:set_count], fn args, state ->
...> {[], Lua.set!(state, :count, Enum.count(args))}
...> end)
iex> {[3], _} = Lua.eval!(lua, "set_count(1, 2, 3); return count")
Sets the path patterns that the VM 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.
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!As an optimization, the c modifier can be used to return a pre-compiled Lua chunk
iex> ~LUA"return 2 + 2"c
Returns the underlying VM tag tuple for a display struct returned by
Lua.eval!/2 in decode: false mode. Returns
values unchanged if they are not display structs, so it is safe to
apply unconditionally to any value flowing back from eval.
iex> {[t], _lua} = Lua.eval!(Lua.new(), "return {1, 2, 3}", decode: false)
iex> match?({:tref, _}, Lua.unwrap(t))
true
iex> {[c], _} = Lua.eval!(Lua.new(), "return function() end")
iex> match?({:lua_closure, _, _}, Lua.unwrap(c)) or match?({:compiled_closure, _, _}, Lua.unwrap(c))
true
iex> Lua.unwrap(42)
42Useful when you need to pass an eval-returned closure or table
reference to a tool that expects the raw VM tag (for example, an
internal helper or a custom deflua).