aboutsummaryrefslogtreecommitdiff
path: root/src/LuaEngine
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-09-16 16:32:06 -0400
committerfrosty <gabriel@bwaaa.monster>2026-09-16 16:32:06 -0400
commit4c8d764d171ec77ff626cec273c44ec7e7a995fc (patch)
treedc36193e611ece53c8f7e74d1dea71f8d9e7004e /src/LuaEngine
downloadyapssg-master.tar.gz
init: initHEADmaster
Diffstat (limited to 'src/LuaEngine')
-rw-r--r--src/LuaEngine/LuaEngine.c273
-rw-r--r--src/LuaEngine/LuaEngine.h8
2 files changed, 281 insertions, 0 deletions
diff --git a/src/LuaEngine/LuaEngine.c b/src/LuaEngine/LuaEngine.c
new file mode 100644
index 0000000..319f408
--- /dev/null
+++ b/src/LuaEngine/LuaEngine.c
@@ -0,0 +1,273 @@
+#include "LuaEngine/LuaEngine.h"
+
+#include "DirectoryWalker/DirectoryWalker.h"
+#include "Dom/Dom.h"
+#include "Logger/Logger.h"
+#include "Logger/LuaLogger.h"
+#include "Path/Path.h"
+#include "Version/Version.h"
+
+#include <lauxlib.h>
+#include <lua.h>
+#include <lualib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+typedef enum { SCRIPT_INLINE, SCRIPT_EXTERNAL } ScriptType;
+
+typedef struct {
+ char *source;
+ ScriptType type;
+ unsigned long line;
+} LuaScript;
+
+typedef struct {
+ LuaScript *items;
+ size_t length;
+ size_t capacity;
+} ScriptList;
+
+static int is_lua_element(const xmlNode *node) {
+ return node->type == XML_ELEMENT_NODE &&
+ strcasecmp((const char *)node->name, "lua") == 0;
+}
+
+static char *duplicate_string(const char *value) {
+ size_t length = strlen(value);
+ char *copy = malloc(length + 1);
+
+ if (copy != NULL) {
+ memcpy(copy, value, length + 1);
+ }
+ return copy;
+}
+
+static int grow_script_list(ScriptList *scripts) {
+ size_t larger_capacity;
+ LuaScript *larger_items;
+
+ if (scripts->capacity > SIZE_MAX / (2 * sizeof(*scripts->items))) {
+ return -1;
+ }
+
+ larger_capacity = scripts->capacity == 0 ? 4 : scripts->capacity * 2;
+ larger_items =
+ realloc(scripts->items, larger_capacity * sizeof(*scripts->items));
+ if (larger_items == NULL) {
+ return -1;
+ }
+
+ scripts->items = larger_items;
+ scripts->capacity = larger_capacity;
+ return 0;
+}
+
+static int append_script(ScriptList *scripts, const char *source,
+ ScriptType type, unsigned long line) {
+ LuaScript *script;
+
+ if (scripts->length == scripts->capacity && grow_script_list(scripts) != 0) {
+ return -1;
+ }
+
+ script = &scripts->items[scripts->length];
+ script->source = duplicate_string(source);
+ if (script->source == NULL) {
+ return -1;
+ }
+
+ script->type = type;
+ script->line = line;
+ ++scripts->length;
+ return 0;
+}
+
+static int collect_script(xmlNode *node, ScriptList *scripts) {
+ xmlChar *source_path = xmlGetProp(node, BAD_CAST "src");
+ xmlChar *code;
+ int result;
+
+ if (source_path != NULL) {
+ result = append_script(scripts, (const char *)source_path, SCRIPT_EXTERNAL,
+ node->line);
+ xmlFree(source_path);
+ return result;
+ }
+
+ code = xmlNodeGetContent(node);
+ result = append_script(scripts, code == NULL ? "" : (const char *)code,
+ SCRIPT_INLINE, node->line);
+ xmlFree(code);
+ return result;
+}
+
+static int collect_scripts(xmlNode *node, ScriptList *scripts) {
+ for (; node != NULL; node = node->next) {
+ if (is_lua_element(node)) {
+ if (collect_script(node, scripts) != 0) {
+ return -1;
+ }
+ } else if (collect_scripts(node->children, scripts) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void free_scripts(ScriptList *scripts) {
+ size_t index;
+
+ for (index = 0; index < scripts->length; ++index) {
+ free(scripts->items[index].source);
+ }
+ free(scripts->items);
+}
+
+static char *make_chunk_name(const char *input_path, unsigned long line) {
+ size_t input_length = strlen(input_path);
+ size_t capacity;
+ char *chunk_name;
+
+ if (input_length > SIZE_MAX - 32) {
+ return NULL;
+ }
+ capacity = input_length + 32;
+ chunk_name = malloc(capacity);
+
+ if (chunk_name != NULL) {
+ snprintf(chunk_name, capacity, "@%s:%lu", input_path, line);
+ }
+ return chunk_name;
+}
+
+static int run_script(lua_State *lua, const LuaScript *script,
+ const char *input_path) {
+ char *source_path = NULL;
+ char *chunk_name = NULL;
+ const char *display_path;
+ int load_result;
+ int result;
+
+ if (script->type == SCRIPT_EXTERNAL) {
+ source_path = path_resolve_from_file(input_path, script->source);
+ if (source_path == NULL) {
+ logger_error("unable to allocate Lua source path");
+ return -1;
+ }
+ display_path = source_path;
+ load_result = luaL_loadfile(lua, source_path);
+ } else {
+ chunk_name = make_chunk_name(input_path, script->line);
+ if (chunk_name == NULL) {
+ logger_error("unable to allocate Lua chunk name");
+ return -1;
+ }
+ display_path = input_path;
+ load_result = luaL_loadbuffer(lua, script->source, strlen(script->source),
+ chunk_name);
+ }
+
+ result = load_result == 0 ? lua_pcall(lua, 0, 0, 0) : load_result;
+ if (result != 0) {
+ const char *message = lua_tostring(lua, -1);
+
+ logger_error("Lua error in %s: %s", display_path,
+ message == NULL ? "unknown error" : message);
+ lua_pop(lua, 1);
+ }
+
+ free(chunk_name);
+ free(source_path);
+ return result == 0 ? 0 : -1;
+}
+
+static int run_scripts(lua_State *lua, const ScriptList *scripts,
+ const char *input_path) {
+ size_t index;
+
+ for (index = 0; index < scripts->length; ++index) {
+ if (run_script(lua, &scripts->items[index], input_path) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void register_yapssg(lua_State *lua) {
+ lua_newtable(lua);
+ lua_pushstring(lua, yapssg_version);
+ lua_setfield(lua, -2, "version");
+ lua_setglobal(lua, "yapssg");
+}
+
+static int replace_document_root(lua_State *lua, htmlDocPtr document,
+ const char *input_path) {
+ xmlNodePtr replacement;
+ xmlNodePtr old_root;
+
+ lua_getglobal(lua, "page");
+ if (!lua_istable(lua, -1)) {
+ logger_error("Lua global 'page' must remain a table: %s", input_path);
+ lua_pop(lua, 1);
+ return -1;
+ }
+
+ replacement = dom_page_from_lua(lua, lua_gettop(lua));
+ lua_pop(lua, 1);
+ if (replacement == NULL || replacement->type != XML_ELEMENT_NODE) {
+ logger_error("Lua produced an invalid page root: %s", input_path);
+ xmlFreeNode(replacement);
+ return -1;
+ }
+
+ old_root = xmlDocSetRootElement(document, replacement);
+ xmlFreeNode(old_root);
+ return 0;
+}
+
+int process_html_document(htmlDocPtr document, const char *input_path) {
+ xmlNodePtr root = xmlDocGetRootElement(document);
+ ScriptList scripts = {0};
+ lua_State *lua;
+ int result;
+
+ if (root == NULL) {
+ logger_error("HTML document has no root element: %s", input_path);
+ return -1;
+ }
+ if (collect_scripts(root, &scripts) != 0) {
+ logger_error("unable to collect Lua scripts: %s", input_path);
+ free_scripts(&scripts);
+ return -1;
+ }
+ if (scripts.length == 0) {
+ free_scripts(&scripts);
+ return 0;
+ }
+
+ lua = luaL_newstate();
+ if (lua == NULL) {
+ logger_error("unable to create Lua state");
+ free_scripts(&scripts);
+ return -1;
+ }
+
+ luaL_openlibs(lua);
+ register_yapssg(lua);
+ lua_logger_register(lua);
+ directory_walker_register(lua, input_path);
+ dom_push_page(lua, root);
+ lua_setglobal(lua, "page");
+
+ result = run_scripts(lua, &scripts, input_path);
+ if (result == 0) {
+ result = replace_document_root(lua, document, input_path);
+ }
+
+ lua_close(lua);
+ free_scripts(&scripts);
+ return result;
+}
diff --git a/src/LuaEngine/LuaEngine.h b/src/LuaEngine/LuaEngine.h
new file mode 100644
index 0000000..bd035b9
--- /dev/null
+++ b/src/LuaEngine/LuaEngine.h
@@ -0,0 +1,8 @@
+#ifndef YAPSSG_LUA_ENGINE_H
+#define YAPSSG_LUA_ENGINE_H
+
+#include <libxml/HTMLparser.h>
+
+int process_html_document(htmlDocPtr document, const char *input_path);
+
+#endif