Lua.VM.State (Lua v1.0.0-rc.3)

View Source

Runtime state for the Lua VM.

Summary

Functions

Allocates a fresh table in the state, returning {{:tref, id}, new_state}.

Allocates userdata and returns a reference.

Guards against unbounded recursion.

Deletes a private value.

Returns the _G table reference.

Reads a global variable from the VM state. Returns nil if unset.

Retrieves a private value. Raises KeyError if the key doesn't exist.

Fetches a table by reference.

Gets userdata by reference.

Returns the underlying globals data map (read-only convenience).

Creates a new VM state.

Stores a private value not exposed to Lua.

Registers a native Elixir function as a global in the VM state.

Sets a global variable in the VM state.

Charges one instruction against the budget and returns the new tally.

Recovers the state a protected call should continue with after trapping an error.

Updates a table in-place via a function.

Types

t()

@type t() :: %Lua.VM.State{
  call_depth: non_neg_integer(),
  call_stack: list(),
  g_ref: nil | {:tref, non_neg_integer()},
  instruction_count: non_neg_integer(),
  max_call_depth: pos_integer() | :infinity,
  max_instructions: pos_integer() | :infinity,
  max_string_bytes: pos_integer(),
  metatables: map(),
  multi_return_count: non_neg_integer(),
  open_upvalues: term(),
  private: map(),
  table_next_id: non_neg_integer(),
  tables: %{optional(non_neg_integer()) => Lua.VM.Table.t()},
  upvalue_cells: map(),
  userdata: %{optional(non_neg_integer()) => term()},
  userdata_next_id: non_neg_integer()
}

Functions

alloc_table(state, data \\ %{})

@spec alloc_table(t(), map()) :: {{:tref, non_neg_integer()}, t()}

Allocates a fresh table in the state, returning {{:tref, id}, new_state}.

alloc_userdata(state, value)

@spec alloc_userdata(t(), term()) :: {{:udref, non_neg_integer()}, t()}

Allocates userdata and returns a reference.

Userdata stores arbitrary Elixir terms that can be passed through Lua but not directly manipulated by Lua code.

check_call_depth!(state)

@spec check_call_depth!(t()) :: :ok

Guards against unbounded recursion.

Raises a Lua "stack overflow" runtime error when the call depth has reached max_call_depth. Call it immediately before pushing a frame onto call_stack.

No-op when depth is under the limit or when max_call_depth is :infinity (the default). The clauses are ordered so both common cases resolve in a single function-head match with no struct rebuild.

delete_private(state, key)

@spec delete_private(t(), term()) :: t()

Deletes a private value.

g_ref(state)

@spec g_ref(t()) :: {:tref, non_neg_integer()}

Returns the _G table reference.

get_global(state, name)

@spec get_global(t(), binary()) :: term()

Reads a global variable from the VM state. Returns nil if unset.

get_private(state, key)

@spec get_private(t(), term()) :: term()

Retrieves a private value. Raises KeyError if the key doesn't exist.

get_table(state, arg)

@spec get_table(
  t(),
  {:tref, non_neg_integer()}
) :: Lua.VM.Table.t()

Fetches a table by reference.

get_userdata(state, arg)

@spec get_userdata(
  t(),
  {:udref, non_neg_integer()}
) :: term()

Gets userdata by reference.

globals(state)

@spec globals(t()) :: map()

Returns the underlying globals data map (read-only convenience).

Equivalent to state._G.data. Avoid using this for state mutation — use set_global/3 instead so that future invariants stay consistent.

new()

@spec new() :: t()

Creates a new VM state.

Allocates an empty _G table to hold globals.

put_private(state, key, value)

@spec put_private(t(), term(), term()) :: t()

Stores a private value not exposed to Lua.

register_function(state, name, fun)

@spec register_function(t(), binary(), (list(), t() -> {list(), t()})) :: t()

Registers a native Elixir function as a global in the VM state.

The function should accept (args, state) and return {results, state}, where args is a list of Lua values and results is a list of return values.

set_global(state, name, value)

@spec set_global(t(), binary(), term()) :: t()

Sets a global variable in the VM state.

Writes into the _G table's data map. Globals storage lives entirely inside the _G table since Plan A16 (Lua 5.3 _ENV semantics).

tick!(state, instruction_count)

@spec tick!(t(), non_neg_integer()) :: non_neg_integer()

Charges one instruction against the budget and returns the new tally.

Guards against unbounded CPU work within a single evaluation by folding the running-tally increment and the budget check into one call. Call it at loop back-edges and call boundaries — never per opcode — so the default :infinity path stays free of per-instruction cost.

The tally is threaded as a parameter, not stored in %State{}. When max_instructions is :infinity (the default) this is a true no-op: it returns the tally unchanged in a single function-head match, doing no arithmetic and rebuilding no struct, so the default path's only per-boundary cost is this one call. When a finite budget is set it increments the tally and, once the new tally reaches max_instructions, raises a catchable Lua "instruction budget exceeded" runtime error. The clauses are ordered so both the :infinity and under-budget cases resolve in a single function-head match.

The raise reuses the same Lua.VM.RuntimeError used by "stack overflow", carrying the raise-time state: so pcall/xpcall recover heap effects for free. The live tally is stamped into that state: (the threaded instruction_count is not otherwise in %State{}), so unwind_to/2 can carry it forward and a caught budget error stays spent — a protected call cannot refund the work it burned.

unwind_to(entry, raised)

@spec unwind_to(t(), t() | nil) :: t()

Recovers the state a protected call should continue with after trapping an error.

Lua 5.3 §2.3: an error aborts the protected call, but heap effects made before it — global writes, table mutations, upvalue-cell assignments, metatable changes — are kept. Only control state unwinds. Accordingly, heap fields come from raised (the state captured at the raise site, ferried out on the exception's :state field) while control fields are restored from entry (the state at protected-call entry):

kept from raised (heap)restored from entry (control)
tables, table_next_idcall_stack, call_depth
userdata, userdata_next_idopen_upvalues
metatables, upvalue_cells, privatemulti_return_count

The instruction tally instruction_count is also carried forward (monotonic max), not reset to the entry value: the work a protected call burned must still count against the one per-evaluation :max_instructions budget, so wrapping heavy work in pcall (or looping over pcall) cannot escape the cap.

Keeping upvalue_cells while restoring open_upvalues matches reference upvalue semantics: cells captured before the protected call keep their mutated values, while cells opened by the unwound frames become unreachable garbage.

When no raise-time state was captured (raised is nil, e.g. the error came from outside Lua execution), falls back to entry unchanged.

update_table(state, arg, fun)

@spec update_table(
  t(),
  {:tref, non_neg_integer()},
  (Lua.VM.Table.t() -> Lua.VM.Table.t())
) :: t()

Updates a table in-place via a function.