Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

> Upgrading from a Luerl-based `0.x` release? See
> [Upgrading from 0.x](#upgrading-from-0x-luerl-based-versions) below.
> [Upgrading from 0.x](#upgrading-from-0x-luerl-based-versions) below, or the
> full [Migrating to 1.0](guides/migrating-to-1.0.md) guide for step-by-step
> before/after code.

## Upgrading from 0.x (Luerl-based versions)

Expand Down Expand Up @@ -45,7 +47,12 @@ Everything else — the default sandbox, `_G`/`_ENV` semantics, metatables, and
the standard-library surface — is compatible. The full breaking-change list
is in the [`1.0.0-rc.0`](#100-rc0---2026-05-26) entry below.

## [Unreleased]
## [1.0.0] - 2026-07-15

The first stable release on the Elixir-native Lua 5.3 VM, culminating the
`1.0.0-rc.0` through `rc.3` series. Upgrading from the last public release,
`0.4.0`? See [Migrating to 1.0](guides/migrating-to-1.0.md) for the full
walkthrough. The changes below are those since `rc.3`.

### Changed
- Elixir callbacks now receive the public `Lua.t` (`%Lua{}`) as their state
Expand Down Expand Up @@ -605,7 +612,7 @@ API is intended to be stable. Please report any regressions before final.
- Upgrade to Luerl 1.4.1
- Tables must now be explicitly decoded when receiving as arguments `deflua` and other Elixir callbacks

[unreleased]: https://github.com/tv-labs/lua/compare/v1.0.0-rc.3...HEAD
[1.0.0]: https://github.com/tv-labs/lua/compare/v0.4.0...v1.0.0
[1.0.0-rc.3]: https://github.com/tv-labs/lua/compare/v1.0.0-rc.2...v1.0.0-rc.3
[1.0.0-rc.2]: https://github.com/tv-labs/lua/compare/v1.0.0-rc.1...v1.0.0-rc.2
[1.0.0-rc.1]: https://github.com/tv-labs/lua/compare/v1.0.0-rc.0...v1.0.0-rc.1
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ Add `lua` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:lua, "~> 1.0.0-rc"}
{:lua, "~> 1.0.0"}
]
end
```

Upgrading from `0.4.0` or earlier? See the
[Migrating to 1.0](guides/migrating-to-1.0.md) guide.

## Quickstart

Evaluate Lua with `Lua.eval!/2`. It returns `{results, lua}` where `results`
Expand Down
2 changes: 1 addition & 1 deletion guides/examples/chunks.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.0"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
2 changes: 1 addition & 1 deletion guides/examples/custom_stdlib.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.0"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
2 changes: 1 addition & 1 deletion guides/examples/error_handling.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.0"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
2 changes: 1 addition & 1 deletion guides/examples/quickstart.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.0"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
2 changes: 1 addition & 1 deletion guides/examples/sandboxing.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.0"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
2 changes: 1 addition & 1 deletion guides/examples/userdata.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.0"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
194 changes: 194 additions & 0 deletions guides/migrating-to-1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Migrating to 1.0

`1.0.0` is the first stable release of `Lua` on its own Elixir-native Lua
5.3 virtual machine. The Luerl backend is gone — Luerl is no longer a
runtime dependency — and the public API is now frozen.

This guide is written for the jump straight from `0.4.0` (the last public
release) to `1.0.0`. If you tracked the release candidates, you have
already absorbed most of this; the per-rc detail lives in the
[CHANGELOG](CHANGELOG.md).

The good news first: **most code built on the high-level `Lua` API keeps
working unchanged.** `Lua.new/1`, `Lua.eval!/2`, `Lua.set!/3`, `deflua`,
and `Lua.load_api/2` behave the same. The default sandbox, `_G`/`_ENV`
semantics, metatables, and the standard-library surface are all
compatible. The breaking changes are concentrated at two boundaries: how
values are **encoded**, and how errors are **surfaced**.

## Bump the dependency

```elixir
# mix.exs
def deps do
[
# was: {:lua, "~> 0.4"}
{:lua, "~> 1.0"}
]
end
```

Luerl was a transitive runtime dependency in `0.x`. It no longer is — if
you depended on `:luerl` directly (or called into it), you now need to
declare it yourself, but in almost all cases you should not need it at all.

## Encoded value tags changed

Encoded Lua values used to carry Luerl's internal tags. The new VM uses its
own representation:

| Value | `0.4.0` (Luerl) | `1.0.0` |
| ------------------- | -------------------- | ------------------------ |
| Table reference | `:luerl.tref()` | `{:tref, integer()}` |
| Userdata reference | `:luerl.usdref()` | `{:udref, integer()}` |
| Elixir callable | `:luerl.erl_func()` | `{:native_func, fun}` |
| Compiled Lua func | — | `{:lua_closure, _, _}` |

If you pattern-matched the old tuples, update the patterns. **Better: stop
matching the internal shape at all.** Treat encoded references as opaque
and round-trip them through `Lua.decode!/2` to get plain Elixir data:

```elixir
# Fragile — matches an internal representation that can change:
case value do
{:tref, _} -> handle_table(value)
end

# Durable — decode to plain Elixir and match that:
case Lua.decode!(lua, value) do
map when is_map(map) -> handle_table(map)
end
```

The corresponding guards (`is_table/1`, `is_userdata/1`, `is_lua_func/1`)
are still available in `deflua` callbacks.

## MFA callback encoding was removed

`Lua.encode!/2` no longer accepts the `{module, function, args}` MFA tuple
form, and the `is_mfa/1` guard has been removed from `Lua.API` (it was a
Luerl-era shim that always returned `false`).

```elixir
# 0.4.0 — MFA tuple:
Lua.set!(lua, [:add], {MyModule, :add, []})

# 1.0.0 — a function literal:
Lua.set!(lua, [:add], fn args -> [MyModule.add(args)] end)

# …or a deflua callback in a use Lua.API module:
defmodule MyAPI do
use Lua.API

deflua add(a, b), do: a + b
end
```

Remove any `when is_mfa(value)` clauses — they never matched anything on
`1.0.0` and now reference an undefined guard.

## Bare struct encoding now raises

Encoding a bare Elixir struct used to silently succeed: `Lua.encode!/2`
(and `Lua.set!/3`) matched the struct as a plain map and produced a Lua
table carrying a `"__struct__"` key — a lossy, accidental conversion. That
now raises. Convert the struct explicitly first, selecting the fields Lua
needs:

```elixir
# 0.4.0 — silently encoded {..., "__struct__" => "Elixir.User"}:
Lua.set!(lua, [:user], %User{name: "Ada", age: 36})

# 1.0.0 — convert first:
Lua.set!(lua, [:user], Map.from_struct(%User{name: "Ada", age: 36}))
# or pick fields: %{name: user.name, age: user.age}
```

## Errors are now exception structs, not strings

This is the largest behavioural change for host code that inspects errors.

The public runtime exception is now solely `Lua.RuntimeException`. The
internal VM error structs (`Lua.VM.RuntimeError`, `Lua.VM.TypeError`,
`Lua.VM.ArgumentError`, `Lua.VM.AssertionError`, `Lua.VM.InternalError`)
are wrapped into `Lua.RuntimeException` before crossing any API boundary.
To discriminate a failure, read the wrapper's `:kind` field
(`:error | :type | :argument | :assertion | :internal`); the underlying VM
struct is on `:original`.

The `{:error, _}`-returning APIs now hand back the exception struct itself
rather than a pre-rendered message string, so the caller owns rendering:

```elixir
# 0.4.0 — reason was a formatted string:
case Lua.call_function(lua, [:boom], []) do
{:error, reason, _lua} when is_binary(reason) -> Logger.error(reason)
end

# 1.0.0 — reason is a Lua.RuntimeException; render it yourself:
case Lua.call_function(lua, [:boom], []) do
{:error, %Lua.RuntimeException{} = ex, _lua} ->
Logger.error(Exception.message(ex))
end
```

`Lua.call_function/3` returns `{:error, exception, lua}` where the raised
Lua value (`error(42)`, a table, `nil`, `false`) is preserved on the
exception's `:value` field, matching what `pcall` hands back inside Lua.
`Lua.parse_chunk/1` now returns `{:error, %Lua.CompilerException{}}`
instead of `{:error, [String.t()]}`; its `:errors` field carries the bare,
ANSI-free messages for programmatic inspection.

Render messages through `Exception.message/1`, **not** the `:message`
struct field — messages render lazily, so `:message` may be `nil` on the
struct:

```elixir
# Prefer this:
Exception.message(exception)

# Not this — the field may be nil:
exception.message
```

## Parser error messages have a new format

The native parser no longer produces Luerl's
`"Line 1: syntax error before: ';'"` wording. Messages now read like
`"Expected expression"`. If you have test assertions that string-match the
old wording, update them. For tooling that needs to render parse errors,
use the structured API instead of matching strings:

```elixir
case Lua.Parser.parse_structured(source) do
{:ok, chunk} -> chunk
{:error, errors} -> Enum.map(errors, &Lua.Parser.Error.to_map/1)
end
```

## 64-bit integers wrap on overflow

Arithmetic and bitwise operations now follow Lua 5.3 §3.4.1: integers are
64-bit and wrap around at 2^63 instead of widening to arbitrary-precision
bignums as Luerl did. Code that relied on Luerl returning bignum results
for large integer math will now see wrapped values.

## Chunks are self-contained

`Lua.Chunk` now holds a compiled prototype and is reusable across
`Lua.eval!/2` calls; there is no separate load step. If you cached a loaded
chunk in `0.x`, you can pass the compiled `Lua.Chunk` directly to
`Lua.eval!/2` and reuse it.

## What did *not* change

- The high-level API: `Lua.new/1`, `Lua.eval!/2`, `Lua.set!/3`,
`Lua.get!/2`, `deflua`, `Lua.load_api/2`.
- The default sandbox and its allow-list.
- `_G` / `_ENV` global-access semantics.
- Metatables and metamethod dispatch.
- The standard-library surface (`string`, `table`, `math`, `os`, `io`
stubs, `package`/`require`).

If your code only touches those, the dependency bump is likely the only
change you need.
2 changes: 1 addition & 1 deletion guides/working-with-lua.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 1.0.0-rc.3"}
{:lua, "~> 1.0.0"}
])
```

Expand Down
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Lua.MixProject do
alias Mix.Tasks.Lua.Eval

@url "https://github.com/tv-labs/lua"
@version "1.0.0-rc.3"
@version "1.0.0"

# The curated public API surface rendered on HexDocs. Everything else is an
# implementation detail: its @moduledoc stays intact for source readers and
Expand Down Expand Up @@ -79,6 +79,7 @@ defmodule Lua.MixProject do
],
extras: [
"guides/working-with-lua.livemd": [title: "Working with Lua"],
"guides/migrating-to-1.0.md": [title: "Migrating to 1.0"],
"guides/sandboxing.md": [title: "Security & Sandboxing"],
"guides/mix_tasks.md": [title: "Mix Tasks & the ~LUA sigil"],
"guides/examples/quickstart.livemd": [title: "Quickstart"],
Expand All @@ -92,6 +93,7 @@ defmodule Lua.MixProject do
groups_for_extras: [
Guides: [
"guides/working-with-lua.livemd",
"guides/migrating-to-1.0.md",
"guides/sandboxing.md",
"guides/mix_tasks.md"
],
Expand Down
Loading