Lua.VM.Table (Lua v1.0.0-rc.2)

View Source

Lua table data structure with split array/hash storage.

Dense positive-integer keys (1..n) live in an Erlang :array (the arr field), giving O(1) functional read/write and a free sequence length. Every other key — strings, non-positive integers, sparse integers beyond the contiguous array border, float/boolean/table keys — lives in the data hash map alongside the iteration-order bookkeeping.

Keeping string keys in data means the executor's field/global fast paths (%{^name => value}) and metatable lookups read the hash map directly with no change. Integer-keyed reads/writes route through the split-aware helpers in this module.

Array border

arr_n is the count of contiguous integer keys 1..arr_n currently stored in arr. Writing key arr_n + 1 extends the border; clearing a key inside the border splits it (the tail beyond the hole migrates to data lazily on read). A write to a sparse integer key beyond the border goes to data until a later contiguous fill promotes it.

Dead-key tracking

Lua 5.3 §6.1 dead-key iteration semantics are preserved for the hash portion via order/order_tail/dead exactly as before. Array-portion keys iterate first, in index order, then hash keys in insertion order.

Summary

Functions

Flushes any pending order_tail appends into order. Idempotent.

Builds a table struct from a plain data map.

Reads a value from a table by key, split-aware (array then hash).

Reads a value from a bare hash data map, applying key normalization.

Returns true when the bare hash data map has an entry for key.

Returns true when the table (array or hash) has a live entry for key.

Returns true if a key is invalid for use in a table assignment.

Returns the Lua sequence length: the largest n with keys 1..n present.

Returns the next key/value pair in iteration order after key.

Normalizes a table key per Lua 5.3 §3.4.11.

Writes value into the table under key, honoring Lua semantics

Writes value into a raw data map under key (hash-only, no array).

Applies an ordered list of {key, value} writes, equivalent to folding put/3 left-to-right but rebuilding the struct once.

Replaces the table contents wholesale from a plain data map, rebuilding the array/hash split and clearing dead.

Materializes the full table contents (array + hash) as a single flat map.

Types

t()

@type t() :: %Lua.VM.Table{
  arr: :array.array() | :undefined,
  arr_n: non_neg_integer(),
  data: %{optional(term()) => term()},
  dead: %{optional(term()) => true},
  metatable: {:tref, non_neg_integer()} | nil,
  order: [term()],
  order_tail: [term()]
}

Functions

flush_order(table)

@spec flush_order(t()) :: t()

Flushes any pending order_tail appends into order. Idempotent.

from_data(data)

@spec from_data(map()) :: t()

Builds a table struct from a plain data map.

Splits dense positive-integer keys into the array and leaves the rest in data, so callers that pass a literal map (stdlib init, table.pack, encode) get split storage with no extra effort.

get(table, key)

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

Reads a value from a table by key, split-aware (array then hash).

This is the struct-level read. Prefer it over the bare-map get_data/2 for any code that has the full %Table{}.

get_data(data, key)

@spec get_data(map(), term()) :: term()

Reads a value from a bare hash data map, applying key normalization.

Hash-only: does not consult the array. Callers with a full struct should use get/2.

has_data?(data, key)

@spec has_data?(map(), term()) :: boolean()

Returns true when the bare hash data map has an entry for key.

has_key?(table, key)

@spec has_key?(t(), term()) :: boolean()

Returns true when the table (array or hash) has a live entry for key.

invalid_key?(key)

@spec invalid_key?(term()) :: boolean()

Returns true if a key is invalid for use in a table assignment.

length(table)

@spec length(t()) :: non_neg_integer()

Returns the Lua sequence length: the largest n with keys 1..n present.

The contiguous array border gives this for free; we only fall back to probing the hash for sparse integers parked beyond the border.

next_entry(table, key)

@spec next_entry(t(), term()) :: {term(), term()} | nil | :invalid_key

Returns the next key/value pair in iteration order after key.

Array keys (1..arr_n) iterate first in index order, then hash keys in insertion order. Mirrors the old next_entry/2 contract: returns {k, v}, nil at the end, or :invalid_key when key is absent everywhere.

normalize_key(key)

@spec normalize_key(term()) :: term()

Normalizes a table key per Lua 5.3 §3.4.11.

put(table, key, value)

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

Writes value into the table under key, honoring Lua semantics:

  • Assigning nil removes the key.
  • Dense positive integers route to the array; other keys to the hash.

put_data(data, key, value)

@spec put_data(map(), term(), term()) :: map()

Writes value into a raw data map under key (hash-only, no array).

Retained for callers that operate on a bare hash map with no surrounding struct. Integer keys still land in the map here.

put_many(table, pairs)

@spec put_many(t(), [{term(), term()}]) :: t()

Applies an ordered list of {key, value} writes, equivalent to folding put/3 left-to-right but rebuilding the struct once.

replace_data(table, data)

@spec replace_data(t(), map()) :: t()

Replaces the table contents wholesale from a plain data map, rebuilding the array/hash split and clearing dead.

to_map(table)

@spec to_map(t()) :: map()

Materializes the full table contents (array + hash) as a single flat map.

Used by code paths that genuinely need the whole table as a map (decode, display, string.gsub replacement lookups). Walks the array once.