diff --git a/CHANGELOG.md b/CHANGELOG.md index e755806..968d0d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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 @@ -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 diff --git a/README.md b/README.md index 66a635f..526f313 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/guides/examples/chunks.livemd b/guides/examples/chunks.livemd index 9df6775..da30360 100644 --- a/guides/examples/chunks.livemd +++ b/guides/examples/chunks.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.0"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/guides/examples/custom_stdlib.livemd b/guides/examples/custom_stdlib.livemd index d8b5391..cb8820c 100644 --- a/guides/examples/custom_stdlib.livemd +++ b/guides/examples/custom_stdlib.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.0"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/guides/examples/error_handling.livemd b/guides/examples/error_handling.livemd index 0e19b92..d4a9b7e 100644 --- a/guides/examples/error_handling.livemd +++ b/guides/examples/error_handling.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.0"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/guides/examples/quickstart.livemd b/guides/examples/quickstart.livemd index 0f5d304..fda880b 100644 --- a/guides/examples/quickstart.livemd +++ b/guides/examples/quickstart.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.0"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/guides/examples/sandboxing.livemd b/guides/examples/sandboxing.livemd index b55572d..4690f23 100644 --- a/guides/examples/sandboxing.livemd +++ b/guides/examples/sandboxing.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.0"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/guides/examples/userdata.livemd b/guides/examples/userdata.livemd index 7a5fe43..922d278 100644 --- a/guides/examples/userdata.livemd +++ b/guides/examples/userdata.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.0"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/guides/migrating-to-1.0.md b/guides/migrating-to-1.0.md new file mode 100644 index 0000000..a2c728b --- /dev/null +++ b/guides/migrating-to-1.0.md @@ -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. diff --git a/guides/working-with-lua.livemd b/guides/working-with-lua.livemd index 294be58..df0562d 100644 --- a/guides/working-with-lua.livemd +++ b/guides/working-with-lua.livemd @@ -4,7 +4,7 @@ ```elixir Mix.install([ - {:lua, "~> 1.0.0-rc.3"} + {:lua, "~> 1.0.0"} ]) ``` diff --git a/mix.exs b/mix.exs index 3482138..2de1fb1 100644 --- a/mix.exs +++ b/mix.exs @@ -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 @@ -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"], @@ -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" ],