ffi/readline: blocking readline() + add_history(), nil on EOF
Phase 0 binding per PHASE0.md §9. M.readline(prompt) returns the line as a Lua string (the C buffer is freed via libc free immediately after ffi.string copies it) or nil on EOF. M.add_history skips empty lines. Loader handles the case where libreadline-dev's unversioned `libreadline.so` symlink isn't installed — falls through to `readline.so.8` (current Debian/Arch ALARM) and `.so.7` (older) before giving up. This trips on noether-the-LXD: only the runtime package is present. Smoke (stdin from heredoc, two lines + EOF): p1> hello world -> "hello world" p2> second line -> "second line" p3> -> nil (EOF) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+37
-2
@@ -1,5 +1,5 @@
|
||||
-- ffi/readline.lua — GNU readline binding.
|
||||
-- Phase 0: readline + add_history + free. Phase 1: custom key bindings.
|
||||
-- Phase 0: readline + add_history + EOF handling. Phase 1: custom key bindings.
|
||||
-- See docs/PHASE0.md §9.
|
||||
|
||||
local ffi = require("ffi")
|
||||
@@ -10,6 +10,41 @@ void add_history(const char *line);
|
||||
void free(void *ptr);
|
||||
]]
|
||||
|
||||
-- libreadline-dev (which ships the unversioned `libreadline.so` symlink) is
|
||||
-- not assumed to be installed on the runtime host; fall back to versioned
|
||||
-- sonames so a base Debian/Arch with just libreadline runtime works.
|
||||
local function load_readline()
|
||||
local errs = {}
|
||||
for _, name in ipairs({"readline", "readline.so.8", "readline.so.7"}) do
|
||||
local ok, lib = pcall(ffi.load, name)
|
||||
if ok then return lib end
|
||||
errs[#errs+1] = name .. ": " .. tostring(lib)
|
||||
end
|
||||
error("libreadline not loadable: " .. table.concat(errs, "; "))
|
||||
end
|
||||
|
||||
local rl = load_readline()
|
||||
local C = ffi.C
|
||||
|
||||
local M = {}
|
||||
-- Phase 0 stubs; wired with the REPL implementation.
|
||||
|
||||
-- Read one line of input.
|
||||
-- Returns:
|
||||
-- string : the line (no trailing newline)
|
||||
-- nil : EOF (Ctrl-D on empty line)
|
||||
function M.readline(prompt)
|
||||
local cstr = rl.readline(prompt)
|
||||
if cstr == nil then return nil end
|
||||
local s = ffi.string(cstr)
|
||||
C.free(cstr)
|
||||
return s
|
||||
end
|
||||
|
||||
-- Append a non-empty line to readline's in-memory history.
|
||||
function M.add_history(line)
|
||||
if line and #line > 0 then
|
||||
rl.add_history(line)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user