[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).
This commit is contained in:
Ritchie Cunningham 2025-09-21 20:13:43 +01:00
parent 76e61336a1
commit fbf70c43b3
6 changed files with 99 additions and 14 deletions

View File

@ -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})

View File

@ -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";

View File

@ -3,14 +3,17 @@
#include <string>
#include "vfs.h"
#include <lua_processor.h>
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;
};

View File

@ -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>("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<std::string>()) {
return result.as<std::string>();
}
} catch(const sol::error& e) {
return e.what();
}
/* Shouldn't reach this, just shutting the compiler up. */
return "[Unknown error in LuaProcessor::execute]";
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <sol/sol.hpp>
#include <string>
/* 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;
};

View File

@ -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);