View Source Lua (Lua v0.0.22)
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-timedeflua
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!("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)
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 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!
.
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
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}
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)
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"
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. Loaded chunks will be marked as loaded,
otherwise they will be re-loaded everytime eval!/2
is called
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]
[: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]]
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
iex> Lua.parse_chunk("local foo =;")
{:error, ["Line 1: syntax error before: ';'"]}
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!"]
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 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 inLua.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