feat(api): Lua.dbg/2 helper plus iex recipes and doctest polish#219
Open
davydog187 wants to merge 6 commits into
Open
feat(api): Lua.dbg/2 helper plus iex recipes and doctest polish#219davydog187 wants to merge 6 commits into
davydog187 wants to merge 6 commits into
Conversation
The original plan suggested ExUnit.CaptureIO for capturing print() output. That pulls :ex_unit into a runtime/production code path, which is a non-starter for an embedded library. Replace with a plain-OTP group-leader swap into a StringIO process, restored in an after block. Also fix references to Lua.eval/2 (the public function is the bang variant, Lua.eval!/2 \u2014 there is no non-bang form), note that Lua.unwrap/1 already exists from A27, and consolidate the five doctests onto lib/lua.ex (lib/lua/vm.ex's surface is awkward to doctest because Lua.VM.execute/2 takes a prebuilt Prototype). Plan: A28
Add Lua.dbg/2 as a debug helper for embedded Lua. It runs the same flow as Lua.eval!/2 but also prints a structured summary alongside the return tuple: source preview, return values, captured print() output, and elapsed time. The dbg/2 form takes a state and a source; dbg/1 defaults the state to Lua.new() for quick one-shots. Print capture works by temporarily swapping the calling process's group leader to a StringIO, restored in an after block so error paths still leave the process IO untouched. The original ExUnit- based plan was rejected because pulling :ex_unit into a runtime code path is a non-starter for an embedded library. Side benefits of the same plan: - Three new doctests on Lua.eval!/2 covering multi-return, table decoding, and the Lua.VM.Display.Closure wrap. - A guides/iex_recipes.md with self-contained snippets for reading globals, calling Lua functions, inspecting tables (including the decode: false display struct), modifying state, and skimming a library with pairs(). All snippets have been run end-to-end and match their claimed output. - Cleanup of stale Lua.eval/2 references in the A27 Display module docstrings (the public function is the bang variant; mix docs was warning). Plan: A28
Discovered while drafting iex recipes for A28 that Lua.VM.Display.peek_table/3 (added in A27) recurses into nested tables without cycle detection, hanging on `return _G` because `_G._G == _G`. A28's recipe works around it; the underlying bug gets its own plan. Plan: A27a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
REPL/iex polish — Lua.dbg, doctest support, debugging recipes
Plan:
.agents/plans/A28-repl-iex-polish.mdGoal
Make iex a first-class debugging surface for embedded Lua. Three deliverables:
Lua.dbg/2— a debug helper that runs Lua and prints a structured summary (source preview, return values, capturedprint()output, elapsed time).Luastate from iex (read globals, call functions, inspect tables, modify state and re-run, build a small Elixir-backed tool).Mid-flight refinement
The original plan suggested
ExUnit.CaptureIOfor capturingprint()output. We rejected that — pulling:ex_unitinto a runtime/production code path is a non-starter for an embedded library. The implementation uses a plain-OTP group-leader swap instead:dbg/2opens aStringIO, makes it the calling process's group leader for the duration of the eval, and restores the original group leader in anafterblock. Lua'sprintis synchronous in-process and writes viaIO.puts/1, which honours the group leader, so all output flows into the buffer cleanly.The plan file was updated up-front (
chore(A28): refine plan to drop ExUnit and use group-leader swap) so the rejected approach is captured for future readers.Success criteria
Lua.dbg(state, source)returns the same asLua.eval!/2and prints a summary including source preview (truncated multi-line previews use⏎as the line-break marker), return values, captured prints, and elapsed milliseconds. Verified manually and intest/lua/dbg_test.exs(14 tests).lib/lua.excovering eval. Net total is 41 in theLuamodule (38 pre-existing + 3 new oneval!/2covering multi-return, table decode, and the closure display struct). The plan originally also asked for ≥2 doctests onlib/lua/vm.ex, butLua.VM.execute/2takes a pre-builtPrototypewhich is awkward to construct in a doctest setup; concentrating onLua.*reads more naturally and clears the same bar.guides/iex_recipes.md— covers reading a Lua global, calling a Lua function (including reaching closures returned fromeval!), inspecting a table (in both decode modes, withLua.unwrap/1round-trip), modifying state and re-running, dbg debugging, skimming a library viapairs(), and building a small "tool" function in Elixir. Every snippet was run end-to-end against the current build.mix testpasses (1626 → 1668 tests, +14 fromtest/lua/dbg_test.exs, 0 failures).What lives where
lib/lua.exdbg/1,2implementation. ImportsKernel, except: [dbg: 2]to shadowKernel.dbg/2. Three new doctests oneval!/2.test/lua/dbg_test.exsguides/iex_recipes.mdlib/lua/vm/display/*.exLua.eval/2doc references cleaned up (the public function is the bang variant;mix docswas warning).Discoveries
Lua.eval!/2. Plan updated, doctest examples corrected, and staleLua.eval/2references in A27's Display module docstrings cleaned up somix docsis quiet on this point.Kernel.dbg/2collision.defmodule Luahad toimport Kernel, except: [dbg: 2]to shadow it. No public-API surface implication.StringIO.contents/1returns{input, output}, not{output, input}. First draft of dbg saw an empty capture because of this. Fixed.inspect/1formats lists of small integers as charlists.[7]→~c"\a". The dbg summary usesinspect(x, charlists: :as_lists)to keep return values unambiguous.Lua.VM.Display.peek_table/3(from A27). Discovered while drafting an_Grecipe —_G._G == _Gcauses the recursive peek to hang. A28 worked around it by encouraging users to iterate withpairs(library)in Lua. The underlying bug gets its own follow-up plan:.agents/plans/A27a-display-cycle-guard.md, filed as part of this PR.iex>lines inside fenced code blocks. The dbgiex>example had to become a non-iex example (using>as the prompt) so the multi-line dbg summary didn't get parsed as a doctest. Test coverage of dbg lives in the dedicated test file instead, where ExUnit.CaptureIO is fine (test-only).Verification
(
mix docsemits a few pre-existing warnings about hidden module references — unrelated to this change.)Manual verification of dbg
Error path:
Out of scope (intentional)
:luacommand-line REPL.Lua.dbg/2is iex-only.IEx.Helpers.peek_table/3— separate plan A27a.Follow-up
.agents/plans/A27a-display-cycle-guard.md— fix the cyclicpeek_table/3recursion soLua.eval!(lua, "return _G", decode: false)doesn't hang.