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:
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) {