From fbf70c43b30073039c16d616d9ff259124982f19 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sun, 21 Sep 2025 20:13:43 +0100 Subject: [PATCH] [Add] Embedded Lua scripting language. Over the past couple of commits, the build process now automates the dependenices you'd normally compile from source. This commit is focused on laying the foundation for scriptable in-game commands using Lua. - sol2 and lua5.4 are the new project dependencies. - Created a new 'LuaProcessor' class to manage a Lua state and eecute scripts. - The 'CommandProcessor' now contains a 'LuaProcessor' instance. - Replaced the hardcoded c++ 'ls' command with a system that executes a '/bin/ls.lua' script from the Virtual File System. - Implemented C++ -> Lua bindings for the 'vfs_node' struct, allowing Lua scripts to inspect the VFS and perform actions (i.e, list files). --- common/CMakeLists.txt | 3 ++- common/src/command_processor.cpp | 40 ++++++++++++++++++++++++-------- common/src/command_processor.h | 3 +++ common/src/lua_processor.cpp | 29 +++++++++++++++++++++++ common/src/lua_processor.h | 18 ++++++++++++++ common/src/vfs_manager.cpp | 20 +++++++++++++--- 6 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 common/src/lua_processor.cpp create mode 100644 common/src/lua_processor.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index bd318ee..83e70ac 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -8,5 +8,6 @@ add_library(bettola target_link_libraries(bettola PUBLIC ${LUA_LIBRARIES} sol2) -target_include_directories(bettola PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) +target_include_directories(bettola PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src + ${LUA_INCLUDE_DIR}) diff --git a/common/src/command_processor.cpp b/common/src/command_processor.cpp index 609885b..f1695d1 100644 --- a/common/src/command_processor.cpp +++ b/common/src/command_processor.cpp @@ -1,8 +1,14 @@ #include "command_processor.h" #include "vfs.h" +#include "lua_processor.h" CommandProcessor::CommandProcessor(vfs_node* starting_dir) { _current_dir = starting_dir; + _lua = new LuaProcessor(); +} + +CommandProcessor::~CommandProcessor(void) { + delete _lua; } vfs_node* CommandProcessor::get_current_dir(void) { @@ -10,6 +16,11 @@ vfs_node* CommandProcessor::get_current_dir(void) { } std::string CommandProcessor::process_command(const std::string& command) { + /* Trim trailing whitespace. */ + std::string cmd = command; + size_t end = cmd.find_last_not_of(" \t\n\r"); + cmd = (end == std::string::npos) ? "" : cmd.substr(0, end+1); + if(command.rfind("cd ", 0) == 0) { std::string target_dir_name = command.substr(3); if(target_dir_name == "..") { @@ -27,18 +38,27 @@ std::string CommandProcessor::process_command(const std::string& command) { return "cd: no such file or directory\n"; } return get_full_path(_current_dir); - } else if(command == "ls") { - std::string response = ""; - if(_current_dir && _current_dir->type == DIR_NODE) { - for(auto const& [name, node] : _current_dir->children) { - response += name; - if(node->type == DIR_NODE) { - response += "/"; - } - response += " "; + } else if(cmd == "ls") { + /* Find the root of the VFS to look for the /bin directory. */ + vfs_node* root = _current_dir; + while(root->parent != nullptr) { + root = root->parent; + } + + fprintf(stderr, "DEBUG: VFS root found, name: '%s'\n", root->name.c_str()); + + /* Find and execute the ls.lua script. */ + if(root->children.count("bin")) { + fprintf(stderr, "DEBUG: Found '/bin' directory.\n"); + vfs_node* bin_dir = root->children["bin"]; + if(bin_dir->children.count("ls.lua")) { + fprintf(stderr, "DEBUG: Found '/bin/ls.lua'. Executing.\n"); + vfs_node* ls_script_node = bin_dir->children["ls.lua"]; + return _lua->execute(ls_script_node->content, _current_dir); } } - return response; + fprintf(stderr, "DEBUG: 'ls' command failed to find '/bin/ls.lua'.\n"); + return "ls: command not found\n"; } return "Unknown command: " + command + "\n"; diff --git a/common/src/command_processor.h b/common/src/command_processor.h index 08609f7..0bf7246 100644 --- a/common/src/command_processor.h +++ b/common/src/command_processor.h @@ -3,14 +3,17 @@ #include #include "vfs.h" +#include class CommandProcessor { public: CommandProcessor(vfs_node* starting_dir); + ~CommandProcessor(void); std::string process_command(const std::string& command); vfs_node* get_current_dir(void); private: vfs_node* _current_dir; + LuaProcessor* _lua; }; diff --git a/common/src/lua_processor.cpp b/common/src/lua_processor.cpp new file mode 100644 index 0000000..5090f19 --- /dev/null +++ b/common/src/lua_processor.cpp @@ -0,0 +1,29 @@ +#include "lua_processor.h" +#include "vfs.h" + +LuaProcessor::LuaProcessor(void) { + _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io); + + /* Expose vfs_node struct members to Lua. */ + _lua.new_usertype("vfs_node", + "name", &vfs_node::name, + "type", &vfs_node::type, + "children", &vfs_node::children); +} + +LuaProcessor::~LuaProcessor(void) {} + +std::string LuaProcessor::execute(const std::string& script, vfs_node* current_dir) { + try { + /* Pass the pointer for the current directory into the Lua env. */ + _lua["current_dir"] = current_dir; + sol::object result = _lua.script(script); + if(result.is()) { + return result.as(); + } + } catch(const sol::error& e) { + return e.what(); + } + /* Shouldn't reach this, just shutting the compiler up. */ + return "[Unknown error in LuaProcessor::execute]"; +} diff --git a/common/src/lua_processor.h b/common/src/lua_processor.h new file mode 100644 index 0000000..78b89ba --- /dev/null +++ b/common/src/lua_processor.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +/* Don't want the full header. */ +struct vfs_node; + +class LuaProcessor { +public: + LuaProcessor(void); + ~LuaProcessor(void); + + /* Executes a string of lua code and returns result as a string. */ + std::string execute(const std::string& script, vfs_node* current_dir); +private: + sol::state _lua; +}; diff --git a/common/src/vfs_manager.cpp b/common/src/vfs_manager.cpp index a233c14..e379b5b 100644 --- a/common/src/vfs_manager.cpp +++ b/common/src/vfs_manager.cpp @@ -29,9 +29,23 @@ vfs_node* vfs_manager::create_root_system(const std::string& system_type) { readme->content = "Welcome to your new virtual machine."; user->children["readme.txt"] = readme; - vfs_node* ls_exe = new_node("ls", FILE_NODE, bin); - ls_exe->content = "[executable]"; - bin->children["ls"] = ls_exe; + vfs_node* ls_script = new_node("ls.lua", FILE_NODE, bin); + ls_script->content = R"lua(-- /bin/ls.lua - Lists files in a directory. + local dir = current_dir -- Get directory object from C++. + local output = "" + + -- Iterate over the 'children' map exposed from c++. + for name, node in pairs(dir.children) do + output = output .. name + if node.type == 1 then + output = output .. "/" + end + output = output .. " " + end + return output + )lua"; + + bin->children["ls.lua"] = ls_script; if(system_type == "npc") { vfs_node* npc_file = new_node("npc_system.txt", FILE_NODE, root);