]> PatchyGit - patchyircd.git/commitdiff
admin: rework lua bindings to map
authorEgor <egor@patchysicp.xyz>
Wed, 4 Feb 2026 13:07:26 +0000 (15:07 +0200)
committerEgor <egor@patchysicp.xyz>
Wed, 4 Feb 2026 13:07:26 +0000 (15:07 +0200)
src/admin.cpp
src/job.hpp
src/map.hpp
src/util.hpp

index e78cad6..b415b9f 100644 (file)
 #include <lua.hpp>
 static lua_State *GL = 0;
 
-#define MAPTYPE_STRINGSET 0
-#define MAPTYPE_CLIENT 1
-#define MAPTYPE_CHANNEL 2
-static int map_index(lua_State *L)
-{
-       map_base *m = *(map_base **)luaL_checkudata(L, 1, "map");
-       const char *key = luaL_checkstring(L, 2);
-       void *value = m->_get(key);
-       if (!value)
-               return 0;
-       lua_getiuservalue(L, 1, 1);
-       switch (lua_tointeger(L, -1)) {
-       case MAPTYPE_STRINGSET:
-               lua_pushvalue(L, 2);
-               return 1;
-       case MAPTYPE_CLIENT:
-               *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = (Client *)value;
-               luaL_setmetatable(L, "client");
-               return 1;
-       case MAPTYPE_CHANNEL:
-               *(Channel **)lua_newuserdatauv(L, sizeof(Channel *), 0) = (Channel *)value;
-               luaL_setmetatable(L, "channel");
-               return 1;
-       }
-       return 0;
+static std::string_view checkstringview(lua_State *L, int idx) {
+       size_t size = 0;
+       const char *key = luaL_checklstring(L, idx, &size);
+       return std::string_view(key, size);
 }
 
-static int map_newindex(lua_State *L)
-{
-       map_base *m = *(map_base **)luaL_checkudata(L, 1, "map");
-       const char *key = luaL_checkstring(L, 2);
-       luaL_checkany(L, 3);
-       if (lua_isnil(L, 3)) {
-               m->_remove(key);
-               return 0;
+template<typename T>
+struct MptrOwnerType {};
+template<typename T, typename U>
+struct MptrOwnerType<U T::*> {
+       using type = T;
+};
+
+template<auto P> // <int(T::* P)(lua_State *L)>
+int LuaForwarder(lua_State *L) {
+       using T = MptrOwnerType<decltype(P)>::type;
+       return (static_cast<T*>(luaL_checkudata(L, 1, T::mtname()))->*P)(L);
+}
+
+template<typename T>
+struct SetRef {
+       map_base2<T> &s;
+
+       static consteval const char *mtname() {
+               if constexpr (std::same_as<T, std::string>) {
+                       return "Set<std::string>";
+               } else if constexpr (std::same_as<T, Client*>) {
+                       return "Set<Client*>";
+               } else if constexpr (std::same_as<T, Channel*>) {
+                       return "Set<Channel*>";
+               } else {
+                       static_assert(false);
+               }
+       };
+       static void pushval(lua_State *L, const T& v) requires std::same_as<T, std::string> {
+               lua_pushlstring(L, v.data(), v.size());
        }
-       lua_getiuservalue(L, 1, 1);
-       switch (lua_tointeger(L, -1)) {
-       case MAPTYPE_STRINGSET:
-               m->_set(key);
-               break;
-       case MAPTYPE_CLIENT: {
-               void **p = (void **)luaL_checkudata(L, 3, "client");
-               m->_set(*p);
-               break;
+       static void pushval(lua_State *L, const T& v) requires std::same_as<T, Client*> {
+               *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = v;
+               luaL_setmetatable(L, "Client");
        }
-       case MAPTYPE_CHANNEL: {
-               void **p = (void **)luaL_checkudata(L, 3, "channel");
-               m->_set(*p);
-               break;
+       static void pushval(lua_State *L, const T& v) requires std::same_as<T, Channel*> {
+               *(Channel **)lua_newuserdatauv(L, sizeof(Channel *), 0) = v;
+               luaL_setmetatable(L, "Channel");
        }
+
+       int index(lua_State *L) {
+               std::string_view key = checkstringview(L, 2);
+               if (!s.has(key))
+                       return 0;
+               pushval(L, s.get(key));
+               return 1;
        }
-       return 0;
-}
-static int map_iter_next(lua_State *L)
-{
-       map_base::iter *it = (map_base::iter *)luaL_checkudata(L, 1, "map_iter");
-       auto [key, value] = it->next();
-       if (!value)
+       int newindex(lua_State *L) {
+               std::string_view key = checkstringview(L, 2);
+               luaL_checkany(L, 3);
+               if (lua_isnil(L, 3)) {
+                       s.remove(key);
+                       return 0;
+               }
+               if constexpr (std::same_as<T, std::string>) {
+                       s.add(std::string(key)); // FIXME:
+               } else if constexpr (std::same_as<T, Client*>) {
+                       s.add(*(Client **)luaL_checkudata(L, 3, "Client"));
+               } else if constexpr (std::same_as<T, Channel*>) {
+                       s.add(*(Channel **)luaL_checkudata(L, 3, "Channel"));
+               } else {
+                       static_assert(false);
+               }
                return 0;
-       lua_getiuservalue(L, 1, 1);
-       switch (lua_tointeger(L, -1)) {
-       case MAPTYPE_STRINGSET:
-               lua_pushlstring(L, key.data(), key.size());
-               lua_pushlstring(L, key.data(), key.size());
-               return 2;
-       case MAPTYPE_CLIENT:
-               lua_pushlstring(L, key.data(), key.size());
-               *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = (Client *)value;
-               luaL_setmetatable(L, "client");
-               return 2;
-       case MAPTYPE_CHANNEL:
-               lua_pushlstring(L, key.data(), key.size());
-               *(Channel **)lua_newuserdatauv(L, sizeof(Channel *), 0) = (Channel *)value;
-               luaL_setmetatable(L, "channel");
-               return 2;
        }
-       return 0;
-}
-static int map_pairs(lua_State *L)
-{
-       map_base *m = *(map_base **)luaL_checkudata(L, 1, "map");
-       lua_pushcfunction(L, map_iter_next);
-       map_base::iter *it = (map_base::iter *)lua_newuserdatauv(L, sizeof(map_base::iter), 1);
-       memset(it, 0, sizeof(*it));
-       it->root = m;
-       luaL_setmetatable(L, "map_iter");
-       lua_getiuservalue(L, 1, 1);
-       lua_setiuservalue(L, -2, 1);
-       lua_pushnil(L);
-       return 3;
-}
-static void map_push(lua_State *L, map_base *map, int type)
-{
-       *(map_base **)lua_newuserdatauv(L, sizeof(map_base *), 1) = map;
-       luaL_setmetatable(L, "map");
-       lua_pushinteger(L, type);
-       lua_setiuservalue(L, -2, 1);
+       struct Iter {
+               static consteval const char *mtname() {
+                       if constexpr (std::same_as<T, std::string>) {
+                               return "Set<std::string>::Iter";
+                       } else if constexpr (std::same_as<T, Client*>) {
+                               return "Set<Client*>::Iter";
+                       } else if constexpr (std::same_as<T, Channel*>) {
+                               return "Set<Channel*>::Iter";
+                       } else {
+                               static_assert(false);
+                       }
+               };
+               size_t index = 0;
+               int next(lua_State *L) {
+                       lua_getiuservalue(L, 1, 1);
+                       SetRef &ref = *(SetRef *)lua_touserdata(L, -1);
+                       auto &m = ref.s.m;
+                       auto it = m.begin() + index;
+                       if (it == m.end())
+                               return 0;
+                       index++;
+                       auto key = to_key(*it);
+                       lua_pushlstring(L, key.data(), key.size());
+                       pushval(L, *it);
+                       return 2;
+               }
+       };
+       int pairs(lua_State *L) {
+               lua_pushcfunction(L, LuaForwarder<&Iter::next>);
+               new (lua_newuserdatauv(L, sizeof(Iter), 1)) Iter();
+               luaL_setmetatable(L, Iter::mtname());
+               lua_pushvalue(L, 1);
+               lua_setiuservalue(L, -2, 1);
+               lua_pushnil(L);
+               return 3;
+       }
+       static void push(lua_State *L, map_base2<T> *s) {
+               new (lua_newuserdatauv(L, sizeof(SetRef), 0)) SetRef(*s);
+               luaL_setmetatable(L, mtname());
+       }
+       static void regmt(lua_State *L) {
+               if (luaL_newmetatable(L, mtname())) {
+                       luaL_Reg funcs[] = {
+                               {"__index", LuaForwarder<&SetRef::index>},
+                               {"__newindex", LuaForwarder<&SetRef::newindex>},
+                               {"__pairs", LuaForwarder<&SetRef::pairs>},
+                               {0, 0},
+                       };
+                       luaL_setfuncs(L, funcs, 0);
+               }
+               lua_pop(L, 1);
+               luaL_newmetatable(L, Iter::mtname());
+               lua_pop(L, 1);
+       }
+};
+template<typename T>
+static void SetRef_push(lua_State *L, map_base2<T> *s) {
+       SetRef<T>::push(L, s);
 }
+
 static int client_index(lua_State *L)
 {
-       Client *cl = *(Client **)luaL_checkudata(L, 1, "client");
+       Client *cl = *(Client **)luaL_checkudata(L, 1, "Client");
        const char *key = luaL_checkstring(L, 2);
        if (!cl)
                return 0;
 #define string_getter(c, s) if (!strcmp(key, #s)) return lua_pushstring(L, c->s), 1
 #define integer_getter(c, s) if (!strcmp(key, #s)) return lua_pushinteger(L, c->s), 1
-#define map_getter(c, s, t) if (!strcmp(key, #s)) return map_push(L, &c->s, t), 1
+#define SetRef_getter(c, s) if (!strcmp(key, #s)) return SetRef_push(L, &c->s), 1
        integer_getter(cl, fd);
        /* cctx */
        if (!strcmp(key, "next")) {
                if (!cl->next)
                        return 0;
                *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = cl->next;
-               luaL_setmetatable(L, "client");
+               luaL_setmetatable(L, "Client");
                return 1;
        }
        string_getter(cl, nick);
@@ -144,9 +176,9 @@ static int client_index(lua_State *L)
        /* rbuf/rlen/wbuf/wlen */
        integer_getter(cl, umode);
        integer_getter(cl, regstate);
-       map_getter(cl, channels, MAPTYPE_CHANNEL);
-       map_getter(cl, invites, MAPTYPE_CHANNEL);
-       map_getter(cl, monitor, MAPTYPE_STRINGSET);
+       SetRef_getter(cl, channels);
+       SetRef_getter(cl, invites);
+       SetRef_getter(cl, monitor);
        integer_getter(cl, cookie);
        integer_getter(cl, signon);
        integer_getter(cl, lastactivity);
@@ -166,7 +198,7 @@ static int client_index(lua_State *L)
 }
 static int channel_index(lua_State *L)
 {
-       Channel *chan = *(Channel **)luaL_checkudata(L, 1, "channel");
+       Channel *chan = *(Channel **)luaL_checkudata(L, 1, "Channel");
        const char *key = luaL_checkstring(L, 2);
        if (!chan)
                return 0;
@@ -176,18 +208,18 @@ static int channel_index(lua_State *L)
        string_getter(chan, topic_who);
        integer_getter(chan, topic_when);
        integer_getter(chan, creation);
-       map_getter(chan, members, MAPTYPE_CLIENT);
-       map_getter(chan, o, MAPTYPE_CLIENT);
-       map_getter(chan, v, MAPTYPE_CLIENT);
+       SetRef_getter(chan, members);
+       SetRef_getter(chan, o);
+       SetRef_getter(chan, v);
        integer_getter(chan, member_count);
        integer_getter(chan, dmode);
-       map_getter(chan, listb, MAPTYPE_STRINGSET);
-       map_getter(chan, liste, MAPTYPE_STRINGSET);
-       map_getter(chan, listI, MAPTYPE_STRINGSET);
-       map_getter(chan, listq, MAPTYPE_STRINGSET);
-       map_getter(chan, listwo, MAPTYPE_STRINGSET);
-       map_getter(chan, listwv, MAPTYPE_STRINGSET);
-       map_getter(chan, invitees, MAPTYPE_CLIENT);
+       SetRef_getter(chan, listb);
+       SetRef_getter(chan, liste);
+       SetRef_getter(chan, listI);
+       SetRef_getter(chan, listq);
+       SetRef_getter(chan, listwo);
+       SetRef_getter(chan, listwv);
+       SetRef_getter(chan, invitees);
        string_getter(chan, chankey);
        integer_getter(chan, member_limit);
        return 0;
@@ -196,17 +228,17 @@ static int globals_index(lua_State *L)
 {
        const char *key = luaL_checkstring(L, 2);
        if (!strcmp(key, "client_map"))
-               return map_push(L, &client_map, MAPTYPE_CLIENT), 1;
+               return SetRef_push(L, &client_map), 1;
        if (!strcmp(key, "channel_map"))
-               return map_push(L, &channel_map, MAPTYPE_CHANNEL), 1;
+               return SetRef_push(L, &channel_map), 1;
        if (!strcmp(key, "clients")) {
                *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = clients;
-               luaL_setmetatable(L, "client");
+               luaL_setmetatable(L, "Client");
                return 1;
        }
        if (!strcmp(key, "dyingclients")) {
                *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = dyingclients;
-               luaL_setmetatable(L, "client");
+               luaL_setmetatable(L, "Client");
                return 1;
        }
        return 0;
@@ -222,25 +254,25 @@ static int command_eval(lua_State *L)
 }
 static int fn_client_privmsg(lua_State *L)
 {
-       Client *cl = *(Client **)luaL_checkudata(L, 1, "client");
+       Client *cl = *(Client **)luaL_checkudata(L, 1, "Client");
        const char *msg = luaL_checkstring(L, 2);
        msg_privmsg_server(luaL_optstring(L, 3, hostname), cl->nick, msg), sendbuf(cl);
        return 0;
 }
 static int fn_client_notice(lua_State *L)
 {
-       Client *cl = *(Client **)luaL_checkudata(L, 1, "client");
+       Client *cl = *(Client **)luaL_checkudata(L, 1, "Client");
        const char *msg = luaL_checkstring(L, 2);
        msg_notice_server(luaL_optstring(L, 3, hostname), cl->nick, msg), sendbuf(cl);
        return 0;
 }
 static int fn_channel_join(lua_State *L)
 {
-       Client *cl = *(Client **)luaL_checkudata(L, 1, "client");
+       Client *cl = *(Client **)luaL_checkudata(L, 1, "Client");
        const char *channel = 0;
        Channel *chan = 0;
        if (lua_isuserdata(L, 2)) {
-               chan = *(Channel **)luaL_checkudata(L, 2, "channel");
+               chan = *(Channel **)luaL_checkudata(L, 2, "Channel");
                channel = chan->key;
        } else {
                channel = luaL_checkstring(L, 2);
@@ -251,12 +283,12 @@ static int fn_channel_join(lua_State *L)
 }
 static int fn_channel_part(lua_State *L)
 {
-       Client *cl = *(Client **)luaL_checkudata(L, 1, "client");
-       Channel *chan = *(Channel **)luaL_checkudata(L, 2, "channel");
+       Client *cl = *(Client **)luaL_checkudata(L, 1, "Client");
+       Channel *chan = *(Channel **)luaL_checkudata(L, 2, "Channel");
        const char *reason = luaL_optstring(L, 3, 0);
        Client *kicker = 0;
        if (lua_isuserdata(L, 4))
-               kicker = *(Client **)luaL_checkudata(L, 4, "client");
+               kicker = *(Client **)luaL_checkudata(L, 4, "Client");
        channel_part(cl, chan, reason, kicker);
        return 0;
 }
@@ -277,7 +309,7 @@ static int fn_channel_new(lua_State *L)
 }
 static int fn_channel_mode(lua_State *L)
 {
-       Channel *chan = *(Channel **)luaL_checkudata(L, 1, "channel");
+       Channel *chan = *(Channel **)luaL_checkudata(L, 1, "Channel");
        size_t as, bs;
        const char *a = luaL_checklstring(L, 2, &as);
        const char *b = luaL_optlstring(L, 3, 0, &bs);
@@ -292,7 +324,7 @@ static int fn_channel_mode(lua_State *L)
 }
 static int fn_channel_topic(lua_State *L)
 {
-       Channel *chan = *(Channel **)luaL_checkudata(L, 1, "channel");
+       Channel *chan = *(Channel **)luaL_checkudata(L, 1, "Channel");
        const char *topic = luaL_checkstring(L, 2);
        const char *who = luaL_checkstring(L, 3);
        time_t when = luaL_optinteger(L, 4, 0);
@@ -301,15 +333,15 @@ static int fn_channel_topic(lua_State *L)
 }
 static int fn_client_chghost(lua_State *L)
 {
-       Client *cl = *(Client **)luaL_checkudata(L, 1, "client");
+       Client *cl = *(Client **)luaL_checkudata(L, 1, "Client");
        const char *newusername = luaL_optstring(L, 2, 0);
        const char *newhostname = luaL_optstring(L, 3, 0);
        client_chghost(cl, newusername, newhostname);
        return 0;
 }
 static int fn_rpl_whoisspecial(lua_State *L) {
-       Client *cl1 = *(Client **)luaL_checkudata(L, 1, "client");
-       Client *cl2 = *(Client **)luaL_checkudata(L, 2, "client");
+       Client *cl1 = *(Client **)luaL_checkudata(L, 1, "Client");
+       Client *cl2 = *(Client **)luaL_checkudata(L, 2, "Client");
        const char *text = luaL_checkstring(L, 3);
        rpl_whoisspecial(cl1, cl2, "", text), sendbuf(cl1);
        return 0;
@@ -373,19 +405,10 @@ void admin_init(void)
        lua_State *L = luaL_newstate();
        GL = L;
        luaL_openlibs(L);
-       if (luaL_newmetatable(L, "map")) {
-               luaL_Reg funcs[] = {
-                       {"__index", map_index},
-                       {"__newindex", map_newindex},
-                       {"__pairs", map_pairs},
-                       {0, 0},
-               };
-               luaL_setfuncs(L, funcs, 0);
-       }
-       lua_pop(L, 1);
-       luaL_newmetatable(L, "map_iter");
-       lua_pop(L, 1);
-       if (luaL_newmetatable(L, "client")) {
+       SetRef<std::string>::regmt(L);
+       SetRef<Client *>::regmt(L);
+       SetRef<Channel *>::regmt(L);
+       if (luaL_newmetatable(L, "Client")) {
                luaL_Reg funcs[] = {
                        {"__index", client_index},
                        {0, 0},
@@ -393,7 +416,7 @@ void admin_init(void)
                luaL_setfuncs(L, funcs, 0);
        }
        lua_pop(L, 1);
-       if (luaL_newmetatable(L, "channel")) {
+       if (luaL_newmetatable(L, "Channel")) {
                luaL_Reg funcs[] = {
                        {"__index", channel_index},
                        {0, 0},
@@ -475,7 +498,7 @@ bool admin_command(Client *client, char *source, char *command, int argc, char *
                return false;
        }
        *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = client;
-       luaL_setmetatable(L, "client");
+       luaL_setmetatable(L, "Client");
        lua_pushstring(L, source);
        lua_pushstring(L, command);
        lua_createtable(L, argc, 1);
@@ -507,19 +530,19 @@ int admin_hook(const char *hook, Client *cl1, Client *cl2, Channel *chan, char *
        lua_pushstring(L, hook);
        if (cl1) {
                *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = cl1;
-               luaL_setmetatable(L, "client");
+               luaL_setmetatable(L, "Client");
        } else {
                lua_pushnil(L);
        }
        if (cl2) {
                *(Client **)lua_newuserdatauv(L, sizeof(Client *), 0) = cl2;
-               luaL_setmetatable(L, "client");
+               luaL_setmetatable(L, "Client");
        } else {
                lua_pushnil(L);
        }
        if (chan) {
                *(Channel **)lua_newuserdatauv(L, sizeof(Client *), 0) = chan;
-               luaL_setmetatable(L, "channel");
+               luaL_setmetatable(L, "Channel");
        } else {
                lua_pushnil(L);
        }
index a0ca166..2f2ecd3 100644 (file)
@@ -10,11 +10,6 @@ void job_fini(void);
 void job_process(void);
 void job_add(Client *client, jobproc_t&& job, contproc_t&& then);
 void job_kill(Client *client);
-template<size_t N>
-struct Literal {
-       char value[N];
-       consteval Literal(const char (&s)[N]) { std::copy_n(s, N, value); }
-};
 template<Literal table>
 void job_password_check(Client *client, const char *key, const char *pw, contproc_t&& then);
 extern template void job_password_check<"ops">(Client *client, const char *key, const char *pw, contproc_t&& then);
index 4625b8e..e437456 100644 (file)
@@ -190,88 +190,21 @@ struct MapValueLess {
                return lhs < to_key(rhs);
        }
 };
-struct map_base {
-       map_base& operator=(map_base&&) = delete;
-       virtual ~map_base() = default;
-       virtual void *_get(const char *key) =0;
-       virtual void _set(const void *value) =0;
-       virtual void _remove(const char *key) =0;
-       virtual std::pair<std::string_view, void *> _next(size_t& index) =0;
-       struct iter {
-               map_base *root;
-               size_t index;
-               auto next() {
-                       return root->_next(index);
-               }
-       };
-};
 
 template<typename T>
-struct map_base2 : map_base {
+struct map_base2 {
+       map_base2& operator=(map_base2&&) = delete;
        FlatSet<T, MapValueLess<T>> m;
 
-       ~map_base2() {
-               clear();
-       }
-private:
-       static constexpr T value_to_void(T v) noexcept requires (std::same_as<T, Client*> || std::same_as<T, Channel*>) {
-               return v;
-       }
-       static constexpr char *value_to_void(const T& v) noexcept requires std::same_as<T, std::string> {
-               return const_cast<char *>(v.data());
-       }
-       using value_to_void_type = decltype(value_to_void(std::declval<T>()));
-
-       static constexpr T void_to_value(void *p) noexcept requires (std::same_as<T, Client*> || std::same_as<T, Channel*>) {
-               return static_cast<T>(p);
-       }
-       static constexpr std::string_view void_to_value(void *p) noexcept requires std::same_as<T, std::string> {
-               return static_cast<char *>(p);
-       }
-public:
-       void *_get(const char *key) final {
-               auto it = m.find(key);
-               return it != m.end() ? value_to_void(*it) : nullptr;
-       }
-       void _set(const void *value) final {
-               if (value) {
-                       auto mv = void_to_value(const_cast<void *>(value));
-                       m.insert(mv);
-               }
-       }
-       void _remove(const char *key) final {
-               remove(key);
-       }
-       std::pair<std::string_view, void *> _next(size_t& index) final {
-               auto it = m.begin() + index;
-               if (it < m.end()) {
-                       index++;
-                       return std::make_pair(to_key(*it), static_cast<void *>(value_to_void(*it)));
-               }
-               return std::make_pair(std::string_view(), nullptr);
-       }
        void clear() {
                m.clear();
        }
 
        using value_type = T;
-       struct iterator {
-               using iterator_category = std::bidirectional_iterator_tag;
-               using value_type = T;
-               using difference_type = ptrdiff_t;
-               using pointer = const T*;
-               using reference = const T&;
-               iterator &operator++() { ++ptr; return *this; }
-               iterator operator++(int) { auto it = *this; ++ptr; return it; }
-               iterator &operator--() { --ptr; return *this; }
-               iterator operator--(int) { auto it = *this; --ptr; return it; }
-               bool operator==(const iterator& other) const = default;
-               reference operator*() const { return *ptr; }
-               decltype(m)::iterator ptr;
-       };
+       using iterator = decltype(m)::iterator;
        static_assert(std::bidirectional_iterator<iterator>);
-       auto begin() { return iterator{m.begin()}; }
-       auto end() { return iterator{m.end()}; }
+       auto begin() { return m.begin(); }
+       auto end() { return m.end(); }
        struct reverse {
                map_base2 &obj;
                auto begin() { return std::make_reverse_iterator(obj.end()); }
index 668c868..d0fee34 100644 (file)
@@ -30,4 +30,10 @@ extern const char *datadir;
 const char *getdatafile(const char *fn);
 extern const char *luadir;
 const char *getluafile(const char *fn);
+
+template<size_t N>
+struct Literal {
+       char value[N];
+       consteval Literal(const char (&s)[N]) { std::copy_n(s, N, value); }
+};
 #endif