manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

commit 9dcf5c6be4fdcbe633b191dac2de05616b6f24a3
parent fec7ee91b26a0dc6baae7f835bc2fc23454b2597
Author: Sylvia Ivory <git@sivory.net>
Date:   Sun, 29 Jun 2025 19:32:39 -0700

Get system Lua executor functional

Diffstat:
MCargo.lock | 8++++++++
MCargo.toml | 2++
Mlua/rpc.lua | 69+++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/completion.rs | 17++++++++++-------
Msrc/editor.rs | 7++++---
Msrc/lua.rs | 93++++++++++++++++++++++++++++++++++++++++++-------------------------------------
6 files changed, 119 insertions(+), 77 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -751,6 +751,8 @@ dependencies = [ "reedline", "rexpect", "rowan", + "send_wrapper", + "thiserror", ] [[package]] @@ -1169,6 +1171,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -30,3 +30,5 @@ nu-ansi-term = "0.50.1" reedline = "0.40.0" rexpect = "0.6.2" rowan = "0.16.1" +send_wrapper = "0.6.0" +thiserror = "2.0.12" diff --git a/lua/rpc.lua b/lua/rpc.lua @@ -1,4 +1,4 @@ ----@diagnostic disable: lowercase-global +local serpent do --[[ Serpent source is released under the MIT License @@ -25,8 +25,11 @@ do ]] local n, v = "serpent", "0.303" -- (C) 2012-18 Paul Kulchenko; MIT License local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" - local snum = { [tostring(1 / 0)] = '1/0 --[[math.huge]]', [tostring(-1 / 0)] = '-1/0 --[[-math.huge]]', - [tostring(0 / 0)] = '0/0' } + local snum = { + [tostring(1 / 0)] = '1/0 --[[math.huge]]', + [tostring(-1 / 0)] = '-1/0 --[[-math.huge]]', + [tostring(0 / 0)] = '0/0' + } local badtype = { thread = true, userdata = true, cdata = true } local getmetatable = debug and debug.getmetatable or getmetatable local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+ @@ -70,8 +73,10 @@ do and (nohuge and snum[tostring(s)] or numformat:format(s):gsub(",", ".")) or origsafestr(s) end end - local function comment(s, l) return comm and (l or 0) < comm and ' --[[' .. select(2, pcall(tostring, s)) .. ']]' or - '' end + local function comment(s, l) + return comm and (l or 0) < comm and ' --[[' .. select(2, pcall(tostring, s)) .. ']]' or + '' + end local function globerr(s, l) return globals[s] and globals[s] .. comment(s, l) or not fatal and safestr(select(2, pcall(tostring, s))) or error("Can't serialize " .. tostring(s)) @@ -83,15 +88,15 @@ do return (path or '') .. (plain and path and '.' or '') .. safe, safe end local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or - function(k, o, n) -- k=keys, o=originaltable, n=padding - local maxn, to = tonumber(n) or 12, { number = 'a', string = 'b' } - local function padnum(d) return ("%0" .. tostring(maxn) .. "d"):format(tonumber(d)) end - table.sort(k, function(a, b) - -- sort numeric keys first: k[key] is not nil for numerical keys - return (k[a] ~= nil and 0 or to[type(a)] or 'z') .. (tostring(a):gsub("%d+", padnum)) - < (k[b] ~= nil and 0 or to[type(b)] or 'z') .. (tostring(b):gsub("%d+", padnum)) - end) - end + function(k, o, n) -- k=keys, o=originaltable, n=padding + local maxn, to = tonumber(n) or 12, { number = 'a', string = 'b' } + local function padnum(d) return ("%0" .. tostring(maxn) .. "d"):format(tonumber(d)) end + table.sort(k, function(a, b) + -- sort numeric keys first: k[key] is not nil for numerical keys + return (k[a] ~= nil and 0 or to[type(a)] or 'z') .. (tostring(a):gsub("%d+", padnum)) + < (k[b] ~= nil and 0 or to[type(b)] or 'z') .. (tostring(b):gsub("%d+", padnum)) + end) + end local function val2str(t, name, indent, insref, path, plainindex, level) local ttype, level, mt = type(t), (level or 0), getmetatable(t) local spath, sname = safename(path, name) @@ -136,7 +141,7 @@ do or opts.keyallow and not opts.keyallow[key] or opts.keyignore and opts.keyignore[key] or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types - or sparse and value == nil then -- skipping nils; do nothing + or sparse and value == nil then -- skipping nils; do nothing elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then if not seen[key] and not globals[key] then sref[#sref + 1] = 'placeholder' @@ -145,7 +150,8 @@ do end sref[#sref + 1] = 'placeholder' local path = seen[t] .. '[' .. tostring(seen[key] or globals[key] or gensym(key)) .. ']' - sref[#sref] = path .. space .. '=' .. space .. tostring(seen[value] or val2str(value, nil, indent, path)) + sref[#sref] = path .. + space .. '=' .. space .. tostring(seen[value] or val2str(value, nil, indent, path)) else out[#out + 1] = val2str(value, key, indent, nil, seen[t], plainindex, level + 1) if maxlen then @@ -170,13 +176,13 @@ do return tag .. (func or globerr(t, level)) else return tag .. safestr(t) - end -- handle all other types + end -- handle all other types end local sepr = indent and "\n" or ";" .. space local body = val2str(t, name, indent) -- this call also populates sref local tail = #sref > 1 and table.concat(sref, sepr) .. sepr or '' local warn = opts.comment and #sref > 1 and space .. "--[[incomplete output with shared/self-references skipped]]" or - '' + '' return not name and body .. warn or "do local " .. body .. sepr .. tail .. "return " .. name .. sepr .. "end" end @@ -210,7 +216,7 @@ do } end -rpc = {} +local rpc = {} function rpc.respond(command, data) io.write(serpent.dump({ @@ -218,7 +224,8 @@ function rpc.respond(command, data) data = data, command = command }, { metatostring = false })) - io.write('\n--[[ done ]]--\n') + io.write('\n') + io.flush() end function rpc.globals() @@ -226,11 +233,25 @@ function rpc.globals() end function rpc.exec(code) - local res = assert(load(code, 'repl', 't'))() + local l = _VERSION == 'Lua 5.1' and loadstring or load + local res = l(code, 'repl') + + if not res then + res = assert(l('return (' .. code .. ')', 'repl')) + end - rpc.respond('exec', res) + rpc.respond('exec', res()) end -function rpc.ping() - rpc.respond('ping', 'pong') +for line in io.stdin:lines() do + local cmd, arg = line:match('(.-):(.*)') + if cmd == nil then + cmd = line + end + + local success, err = pcall(rpc[cmd], arg) + + if not success then + rpc.respond('error', err) + end end diff --git a/src/completion.rs b/src/completion.rs @@ -46,12 +46,15 @@ impl LuaCompleter { } fn globals(&self) -> Vec<String> { - self.lua_executor - .globals() - .pairs() - .flatten() - .map(|(k, _): (String, LuaValue)| k) - .collect() + if let Ok(globals) = self.lua_executor.globals() { + globals + .pairs() + .flatten() + .map(|(k, _): (String, LuaValue)| k) + .collect() + } else { + Vec::new() + } } fn resolve_scopes(&self) -> Vec<Scope> { @@ -336,7 +339,7 @@ mod tests { #[test] fn upvalues() { let lua = lua_executor(); - lua.globals().set("foobar", "").unwrap(); + lua.globals().unwrap().set("foobar", "").unwrap(); let mut completer = LuaCompleter::new(lua); diff --git a/src/editor.rs b/src/editor.rs @@ -18,7 +18,7 @@ use crate::{ completion::LuaCompleter, hinter::LuaHinter, inspect::{TableFormat, display_basic}, - lua::{LuaExecutor, MluaExecutor}, + lua::{LuaExecutor, MluaExecutor, SystemLuaExecutor}, parse::LuaHighlighter, validator::LuaValidator, }; @@ -33,8 +33,9 @@ pub struct Editor { impl Editor { pub fn new() -> LuaResult<Self> { - let mlua = Arc::new(MluaExecutor::new()); - let version: String = mlua.globals().get("_VERSION")?; + // let mlua = Arc::new(MluaExecutor::new()); + let mlua = Arc::new(SystemLuaExecutor::new("lua5.1").unwrap()); + let version: String = mlua.globals()?.get("_VERSION")?; let prompt = DefaultPrompt::new( DefaultPromptSegment::Basic(version), diff --git a/src/lua.rs b/src/lua.rs @@ -1,14 +1,19 @@ -use std::{io::{self, Write}, process::{Child, ChildStdin, ChildStdout, Command, Stdio}, sync::{ - atomic::{AtomicBool, Ordering}, Arc, RwLock -}}; +use std::{ + process::Command, + sync::{ + Arc, RwLock, + atomic::{AtomicBool, Ordering}, + }, +}; use mlua::prelude::*; - -use crate::inspect::format_string_bytes; +use rexpect::session::{PtySession, spawn_command}; +use send_wrapper::SendWrapper; +use thiserror::Error; pub trait LuaExecutor: Send + Sync { fn exec(&self, code: &str) -> LuaResult<LuaValue>; - fn globals(&self) -> LuaTable; + fn globals(&self) -> LuaResult<LuaTable>; fn cancel(&self); } @@ -42,8 +47,8 @@ impl LuaExecutor for MluaExecutor { self.lua.load(code).set_name("=repl").eval() } - fn globals(&self) -> LuaTable { - self.lua.globals() + fn globals(&self) -> LuaResult<LuaTable> { + Ok(self.lua.globals()) } fn cancel(&self) { @@ -51,19 +56,22 @@ impl LuaExecutor for MluaExecutor { } } -const LUA_RPC: &str = include_str!("../lua/rpc.lua"); - pub struct SystemLuaExecutor { - child: RwLock<Child>, - stdin: RwLock<ChildStdin>, - stdout: RwLock<ChildStdout>, - + process: RwLock<SendWrapper<PtySession>>, program: String, + lua: Lua, +} + +#[derive(Debug, Error)] +pub enum SystemLuaError { + #[error("lua error: {0}")] + Lua(#[from] LuaError), + #[error("expect error: {0}")] + Expect(#[from] rexpect::error::Error), } pub enum RpcCommand { Globals, - Ping, Exec(String), } @@ -71,57 +79,56 @@ impl RpcCommand { pub fn to_lua(&self) -> String { let func = match self { Self::Globals => "globals", - Self::Ping => "ping", - Self::Exec(_) => "exec" + Self::Exec(_) => "exec", }; - let param = if let Self::Exec(code) = self { - format_string_bytes(code.as_bytes(), false) + if let Self::Exec(code) = self { + format!("{func}:{code}") } else { - String::new() - }; - - format!("rpc.{func}({param})") + func.to_string() + } } } impl SystemLuaExecutor { - pub fn new(program: &str) -> io::Result<Self> { - let (child, stdin, stdout) = Self::obtain_process(program)?; - + pub fn new(program: &str) -> Result<Self, SystemLuaError> { Ok(Self { - child: RwLock::new(child), - stdin: RwLock::new(stdin), - stdout: RwLock::new(stdout), - + process: RwLock::new(SendWrapper::new(Self::obtain_process(program)?)), program: program.to_string(), + lua: unsafe { Lua::unsafe_new() }, }) } - fn obtain_process(program: &str) -> io::Result<(Child, ChildStdin, ChildStdout)> { - let mut child = Command::new(program) - .stderr(Stdio::null()) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; + fn obtain_process(program: &str) -> Result<PtySession, SystemLuaError> { + let mut cmd = Command::new(program); + cmd.arg("lua/rpc.lua"); - let mut stdin = child.stdin.take().unwrap(); + Ok(spawn_command(cmd, None)?) + } - stdin.write_all(LUA_RPC.as_bytes())?; + fn request(&self, command: RpcCommand) -> Result<LuaTable, SystemLuaError> { + let mut process = self.process.write().expect("write process"); - let stdout = child.stdout.take().unwrap(); + let cmd = command.to_lua(); + process.send_line(&cmd)?; - Ok((child, stdin, stdout)) + let code = process.read_line()?; + + Ok(self.lua.load(code).eval()?) } } impl LuaExecutor for SystemLuaExecutor { fn exec(&self, code: &str) -> LuaResult<LuaValue> { - todo!() + self.request(RpcCommand::Exec(code.to_string())) + .map_err(LuaError::external)? + .get("data") } - fn globals(&self) -> LuaTable { - todo!() + fn globals(&self) -> LuaResult<LuaTable> { + self.request(RpcCommand::Globals) + .map_err(LuaError::external)? + .get("data") } fn cancel(&self) {