Move io wrapper to sol

This commit is contained in:
Mm2PL 2024-10-07 23:08:22 +02:00
parent 3faa5cb9b3
commit e1dcf28dac
No known key found for this signature in database
GPG key ID: 94AC9B80EFA15ED9
3 changed files with 223 additions and 237 deletions

View file

@ -23,6 +23,7 @@
# include <lualib.h>
# include <QJsonDocument>
# include <sol/forward.hpp>
# include <sol/overload.hpp>
# include <sol/sol.hpp>
# include <sol/types.hpp>
# include <sol/variadic_args.hpp>
@ -217,24 +218,7 @@ void PluginController::openLibrariesFor(Plugin *plugin, const QDir &pluginDir)
lua_seti(L, -2, 3);
lua_pop(L, 2); // remove package, package.searchers
// NOLINTNEXTLINE(*-avoid-c-arrays)
static const luaL_Reg ioLib[] = {
{"close", lua::api::io_close},
{"flush", lua::api::io_flush},
{"input", lua::api::io_input},
{"lines", lua::api::io_lines},
{"open", lua::api::io_open},
{"output", lua::api::io_output},
{"popen", lua::api::io_popen}, // stub
{"read", lua::api::io_read},
{"tmpfile", lua::api::io_tmpfile}, // stub
{"write", lua::api::io_write},
// type = realio.type
{nullptr, nullptr},
};
// TODO: io.popen stub
auto iolibIdx = lua::pushEmptyTable(L, 1);
luaL_setfuncs(L, ioLib, 0);
// set ourio.type = realio.type
lua_pushvalue(L, iolibIdx);
@ -299,6 +283,26 @@ void PluginController::initSol(sol::state_view &lua, Plugin *plugin)
c2["HTTPMethod"] = lua::createEnumTable<NetworkRequestType>(lua);
c2["EventType"] = lua::createEnumTable<lua::api::EventType>(lua);
c2["LogLevel"] = lua::createEnumTable<lua::api::LogLevel>(lua);
sol::table io = g["io"];
io.set_function(
"open", sol::overload(&lua::api::io_open, &lua::api::io_open_modeless));
io.set_function("lines", sol::overload(&lua::api::io_lines,
&lua::api::io_lines_noargs));
io.set_function("input", sol::overload(&lua::api::io_input_argless,
&lua::api::io_input_name,
&lua::api::io_input_file));
io.set_function("output", sol::overload(&lua::api::io_output_argless,
&lua::api::io_output_name,
&lua::api::io_output_file));
io.set_function("close", sol::overload(&lua::api::io_close_argless,
&lua::api::io_close_file));
io.set_function("flush", sol::overload(&lua::api::io_flush_argless,
&lua::api::io_flush_file));
io.set_function("read", &lua::api::io_read);
io.set_function("write", &lua::api::io_write);
io.set_function("popen", &lua::api::io_popen);
io.set_function("tmpfile", &lua::api::io_tmpfile);
}
void PluginController::load(const QFileInfo &index, const QDir &pluginDir,

View file

@ -2,13 +2,23 @@
# include "controllers/plugins/api/IOWrapper.hpp"
# include "Application.hpp"
# include "controllers/plugins/LuaUtilities.hpp"
# include "common/QLogging.hpp"
# include "controllers/plugins/PluginController.hpp"
# include <lauxlib.h>
# include <lua.h>
# include <sol/forward.hpp>
# include <sol/in_place.hpp>
# include <sol/object.hpp>
# include <sol/protected_function_result.hpp>
# include <sol/state_view.hpp>
# include <sol/types.hpp>
# include <sol/variadic_args.hpp>
# include <sol/variadic_results.hpp>
# include <cerrno>
# include <stdexcept>
# include <utility>
namespace chatterino::lua::api {
@ -89,45 +99,28 @@ struct LuaFileMode {
}
};
int ioError(lua_State *L, const QString &value, int errnoequiv)
sol::variadic_results ioError(lua_State *L, const QString &value,
int errnoequiv)
{
lua_pushnil(L);
lua::push(L, value);
lua::push(L, errnoequiv);
return 3;
sol::variadic_results out;
out.push_back(sol::nil);
out.push_back(sol::make_object(L, value.toStdString()));
out.push_back({L, sol::in_place_type<int>, errnoequiv});
return out;
}
// NOLINTBEGIN(*vararg)
int io_open(lua_State *L)
sol::variadic_results io_open(sol::this_state L, QString filename,
QString strmode)
{
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
luaL_error(L, "internal error: no plugin");
return 0;
throw std::runtime_error("internal error: no plugin");
}
LuaFileMode mode;
if (lua_gettop(L) == 2)
{
// we have a mode
QString smode;
if (!lua::pop(L, &smode))
{
return luaL_error(
L,
"io.open mode (2nd argument) must be a string or not present");
}
mode = LuaFileMode(smode);
LuaFileMode mode(strmode);
if (!mode.error.isEmpty())
{
return luaL_error(L, mode.error.toStdString().c_str());
}
}
QString filename;
if (!lua::pop(L, &filename))
{
return luaL_error(L,
"io.open filename (1st argument) must be a string");
throw std::runtime_error(mode.error.toStdString());
}
QFileInfo file(pl->dataDirectory().filePath(filename));
auto abs = file.absoluteFilePath();
@ -142,39 +135,35 @@ int io_open(lua_State *L)
"Plugin does not have permissions to access given file.",
EACCES);
}
lua_getfield(L, LUA_REGISTRYINDEX, REG_REAL_IO_NAME);
lua_getfield(L, -1, "open");
lua_remove(L, -2); // remove LUA_REGISTRYINDEX[REAL_IO_NAME]
lua::push(L, abs);
lua::push(L, mode.toString());
lua_call(L, 2, 3);
return 3;
sol::state_view lua(L);
auto open = lua.registry()[REG_REAL_IO_NAME]["open"];
sol::protected_function_result res =
open(abs.toStdString(), mode.toString().toStdString());
return res;
}
sol::variadic_results io_open_modeless(sol::this_state L, QString filename)
{
return io_open(L, std::move(filename), "r");
}
int io_lines(lua_State *L)
sol::variadic_results io_lines_noargs(sol::this_state L)
{
sol::state_view lua(L);
auto lines = lua.registry()[REG_REAL_IO_NAME]["lines"];
sol::protected_function_result res = lines();
return res;
}
sol::variadic_results io_lines(sol::this_state L, QString filename,
sol::variadic_args args)
{
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
luaL_error(L, "internal error: no plugin");
return 0;
}
if (lua_gettop(L) == 0)
{
// io.lines() case, just call realio.lines
lua_getfield(L, LUA_REGISTRYINDEX, REG_REAL_IO_NAME);
lua_getfield(L, -1, "lines");
lua_remove(L, -2); // remove LUA_REGISTRYINDEX[REAL_IO_NAME]
lua_call(L, 0, 1);
return 1;
}
QString filename;
if (!lua::pop(L, &filename))
{
return luaL_error(
L,
"io.lines filename (1st argument) must be a string or not present");
throw std::runtime_error("internal error: no plugin");
}
sol::state_view lua(L);
QFileInfo file(pl->dataDirectory().filePath(filename));
auto abs = file.absoluteFilePath();
qCDebug(chatterinoLua) << "[" << pl->id << ":" << pl->meta.name
@ -187,184 +176,164 @@ int io_lines(lua_State *L)
"Plugin does not have permissions to access given file.",
EACCES);
}
// Our stack looks like this:
// - {...}[1]
// - {...}[2]
// ...
// We want:
// - REG[REG_REAL_IO_NAME].lines
// - absolute file path
// - {...}[1]
// - {...}[2]
// ...
lua_getfield(L, LUA_REGISTRYINDEX, REG_REAL_IO_NAME);
lua_getfield(L, -1, "lines");
lua_remove(L, -2); // remove LUA_REGISTRYINDEX[REAL_IO_NAME]
lua_insert(L, 1); // move function to start of stack
lua::push(L, abs);
lua_insert(L, 2); // move file name just after the function
lua_call(L, lua_gettop(L) - 1, LUA_MULTRET);
return lua_gettop(L);
auto lines = lua.registry()[REG_REAL_IO_NAME]["lines"];
sol::protected_function_result res = lines(abs.toStdString(), args);
return res;
}
namespace {
// This is the code for both io.input and io.output
int globalFileCommon(lua_State *L, bool output)
{
sol::variadic_results io_input_argless(sol::this_state L)
{
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
luaL_error(L, "internal error: no plugin");
return 0;
throw std::runtime_error("internal error: no plugin");
}
// Three signature cases:
// io.input()
// io.input(file)
// io.input(name)
if (lua_gettop(L) == 0)
{
// We have no arguments, call realio.input()
lua_getfield(L, LUA_REGISTRYINDEX, REG_REAL_IO_NAME);
if (output)
{
lua_getfield(L, -1, "output");
}
else
{
lua_getfield(L, -1, "input");
}
lua_remove(L, -2); // remove LUA_REGISTRYINDEX[REAL_IO_NAME]
lua_call(L, 0, 1);
return 1;
}
if (lua_gettop(L) != 1)
{
return luaL_error(L, "Too many arguments given to io.input().");
}
// Now check if we have a file or name
auto *p = luaL_testudata(L, 1, LUA_FILEHANDLE);
if (p == nullptr)
{
// this is not a file handle, send it to open
luaL_getsubtable(L, LUA_REGISTRYINDEX, REG_C2_IO_NAME);
lua_getfield(L, -1, "open");
lua_remove(L, -2); // remove io
sol::state_view lua(L);
lua_pushvalue(L, 1); // dupe arg
if (output)
{
lua_pushstring(L, "w");
}
else
{
lua_pushstring(L, "r");
}
lua_call(L, 2, 1); // call ourio.open(arg1, 'r'|'w')
// if this isn't a string ourio.open errors
// this leaves us with:
// 1. arg
// 2. new_file
lua_remove(L, 1); // remove arg, replacing it with new_file
}
// file handle, pass it off to realio.input
lua_getfield(L, LUA_REGISTRYINDEX, REG_REAL_IO_NAME);
if (output)
{
lua_getfield(L, -1, "output");
}
else
{
lua_getfield(L, -1, "input");
}
lua_remove(L, -2); // remove LUA_REGISTRYINDEX[REAL_IO_NAME]
lua_pushvalue(L, 1); // duplicate arg
lua_call(L, 1, 1);
return 1;
}
} // namespace
int io_input(lua_State *L)
auto func = lua.registry()[REG_REAL_IO_NAME]["input"];
sol::protected_function_result res = func();
return res;
}
sol::variadic_results io_input_file(sol::this_state L, sol::userdata file)
{
return globalFileCommon(L, false);
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
throw std::runtime_error("internal error: no plugin");
}
sol::state_view lua(L);
auto func = lua.registry()[REG_REAL_IO_NAME]["input"];
sol::protected_function_result res = func(file);
return res;
}
sol::variadic_results io_input_name(sol::this_state L, QString filename)
{
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
throw std::runtime_error("internal error: no plugin");
}
sol::state_view lua(L);
auto res = io_open(L, std::move(filename), "r");
if (res.size() != 1)
{
throw std::runtime_error(res.at(1).as<std::string>());
}
auto obj = res.at(0);
if (obj.get_type() != sol::type::userdata)
{
throw std::runtime_error("a file must be a userdata.");
}
return io_input_file(L, obj);
}
int io_output(lua_State *L)
sol::variadic_results io_output_argless(sol::this_state L)
{
return globalFileCommon(L, true);
}
int io_close(lua_State *L)
{
if (lua_gettop(L) > 1)
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
return luaL_error(
L, "Too many arguments for io.close. Expected one or zero.");
throw std::runtime_error("internal error: no plugin");
}
if (lua_gettop(L) == 0)
sol::state_view lua(L);
auto func = lua.registry()[REG_REAL_IO_NAME]["output"];
sol::protected_function_result res = func();
return res;
}
sol::variadic_results io_output_file(sol::this_state L, sol::userdata file)
{
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
lua_getfield(L, LUA_REGISTRYINDEX, "_IO_output");
throw std::runtime_error("internal error: no plugin");
}
lua_getfield(L, -1, "close");
lua_pushvalue(L, -2);
lua_call(L, 1, 0);
return 0;
}
sol::state_view lua(L);
int io_flush(lua_State *L)
auto func = lua.registry()[REG_REAL_IO_NAME]["output"];
sol::protected_function_result res = func(file);
return res;
}
sol::variadic_results io_output_name(sol::this_state L, QString filename)
{
if (lua_gettop(L) > 1)
auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L);
if (pl == nullptr)
{
return luaL_error(
L, "Too many arguments for io.flush. Expected one or zero.");
throw std::runtime_error("internal error: no plugin");
}
lua_getfield(L, LUA_REGISTRYINDEX, "_IO_output");
lua_getfield(L, -1, "flush");
lua_pushvalue(L, -2);
lua_call(L, 1, 0);
return 0;
}
int io_read(lua_State *L)
{
if (lua_gettop(L) > 1)
sol::state_view lua(L);
auto res = io_open(L, std::move(filename), "w");
if (res.size() != 1)
{
return luaL_error(
L, "Too many arguments for io.read. Expected one or zero.");
throw std::runtime_error(res.at(1).as<std::string>());
}
lua_getfield(L, LUA_REGISTRYINDEX, "_IO_input");
lua_getfield(L, -1, "read");
lua_insert(L, 1);
lua_insert(L, 2);
lua_call(L, lua_gettop(L) - 1, 1);
return 1;
auto obj = res.at(0);
if (obj.get_type() != sol::type::userdata)
{
throw std::runtime_error("internal error: a file must be a userdata.");
}
return io_output_file(L, obj);
}
int io_write(lua_State *L)
bool io_close_argless(sol::this_state L)
{
lua_getfield(L, LUA_REGISTRYINDEX, "_IO_output");
lua_getfield(L, -1, "write");
lua_insert(L, 1);
lua_insert(L, 2);
// (input)
// (input).read
// args
lua_call(L, lua_gettop(L) - 1, 1);
return 1;
sol::state_view lua(L);
auto out = lua.registry()["_IO_output"];
return io_close_file(L, out);
}
int io_popen(lua_State *L)
bool io_close_file(sol::this_state L, sol::userdata file)
{
return luaL_error(L, "io.popen: This function is a stub!");
sol::state_view lua(L);
return file["close"](file);
}
int io_tmpfile(lua_State *L)
void io_flush_argless(sol::this_state L)
{
return luaL_error(L, "io.tmpfile: This function is a stub!");
sol::state_view lua(L);
auto out = lua.registry()["_IO_output"];
io_flush_file(L, out);
}
void io_flush_file(sol::this_state L, sol::userdata file)
{
sol::state_view lua(L);
file["flush"](file);
}
sol::variadic_results io_read(sol::this_state L, sol::variadic_args args)
{
sol::state_view lua(L);
auto inp = lua.registry()["_IO_input"];
if (!inp.is<sol::userdata>())
{
throw std::runtime_error("Input not set to a file");
}
sol::protected_function read = inp["read"];
return read(inp, args);
}
sol::variadic_results io_write(sol::this_state L, sol::variadic_args args)
{
sol::state_view lua(L);
auto out = lua.registry()["_IO_output"];
if (!out.is<sol::userdata>())
{
throw std::runtime_error("Output not set to a file");
}
sol::protected_function write = out["write"];
return write(out, args);
}
void io_popen()
{
throw std::runtime_error("io.popen: This function is a stub!");
}
void io_tmpfile()
{
throw std::runtime_error("io.tmpfile: This function is a stub!");
}
// NOLINTEND(*vararg)

View file

@ -1,4 +1,7 @@
#pragma once
#include <sol/types.hpp>
#include <sol/variadic_args.hpp>
#include <sol/variadic_results.hpp>
#ifdef CHATTERINO_HAVE_PLUGINS
struct lua_State;
@ -20,7 +23,9 @@ const char *const REG_C2_IO_NAME = "c2io";
* @lua@param mode nil|"r"|"w"|"a"|"r+"|"w+"|"a+"
* @exposed io.open
*/
int io_open(lua_State *L);
sol::variadic_results io_open(sol::this_state L, QString filename,
QString strmode);
sol::variadic_results io_open_modeless(sol::this_state L, QString filename);
/**
* Equivalent to io.input():lines("l") or a specific iterator over given file
@ -32,7 +37,9 @@ int io_open(lua_State *L);
* @lua@param ...
* @exposed io.lines
*/
int io_lines(lua_State *L);
sol::variadic_results io_lines(sol::this_state L, QString filename,
sol::variadic_args args);
sol::variadic_results io_lines_noargs(sol::this_state L);
/**
* Opens a file and sets it as default input or if given no arguments returns the default input.
@ -42,7 +49,9 @@ int io_lines(lua_State *L);
* @lua@return nil|FILE*
* @exposed io.input
*/
int io_input(lua_State *L);
sol::variadic_results io_input_argless(sol::this_state L);
sol::variadic_results io_input_file(sol::this_state L, sol::userdata file);
sol::variadic_results io_input_name(sol::this_state L, QString filename);
/**
* Opens a file and sets it as default output or if given no arguments returns the default output
@ -52,7 +61,9 @@ int io_input(lua_State *L);
* @lua@return nil|FILE*
* @exposed io.output
*/
int io_output(lua_State *L);
sol::variadic_results io_output_argless(sol::this_state L);
sol::variadic_results io_output_file(sol::this_state L, sol::userdata file);
sol::variadic_results io_output_name(sol::this_state L, QString filename);
/**
* Closes given file or io.output() if not given.
@ -61,7 +72,8 @@ int io_output(lua_State *L);
* @lua@param nil|FILE*
* @exposed io.close
*/
int io_close(lua_State *L);
bool io_close_argless(sol::this_state L);
bool io_close_file(sol::this_state L, sol::userdata file);
/**
* Flushes the buffer for given file or io.output() if not given.
@ -70,7 +82,8 @@ int io_close(lua_State *L);
* @lua@param nil|FILE*
* @exposed io.flush
*/
int io_flush(lua_State *L);
void io_flush_argless(sol::this_state L);
void io_flush_file(sol::this_state L, sol::userdata file);
/**
* Reads some data from the default input file
@ -79,7 +92,7 @@ int io_flush(lua_State *L);
* @lua@param nil|string
* @exposed io.read
*/
int io_read(lua_State *L);
sol::variadic_results io_read(sol::this_state L, sol::variadic_args args);
/**
* Writes some data to the default output file
@ -88,10 +101,10 @@ int io_read(lua_State *L);
* @lua@param nil|string
* @exposed io.write
*/
int io_write(lua_State *L);
sol::variadic_results io_write(sol::this_state L, sol::variadic_args args);
int io_popen(lua_State *L);
int io_tmpfile(lua_State *L);
void io_popen();
void io_tmpfile();
// NOLINTEND(readability-identifier-naming)
} // namespace chatterino::lua::api