From a610e6732506bbcf949f789bbd15d4d4b74f9b95 Mon Sep 17 00:00:00 2001 From: Riley-King <46487753+RRKS101@users.noreply.github.com> Date: Thu, 27 Feb 2025 00:01:01 +1100 Subject: [PATCH] Initial release commit --- .gitignore | 12 + .gitmodules | 3 + include/includes.h | 12 + include/lua_datetime.h | 9 + include/lua_dependency_tree.h | 101 +++++ include/lua_filesystem.h | 59 +++ include/lua_git.h | 0 include/lua_parallel.h | 48 +++ include/lua_platform.h | 34 ++ include/lua_stack.h | 43 +++ include/platform_agnostic.h | 8 + include/simple_string.h | 47 +++ makefile | 59 +++ src/dynarray.hpp | 365 +++++++++++++++++++ src/lua_dependency_tree.cpp | 667 ++++++++++++++++++++++++++++++++++ src/lua_filesystem.cpp | 331 +++++++++++++++++ src/lua_platform.cpp | 191 ++++++++++ src/main.cpp | 116 ++++++ src/simple_string.cpp | 216 +++++++++++ thirdparty/luajit | 1 + 20 files changed, 2322 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 include/includes.h create mode 100644 include/lua_datetime.h create mode 100644 include/lua_dependency_tree.h create mode 100644 include/lua_filesystem.h create mode 100644 include/lua_git.h create mode 100644 include/lua_parallel.h create mode 100644 include/lua_platform.h create mode 100644 include/lua_stack.h create mode 100644 include/platform_agnostic.h create mode 100644 include/simple_string.h create mode 100644 makefile create mode 100644 src/dynarray.hpp create mode 100644 src/lua_dependency_tree.cpp create mode 100644 src/lua_filesystem.cpp create mode 100644 src/lua_platform.cpp create mode 100644 src/main.cpp create mode 100644 src/simple_string.cpp create mode 160000 thirdparty/luajit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..363c86a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/.vs +/build/lbs.exe +/build/lbs.pdb +/obj/src/lua_dependency_tree.cpp.o +/obj/src/lua_filesystem.cpp.o +/obj/src/lua_platform.cpp.o +/obj/src/main.cpp.o +/obj/src/simple_string.cpp.o diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ef42472 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/luajit"] + path = thirdparty/luajit + url = https://luajit.org/git/luajit.git diff --git a/include/includes.h b/include/includes.h new file mode 100644 index 0000000..0c16627 --- /dev/null +++ b/include/includes.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +// Any function marked with this means that the exposed lua API is not sandboxed +#define LUA_UNSAFE diff --git a/include/lua_datetime.h b/include/lua_datetime.h new file mode 100644 index 0000000..db354c4 --- /dev/null +++ b/include/lua_datetime.h @@ -0,0 +1,9 @@ +#pragma once +#include "includes.h" + +int luaopen_datetime(lua_State* L); + +int lua_datetime_unix_time(lua_State* L); + +int lua_datetime_tostring(lua_State* L); +int lua_datetime_tounix(lua_State* L); \ No newline at end of file diff --git a/include/lua_dependency_tree.h b/include/lua_dependency_tree.h new file mode 100644 index 0000000..69705cc --- /dev/null +++ b/include/lua_dependency_tree.h @@ -0,0 +1,101 @@ +#pragma once +#include "includes.h" +#include "dynarray.hpp" +#include +#include +#include + +struct dependency_tree +{ + typedef uint32_t ref_t; + typedef ref_t state_t; + typedef ref_t state_cmp_t; + + enum STATE : state_t + { + FULFILLED = 0, + FAILED = 1, + WAITING = 2, + WORKING = 3 + }; + enum STATE_CMP : state_cmp_t + { + CMP_FULFILED = 1, + CMP_FAILED = 2, + CMP_WAITING = 4, + CMP_WORKING = 8 + }; + + struct node + { + node(const node&) = delete; + node& operator =(const node&) = delete; + node(node&& other) noexcept; + node& operator =(node&& other) noexcept; + node(); + // What depends on this node + std::set m_dependents = {}; + // Name of the node + const char* m_name = nullptr; + // How many other nodes does this node depend on + ref_t m_num_pending_dependencies = 0; + // A user-controlled u30 + ref_t m_userdata : (sizeof(ref_t)-2) = -1; + // State of the node + state_t m_state : 2; + + ~node(); + }; + + ~dependency_tree(); + + // A list that maps a nodes name to its index + std::map m_node_name_mapping; + // A list of all nodes in the dependency tree + dynarray m_nodes; + + node& operator[](const ref_t idx) noexcept; + const node& operator[](const ref_t idx) const noexcept; + + dynarray get_leaf_nodes(const bool ignore_fulfilled=false) const; + dynarray get_leaf_node_names(const bool ignore_fulfilled=false) const; + dynarray filter_nodes(const state_cmp_t state_cmp, const ref_t num_dependencies_gt, const bool invert_num_deps) const; + void set_node_state(const ref_t node, const state_t new_state); + void set_node_state(const char* node, const state_t new_state); + + ref_t add_node(const char* node_name, const dynarray& dependents); + ref_t add_node(const char* node_name, const dynarray& dependents); + ref_t add_node(const char* node_name, const std::initializer_list& dependents); + ref_t add_node(const char* node_name, const std::initializer_list& dependents); + ref_t add_node(const char* node_name); + + void add_node_dependency(const ref_t node, const ref_t dependency); + void add_node_dependency(const ref_t node, const char* dependency); + void add_node_dependency(const char* node, const ref_t dependency); + void add_node_dependency(const char* node, const char* dependency); + + void add_node_dependencies(const ref_t node, const std::initializer_list& dependencies); + void add_node_dependencies(const ref_t node, const dynarray& dependencies); + void add_node_dependencies(const ref_t node, const std::initializer_list& dependencies); + void add_node_dependencies(const ref_t node, const dynarray& dependencies); +}; + + +int luaopen_dependency_tree(lua_State* L); + +int lua_dependency_create(lua_State* L); +int lua_dependency_destroy(lua_State* L); +int lua_dependency_add_node(lua_State* L); +int lua_dependency_add_dependency(lua_State* L); +int lua_dependency_remove_dependency(lua_State* L); +int lua_dependency_add_dependencies(lua_State* L); +int lua_dependency_remove_dependencies(lua_State* L); +int lua_dependency_get_leafs(lua_State* L); +int lua_dependency_match_nodes(lua_State* L); +int lua_dependency_get_node_state(lua_State* L); +int lua_dependency_set_node_state(lua_State* L); +int lua_dependency_get_node_userval(lua_State* L); +int lua_dependency_set_node_userval(lua_State* L); +int lua_dependency_get_node_name(lua_State* L); +int lua_dependency_get_node(lua_State* L); +int lua_dependency_index(lua_State* L); \ No newline at end of file diff --git a/include/lua_filesystem.h b/include/lua_filesystem.h new file mode 100644 index 0000000..6b9f0fe --- /dev/null +++ b/include/lua_filesystem.h @@ -0,0 +1,59 @@ +#pragma once +#include "includes.h" + +// Provides variables; +// fs.work_dir: string +int luaopen_filesystem(lua_State* L); + +// x = list_dir(path) +int lua_fs_list_dir(lua_State* L); + +// x = list_all_dirs(path) +int lua_fs_list_alldirs(lua_State* L); + +int lua_fs_foreach_dir(lua_State* L); +int lua_fs_forall_dir(lua_State* L); + +int lua_fs_foreach_dir_next(lua_State* L); +int lua_fs_forall_dir_next(lua_State* L); + +int lua_fs_foreach_dir_dtor(lua_State* L); +int lua_fs_forall_dir_dtor(lua_State* L); + + +// number | nil function fs.last_modified(path: string) +int lua_fs_last_modified(lua_State* L); +// boolean function fs.exists(path: string) +int lua_fs_exists(lua_State* L); +// boolean function fs.is_dir(path: string) +int lua_fs_is_dir(lua_State* L); +// boolean function fs.is_file(path: string) +int lua_fs_is_file(lua_State* L); +// boolean function create_dir(path: string) +int lua_fs_create_dir(lua_State* L); +// userdata | nil function fs.open_file(path: string) +int lua_fs_open_file(lua_State* L); +// nil function fs.close_file(userdata) +// file dtor +int lua_fs_close_file(lua_State* L); +// string | nil function fs.read_file(userdata, num_bytes: number) +int lua_fs_read_file(lua_State* L); +// nil function fs.seek(userdata, num_bytes: number, [opt] boolean: seek_backward=false) +int lua_fs_seek_file(lua_State* L); +// nil function fs.getpos(userdata) +int lua_fs_getpos_file(lua_State* L); +// nil function fs.write(userdata, string) +int lua_fs_write_file(lua_State* L); +// nil function fs.append(userdata, string) +int lua_fs_append_file(lua_State* L); + +// string function fs.relative(string base, string path) +int lua_fs_relative(lua_State* L); +// string function fs.absolute(string path) +int lua_fs_absolute(lua_State* L); +// string function fs.file_name(string path) +int lua_fs_file_name(lua_State* L); +// string function fs.file_extension(string path) +int lua_fs_file_extension(lua_State* L); +// string function fs.file_directory(string path) +int lua_fs_file_directory(lua_State* L); \ No newline at end of file diff --git a/include/lua_git.h b/include/lua_git.h new file mode 100644 index 0000000..e69de29 diff --git a/include/lua_parallel.h b/include/lua_parallel.h new file mode 100644 index 0000000..3be2f4f --- /dev/null +++ b/include/lua_parallel.h @@ -0,0 +1,48 @@ +// A lua module that provides multithreadding utilities to lua +#pragma once +#include "lua_stack.h" + +// All functions are sandboxed +int luaopen_parallel_safe(lua_State* L); +// Some functions are not sandboxed +int luaopen_parallel_unsafe(lua_State* L); + +int lua_parallel_create_mutex(lua_State* L); +int lua_parallel_destroy_mutex(lua_State* L); +int lua_parallel_lock_mutex(lua_State* L); +int lua_parallel_unlock_mutex(lua_State* L); + +int lua_parallel_create_promise(lua_State* L); +int lua_parallel_destroy_promise(lua_State* L); +int lua_parallel_await_promise(lua_State* L); +int lua_parallel_fullfilled_promise(lua_State* L); +int lua_parallel_fullfilled_all_promises(lua_State* L); + +struct lua_parallel_thread +{ + // A list of all messages recieved from other threads + lua_stack stack; + // Coroutine lua state pointer + lua_State* L; + // underlying thread object + std::jthread thread; +}; + +// userdata function parallel.create_thread(function fn, ...) +int lua_parallel_create_thread(lua_State* L); +// N/A +int lua_parallel_destroy_thread(lua_State* L); +// nil function parallel.join(userdata thread) +int lua_parallel_join_thread(lua_State* L); +// nil function parallel.request_stop(userdata thread) +int lua_parallel_request_stop(lua_State* L); +// boolean function parallel.stop_requested() +int lua_parallel_stop_requested(lua_State* L); +// nil function parallel.sendmsg(userdata thread, ...) +int lua_parallel_sendmsg_thread(lua_State* L); +// table function parallel.recvmsg(userdata[] | nil filter) +int lua_parallel_recvmsg_thread(lua_State* L); +// number function parallel.recvsize(userdata[] | nil filter) +int lua_parallel_recvsize_thread(lua_State* L); + + diff --git a/include/lua_platform.h b/include/lua_platform.h new file mode 100644 index 0000000..65b4d7f --- /dev/null +++ b/include/lua_platform.h @@ -0,0 +1,34 @@ +#pragma once +#include "lua_parallel.h" +// Platform-related code + +// Provided variables; +// platform.os: string +// platform.arch: string +// platform.call_convention: string +// platform.isa_exts: table +int luaopen_platform(lua_State* L); + +// Returns the exit code of the command as well as a string of its stdout +// int, string function platform.exec(cmd: string, args: array) +int lua_platform_exec(lua_State* L); + +// Executes a command asynchronosly, returning a promise to the functions return values +// promise, promise function platform.exec_async(cmd: string, args: array) +int lua_platform_exec_async(lua_State* L); + +// Executes a command asynchronosly, calling a callback on its completion with its error code and +// overall stdout and another callback on each token read from its stdout +// function platform.exec_async_cb(cmd: string, +// function on_complete(cmd: string, exit_code: int, overall_output: string), +// function on_stdout(cmd: string, overall_output: string, delta_output: string) +// ) +int lua_platform_exec_async_cb(lua_State* L); + + +// Executes the same command up to N times in parallel, iterating through the args provided until all arguments have been used +// array[promise, promise] function platform.exec_parallel_async(cmd: string, N: int, args: array>) +int lua_platform_exec_parallel_async(lua_State* L); + +// array, array function platform.exec_parallel(cmd: string, N: int, args: array>) +int lua_platform_exec_parallel(lua_State* L); \ No newline at end of file diff --git a/include/lua_stack.h b/include/lua_stack.h new file mode 100644 index 0000000..c4dc453 --- /dev/null +++ b/include/lua_stack.h @@ -0,0 +1,43 @@ +#pragma once +#include "includes.h" + + +struct lua_stack +{ + typedef uint8_t type_t; + enum TYPES : type_t + { + NIL, + NUMBER, + BOOLEAN, + INTEGER, + LSTRING, + STRING, + USERDATA, + THREAD, + CFUNCTION, + CCLOSURE, + TABLE + }; + void push_nil (void); + void push_boolean (const bool val); + void push_integer (const lua_Integer val); + void push_number (const lua_Number val); + void push_lstring (const char* beg, const char* end); + void push_lstring (const std::string::iterator& beg, const std::string::iterator& end); + void push_lstring (const char* val, const size_t slen); + void push_string (const char* val); + void push_string (const std::string& val); + void push_lightuserdata (void* p); + void push_thread (lua_State* L); + void push_cfunction (const lua_CFunction f); + void push_cclosure (const lua_CFunction fn, const int num_upvalues); + void push_table (lua_State* L, const int tbl_idx); + + void value_type(const size_t idx); + + uint8_t* m_data; + uint8_t* m_types; + size_t m_size; + size_t m_capacity; +}; \ No newline at end of file diff --git a/include/platform_agnostic.h b/include/platform_agnostic.h new file mode 100644 index 0000000..cf36c1c --- /dev/null +++ b/include/platform_agnostic.h @@ -0,0 +1,8 @@ +#pragma once +#include +#if defined (WIN32) || defined (_WIN32) +FILE* popen(const char* cmd, const char* mode) { return _popen(cmd, mode); } +int pclose(FILE* stream) { return _pclose(stream); }; +#else + +#endif \ No newline at end of file diff --git a/include/simple_string.h b/include/simple_string.h new file mode 100644 index 0000000..25629c7 --- /dev/null +++ b/include/simple_string.h @@ -0,0 +1,47 @@ +#include +#include + +class simple_string +{ +public: + simple_string(const simple_string& src); + simple_string& operator =(const simple_string& src); + simple_string(simple_string&& src) noexcept; + simple_string& operator =(simple_string&& src) noexcept; + + simple_string(const char* beg, const char* end); + simple_string(const char* str, const size_t slen); + simple_string(const char* str); + simple_string(const size_t initial_capacity, const char fill_value='\0'); + simple_string(const std::string& str); + simple_string(); + + ~simple_string(void); + + simple_string& append(const char* other); + simple_string& append(const char* beg, const char* end); + simple_string& append(const char* other, const size_t slen); + simple_string& append(const std::string& other); + simple_string& append(const simple_string& other); + simple_string& append(const char x); + + void resize(const size_t new_size); + void expand(const size_t extra_capacity); + void reserve(const size_t new_capacity); + + const char* c_str(void) const noexcept; + char* data(void) noexcept; + size_t size(void) const noexcept; + + char* begin(void) noexcept; + char* end(void) noexcept; + const char* cbegin(void) const noexcept; + const char* cend(void) const noexcept; + + char& operator [](const size_t idx) noexcept; + const char& operator [](const size_t idx) const noexcept; +private: + char* m_data = nullptr; + size_t m_size = 0; + size_t m_capacity = 0; +}; \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..746d563 --- /dev/null +++ b/makefile @@ -0,0 +1,59 @@ +CXX := clang++ + +INCLUDES := include src thirdparty/luajit/src +LIBRARY_DIRECTORIES := thirdparty/luajit/src +LINKER_INPUTS := lua51 luajit + +DEBUG_DEFINES := +DEFINES := + +CXX_FLAGS := -std=c++20 -g -O0 +CXX_DEBUG_FLAGS := +LNK_FLAGS := -g -O0 +LNK_DEBUG_FLAGS := + +LUAJUT_BUILD_CMD := call msvcbuild.bat + +.PHONY: all clean +all: build_deps link_lbs + +# From https://stackoverflow.com/a/18258352/8617429 +rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) +define \n + +endef +ifeq ($(OS), Windows_NT) + PLATFORM_SEPERATOR := & + touch = type nul > $(1) + rm = rmdir /S /Q $(1) > nul || (exit 0) + RM = del + mkdir = mkdir $(subst /,\,$(1)) > nul 2>&1 || (exit 0) + OUTPUT_NAME := lbs.exe +else + PLATFORM_SEPERATOR := && + touch = touch $(1) + rm = rm -rf $(1) + RM = rm -f + mkdir = mkdir -p $(1) + OUTPUT_NAME := lbs +endif + +clean: + $(call RM,$(call rwildcard, obj,*.o) build/$(OUTPUT_NAME)) + +SOURCE_FILES := $(call rwildcard,src,*.cpp,*.hpp) +OBJECT_FILES := $(foreach d,$(addsuffix .o,$(SOURCE_FILES)), obj/$(d)) + +obj/src/%.cpp.o: src/%.cpp + $(CXX) -c $(CXX_FLAGS) $(foreach inc,$(INCLUDES),-I$(inc)) $(foreach def,$(DEFINES),-D$(def)) $< -o $@ +obj/src/%.hpp.o: src/%.hpp + +link_lbs: $(OBJECT_FILES) + $(CXX) $(LNK_FLAGS) $(LNK_FLAGS) $(foreach lib,$(LIBRARY_DIRECTORIES),-L$(lib)) $(foreach lnk,$(LINKER_INPUTS),-l$(lnk)) $^ -o build/$(OUTPUT_NAME) + +build_deps: .make/thirdparty_luajit + +.make/thirdparty_luajit: + git submodule update --init --recursive + cd thirdparty/luajit/src $(PLATFORM_SEPERATOR) $(LUAJUT_BUILD_CMD) + $(call touch,".make/thirdparty_luajit") \ No newline at end of file diff --git a/src/dynarray.hpp b/src/dynarray.hpp new file mode 100644 index 0000000..8d36db8 --- /dev/null +++ b/src/dynarray.hpp @@ -0,0 +1,365 @@ +#pragma once +#include +#include + +template +class dynarray +{ +public: + dynarray(const dynarray& src); + dynarray& operator =(const dynarray& src); + dynarray(dynarray&& other) noexcept; + dynarray& operator =(dynarray&& other) noexcept; + + dynarray(void); + dynarray(const std::initializer_list& src); + dynarray(const size_t initial_capacity); + + ~dynarray(void); + + T& operator [](const size_t idx) noexcept; + const T& operator [](const size_t idx) const noexcept; + + T* begin(void) noexcept; + const T* begin(void) const noexcept; + const T* cbegin(void) const noexcept; + T* end(void) noexcept; + const T* end(void) const noexcept; + const T* cend(void) const noexcept; + + T* c_str(void) const noexcept; + T* c_array(void) const noexcept; + T* data(void) const noexcept; + size_t size(void) const noexcept; + size_t capacity(void) const noexcept; + + bool is_empty(void) const noexcept; + bool is_nullptr(void) const noexcept; + + void push_back(const T& val); + void push_back(T&& val); + void push_back(const std::initializer_list& vals); + void push_back(const dynarray& vals); + void push_back(const T* vals, const size_t num_vals); + + T&& pop_back(void) noexcept; + + T& front(void) noexcept; + const T& front(void) const noexcept; + T& back(void) noexcept; + const T& back(void) const noexcept; + + void resize(const size_t new_size); + void expand(const size_t extra_capacity); + void reserve(const size_t new_capacity); + void shrink_to_fit(void); +private: + T* m_data = nullptr; + size_t m_capacity = 0; + size_t m_size = 0; +}; + +template +inline dynarray::dynarray(const dynarray& src) +{ + if (m_data == src.m_data) return; + m_size = src.m_size; + m_capacity = src.m_capacity; + m_data = (T*)malloc(src.m_size*sizeof(T)); + if (src.m_data != nullptr) + for (size_t i = 0; i < m_size; ++i) + m_data[i] = src.m_data[i]; +} + +template +inline dynarray& dynarray::operator=(const dynarray& src) +{ + if (m_data == src.m_data) return; + if (m_data != nullptr) delete[] m_data; + m_data = nullptr; + m_size = src.m_size; + m_capacity = src.m_capacity; + m_data = (T*)malloc(src.m_size*sizeof(T)); + if (src.m_data != nullptr) + for (size_t i = 0; i < m_size; ++i) + m_data[i] = src.m_data[i]; + return *this; +} + +template +inline dynarray::dynarray(dynarray&& other) noexcept +{ + if (this == &other) return; + m_data = other.m_data; + m_size = other.m_size; + m_capacity = other.m_capacity; + other.m_size = 0; + other.m_capacity = 0; + other.m_data = nullptr; +} + +template +inline dynarray& dynarray::operator=(dynarray&& other) noexcept +{ + if (this == &other) return *this; + if (m_data != nullptr) delete[] m_data; + m_data = other.m_data; + m_size = other.m_size; + m_capacity = other.m_capacity; + other.m_size = 0; + other.m_capacity = 0; + other.m_data = nullptr; + return *this; +} + +template +inline dynarray::dynarray(void) +{ + m_data = nullptr; + m_size = 0; + m_capacity = 0; +} + +template +inline dynarray::dynarray(const std::initializer_list& src) +{ + m_data = (T*)malloc(src.size()*sizeof(T)); + m_size = src.size(); + m_capacity = src.size(); + if (std::is_trivially_copyable::value) + memcpy(m_data, src.begin(), sizeof(T) * m_size); + else + for (size_t i = 0; i < src.size(); ++i) + m_data[i++] = src.begin()[i]; +} + +template +inline dynarray::dynarray(const size_t initial_capacity) +{ + m_data = (initial_capacity == 0) ? nullptr : (T*)malloc(initial_capacity*sizeof(T)); + m_size = 0; + m_capacity = initial_capacity; +} + +template +inline dynarray::~dynarray(void) +{ + if (m_data != nullptr) + { + if (!std::is_trivially_destructible::value) + { + for (size_t i = 0; i < m_size; ++i) + m_data[i].~T(); + } + free(m_data); + m_data = nullptr; + } +} + +template +inline T& dynarray::operator[](const size_t idx) noexcept +{ return m_data[idx]; } + +template +inline const T& dynarray::operator[](const size_t idx) const noexcept +{ return m_data[idx]; } + +template +inline T* dynarray::begin(void) noexcept +{ return m_data; } + +template +inline const T* dynarray::begin(void) const noexcept +{ return m_data; } + +template +inline const T* dynarray::cbegin(void) const noexcept +{ return m_data; } + +template +inline T* dynarray::end(void) noexcept +{ return m_data == nullptr ? nullptr : (m_data + m_size); } + +template +inline const T* dynarray::end(void) const noexcept +{ return m_data == nullptr ? nullptr : (m_data + m_size); } + +template +inline const T* dynarray::cend(void) const noexcept +{ return m_data == nullptr ? nullptr : (m_data + m_size); } + +template +inline T* dynarray::c_str(void) const noexcept +{ return m_data; } + +template +inline T* dynarray::c_array(void) const noexcept +{ return m_data; } + +template +inline T* dynarray::data(void) const noexcept +{ return m_data; } + +template +inline size_t dynarray::size(void) const noexcept +{ return m_size; } + +template +inline size_t dynarray::capacity(void) const noexcept +{ return m_capacity; } + +template +inline bool dynarray::is_empty(void) const noexcept +{ return m_size == 0; } + +template +inline bool dynarray::is_nullptr(void) const noexcept +{ + return m_data == nullptr || m_capacity == 0; +} + +template +inline void dynarray::push_back(const T& val) +{ + // Double capacity if we can not fit an extra value + if (m_size + 1 > m_capacity) this->expand(m_capacity == 0 ? 32 : m_capacity); + m_data[m_size++] = val; +} + +template +inline void dynarray::push_back(T&& val) +{ + // Double capacity if we can not fit an extra value + if (m_size + 1 > m_capacity) this->expand(m_capacity == 0 ? 32 : m_capacity); + new (m_data + m_size) T(std::move(val)); + m_size++; +} + +template +inline void dynarray::push_back(const std::initializer_list& vals) +{ + const size_t prev_size = m_size; + this->resize(m_size + vals.size()); + if (std::is_trivially_copyable::value) + memcpy(m_data + prev_size, vals.begin(), sizeof(T) * vals.size()); + else + for (size_t i = 0; i < vals.size(); ++i) + m_data[prev_size + i] = vals.begin()[i]; +} + +template +inline void dynarray::push_back(const dynarray& vals) +{ + const size_t prev_size = m_size; + this->resize(m_size + vals.m_size); + if (std::is_trivially_copyable::value) + memcpy(m_data + prev_size, vals.m_data, sizeof(T) * vals.m_size); + else + for (size_t i = 0; i < vals.m_size; ++i) + m_data[prev_size + i] = vals.m_data[i]; +} + +template +inline void dynarray::push_back(const T* vals, const size_t num_vals) +{ + const size_t prev_size = m_size; + this->resize(m_size + num_vals); + if (std::is_trivially_copyable::value) + memcpy(m_data + prev_size, vals, sizeof(T) * num_vals); + else + for (size_t i = 0; i < num_vals; ++i) + m_data[prev_size + i] = vals[i]; +} + +template +inline T&& dynarray::pop_back(void) noexcept +{ + m_size--; + return std::move(m_data[m_size]); +} + +template +inline T& dynarray::front(void) noexcept +{ return *m_data; } + +template +inline const T& dynarray::front(void) const noexcept +{ return *m_data; } + +template +inline T& dynarray::back(void) noexcept +{ return m_data[m_size - 1]; } + +template +inline const T& dynarray::back(void) const noexcept +{ return m_data[m_size-1]; } + +template +inline void dynarray::resize(const size_t new_size) +{ + if (new_size > m_capacity) + this->expand(new_size); + else if (new_size < m_size && std::is_trivially_destructible::value) + for (size_t i = new_size; i < m_size; ++i) + m_data[i].~T(); + m_size = new_size; +} + +template +inline void dynarray::expand(const size_t extra_capacity) +{ this->reserve(m_capacity + extra_capacity); } + +template +inline void dynarray::reserve(const size_t new_capacity) +{ + if (new_capacity <= m_capacity) return; + T* new_data = (T*)malloc(new_capacity*sizeof(T)); + if (new_data == nullptr) return; + if constexpr (std::is_trivially_copyable::value) + memcpy(new_data, m_data, m_size * sizeof(T)); + else if constexpr (std::is_move_assignable::value) + { + for (size_t i = 0; i < m_size; ++i) + new (new_data + i) T(std::move(m_data[i])); + } + else if constexpr (std::is_copy_assignable::value) + { + for (size_t i = 0; i < m_size; ++i) + new (new_data+i) T(m_data[i]); + } + + if (!std::is_trivially_destructible::value) + { + for (size_t i = 0; i < m_size; ++i) + m_data[i].~T(); + } + free(m_data); + + m_data = new_data; + m_capacity = new_capacity; +} + +template +inline void dynarray::shrink_to_fit(void) +{ + if (m_capacity == m_size) return; + if (m_size == 0) + { + delete[] m_data; + m_capacity = 0; + m_data = nullptr; + } + + T* new_data = (T*)malloc(m_size*sizeof(T)); + if (std::is_trivially_copyable::value) + memcpy(new_data, m_data, m_size * sizeof(T)); + else if (std::is_move_assignable::value) + for (size_t i = 0; i < m_size; ++i) + new (new_data+i) T(std::move(m_data[i])); + else if (std::is_copy_assignable::value) + for (size_t i = 0; i < m_size; ++i) + new (new_data+i) T(m_data[i]); + delete[] m_data; + m_data = new_data; + m_capacity = m_size; +} diff --git a/src/lua_dependency_tree.cpp b/src/lua_dependency_tree.cpp new file mode 100644 index 0000000..9800605 --- /dev/null +++ b/src/lua_dependency_tree.cpp @@ -0,0 +1,667 @@ +#include "lua_dependency_tree.h" +#include + +dependency_tree::node& dependency_tree::operator[](const ref_t idx) noexcept +{ return m_nodes[idx]; } + +const dependency_tree::node& dependency_tree::operator[](const ref_t idx) const noexcept +{ return m_nodes[idx]; } + +dynarray dependency_tree::get_leaf_nodes(const bool ignore_fulfilled) const +{ + dynarray result; + for (size_t i = 0; i < m_nodes.size(); ++i) + { + if (m_nodes[i].m_num_pending_dependencies == 0 && + (!ignore_fulfilled || m_nodes[i].m_state != FULFILLED)) + result.push_back(i); + } + return result; +} + +dynarray dependency_tree::get_leaf_node_names(const bool ignore_fulfilled) const +{ + dynarray refs = this->get_leaf_nodes(ignore_fulfilled); + dynarray names; + for (const ref_t ref : refs) + names.push_back(m_nodes[ref].m_name); + return names; +} + +dynarray dependency_tree::filter_nodes(const state_cmp_t state_cmp, const ref_t num_dependencies_gt, const bool invert_num_deps) const +{ + dynarray res; + + for (ref_t i = 0; i < m_nodes.size(); ++i) + { + const node& n = m_nodes[i]; + // Ignore nodes that do not match the num_dependencies requirement + if (!((n.m_num_pending_dependencies > num_dependencies_gt) ^ invert_num_deps)) continue; + + bool inc = false; + inc |= (state_cmp & CMP_FULFILED) > 0 && n.m_state == FULFILLED; + inc |= (state_cmp & CMP_FAILED ) > 0 && n.m_state == FAILED; + inc |= (state_cmp & CMP_WORKING ) > 0 && n.m_state == WORKING; + inc |= (state_cmp & CMP_WAITING ) > 0 && n.m_state == WAITING; + if (!inc) continue; + + res.push_back(i); + } + + return res; +} + +// Sets the state of a node +// If the node transitions from a fulfilled state it will recursively update its parents +void dependency_tree::set_node_state(const ref_t ref_node, const state_t new_state) +{ + node& n = m_nodes[ref_node]; + if (n.m_state == new_state) return; + if (new_state == WORKING && n.m_num_pending_dependencies != 0) return; + if (new_state == FULFILLED && n.m_num_pending_dependencies != 0) return; + + + + else if (n.m_state == FULFILLED && new_state != FULFILLED) + { + for (const ref_t ref_p : n.m_dependents) + { + node& p = m_nodes[ref_p]; + p.m_num_pending_dependencies++; + if (p.m_state == FULFILLED) + this->set_node_state(ref_p, WAITING); + } + } + // If changing from a non-resolved state to a resolved state, + // Decrement the node and update its parents + else if (new_state == FULFILLED) + { + for (const ref_t ref_p : n.m_dependents) + { + node& p = m_nodes[ref_p]; + p.m_num_pending_dependencies--; + if (p.m_num_pending_dependencies == 0) + this->set_node_state(ref_p, WORKING); + } + } + + n.m_state = new_state; +} + +void dependency_tree::set_node_state(const char* node, const state_t new_state) +{ this->set_node_state(m_node_name_mapping[node], new_state); } + +dependency_tree::ref_t dependency_tree::add_node(const char* node_name, const dynarray& dependents) +{ + ref_t ret = -1; + // The node already exists, so just add non-duplicating dependents to it + if (m_node_name_mapping.contains(node_name)) + { + ret = m_node_name_mapping[node_name]; + for (const ref_t dep : dependents) + { + if (m_nodes[ret].m_dependents.insert(dep).second) + m_nodes[dep].m_num_pending_dependencies += m_nodes[ret].m_state != FULFILLED; + } + } + // Otherwise create the node and add the dependencies to it + else + { + ret = (ref_t)m_nodes.size(); + node n; + n.m_dependents = std::set(dependents.cbegin(), dependents.cend()); + size_t sl = strlen(node_name); + char* local_name = new char[sl + 1]; + local_name[sl] = '\0'; + memcpy(local_name, node_name, sl); + n.m_name = local_name; + n.m_num_pending_dependencies = 0; + n.m_state = WAITING; + n.m_userdata = 0; + m_nodes.push_back(std::move(n)); + m_node_name_mapping[local_name] = ret; + for (const ref_t dep : dependents) + m_nodes[dep].m_num_pending_dependencies += m_nodes.back().m_state != FULFILLED; + } + return ret; +} + +dependency_tree::ref_t dependency_tree::add_node(const char* node_name, const dynarray& dependents) +{ + dynarray ref_dependents; + for (const char* dep : dependents) + ref_dependents.push_back(m_node_name_mapping[dep]); + return this->add_node(node_name, ref_dependents); +} + +dependency_tree::ref_t dependency_tree::add_node(const char* node_name, const std::initializer_list& dependents) +{ return this->add_node(node_name, dynarray(dependents)); } + +dependency_tree::ref_t dependency_tree::add_node(const char* node_name, const std::initializer_list& dependents) +{ + dynarray ref_dependents; + for (const char* dep : dependents) + ref_dependents.push_back(m_node_name_mapping[dep]); + return this->add_node(node_name, ref_dependents); +} + +dependency_tree::ref_t dependency_tree::add_node(const char* node_name) +{ + if (!m_node_name_mapping.contains(node_name)) + { + ref_t ret = (ref_t)m_nodes.size(); + node n; + n.m_dependents = std::set(); + size_t sl = strlen(node_name); + char* local_name = new char[sl + 1]; + local_name[sl] = '\0'; + memcpy(local_name, node_name, sl); + n.m_name = local_name; + n.m_num_pending_dependencies = 0; + n.m_state = WAITING; + n.m_userdata = 0; + m_nodes.push_back(std::move(n)); + m_node_name_mapping[local_name] = ret; + return ret; + } + return m_node_name_mapping[node_name]; +} + +void dependency_tree::add_node_dependency(const ref_t node, const ref_t dependency) +{ + if (m_nodes[dependency].m_dependents.insert(node).second) + m_nodes[node].m_num_pending_dependencies++; +} + +void dependency_tree::add_node_dependency(const ref_t node, const char* dependency) +{ this->add_node_dependency(node, m_node_name_mapping[dependency]); } + +void dependency_tree::add_node_dependency(const char* node, const ref_t dependency) +{ this->add_node_dependency(m_node_name_mapping[node], dependency); } + +void dependency_tree::add_node_dependency(const char* node, const char* dependency) +{ this->add_node_dependency(m_node_name_mapping[node], m_node_name_mapping[dependency]); } + +void dependency_tree::add_node_dependencies(const ref_t node, const std::initializer_list& dependencies) +{ + for (const ref_t& dep : dependencies) + this->add_node_dependency(node, dep); +} + +void dependency_tree::add_node_dependencies(const ref_t node, const dynarray& dependencies) +{ + for (const ref_t& dep : dependencies) + this->add_node_dependency(node, dep); +} +void dependency_tree::add_node_dependencies(const ref_t node, const std::initializer_list& dependencies) +{ + for (const char* dep : dependencies) + this->add_node_dependency(node, dep); +} + +void dependency_tree::add_node_dependencies(const ref_t node, const dynarray& dependencies) +{ + for (const char* dep : dependencies) + this->add_node_dependency(node, dep); +} + +dependency_tree::~dependency_tree() {} + +dependency_tree::node::node(node&& other) noexcept +{ + this->m_dependents = std::move(other.m_dependents); + this->m_name = other.m_name; + this->m_num_pending_dependencies = other.m_num_pending_dependencies; + this->m_state = other.m_state; + this->m_userdata = other.m_userdata; + other.m_name = nullptr; + other.m_num_pending_dependencies = -1; + other.m_state = -1; + other.m_userdata = -1; +} + +dependency_tree::node& dependency_tree::node::operator=(node&& other) noexcept +{ + if (this == &other) return *this; + + if (this->m_name != nullptr) delete[] m_name; + + this->m_dependents = std::move(other.m_dependents); + this->m_name = other.m_name; + this->m_num_pending_dependencies = other.m_num_pending_dependencies; + this->m_state = other.m_state; + this->m_userdata = other.m_userdata; + other.m_name = nullptr; + other.m_num_pending_dependencies = -1; + other.m_state = -1; + other.m_userdata = -1; + + return *this; +} + +dependency_tree::node::node() +{ + this->m_name = nullptr; + this->m_num_pending_dependencies = -1; + this->m_state = -1; + this->m_userdata = -1; +} + +dependency_tree::node::~node() +{ + if (m_name == nullptr) return; + delete[] m_name; +} + + +int luaopen_dependency_tree(lua_State* L) +{ + luaL_newmetatable(L, "lbs.dependency_tree"); + + lua_pushcfunction(L, lua_dependency_destroy); + lua_setfield(L, -2, "__gc"); + + lua_pushcfunction(L, lua_dependency_add_node); + lua_setfield(L, -2, "add_node"); + lua_pushcfunction(L, lua_dependency_add_dependency); + lua_setfield(L, -2, "add_dependency"); + lua_pushcfunction(L, lua_dependency_add_dependencies); + lua_setfield(L, -2, "add_dependencies"); + lua_pushcfunction(L, lua_dependency_remove_dependency); + lua_setfield(L, -2, "remove_dependency"); + lua_pushcfunction(L, lua_dependency_remove_dependencies); + lua_setfield(L, -2, "remove_dependencies"); + lua_pushcfunction(L, lua_dependency_get_leafs); + lua_setfield(L, -2, "get_leafs"); + lua_pushcfunction(L, lua_dependency_match_nodes); + lua_setfield(L, -2, "match"); + lua_pushcfunction(L, lua_dependency_set_node_state); + lua_setfield(L, -2, "set_state"); + lua_pushcfunction(L, lua_dependency_get_node_state); + lua_setfield(L, -2, "get_state"); + lua_pushcfunction(L, lua_dependency_get_node_userval); + lua_setfield(L, -2, "get_userval"); + lua_pushcfunction(L, lua_dependency_set_node_userval); + lua_setfield(L, -2, "set_userval"); + lua_pushcfunction(L, lua_dependency_get_node_name); + lua_setfield(L, -2, "get_name"); + lua_pushcfunction(L, lua_dependency_get_node); + lua_setfield(L, -2, "get_node"); + + //lua_pushcfunction(L, lua_dependency_index); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + + lua_newtable(L); + + lua_pushcfunction(L, lua_dependency_create); + lua_setfield(L, -2, "create"); + lua_pushcfunction(L, lua_dependency_add_node); + lua_setfield(L, -2, "add_node"); + lua_pushcfunction(L, lua_dependency_add_dependency); + lua_setfield(L, -2, "add_dependency"); + lua_pushcfunction(L, lua_dependency_add_dependencies); + lua_setfield(L, -2, "add_dependencies"); + lua_pushcfunction(L, lua_dependency_remove_dependency); + lua_setfield(L, -2, "remove_dependency"); + lua_pushcfunction(L, lua_dependency_remove_dependencies); + lua_setfield(L, -2, "remove_dependencies"); + lua_pushcfunction(L, lua_dependency_get_leafs); + lua_setfield(L, -2, "get_leafs"); + lua_pushcfunction(L, lua_dependency_match_nodes); + lua_setfield(L, -2, "match"); + lua_pushcfunction(L, lua_dependency_set_node_state); + lua_setfield(L, -2, "set_state"); + lua_pushcfunction(L, lua_dependency_get_node_state); + lua_setfield(L, -2, "get_state"); + lua_pushcfunction(L, lua_dependency_get_node_userval); + lua_setfield(L, -2, "get_userval"); + lua_pushcfunction(L, lua_dependency_set_node_userval); + lua_setfield(L, -2, "set_userval"); + lua_pushcfunction(L, lua_dependency_get_node_name); + lua_setfield(L, -2, "get_name"); + lua_pushcfunction(L, lua_dependency_get_node); + lua_setfield(L, -2, "get_node"); + + lua_pushvalue(L, -1); + lua_setfield(L, LUA_GLOBALSINDEX, "dependency_tree"); + return 1; +} + +dependency_tree* get_lua_dependency_tree(lua_State* L, int idx) +{ return (dependency_tree*)luaL_checkudata(L, idx, "lbs.dependency_tree"); } + +dependency_tree::ref_t get_lua_dependency_ref(dependency_tree* tree, lua_State* L, int idx) +{ + if (lua_isnumber(L, idx)) + { + if (lua_tonumber(L, idx) >= tree->m_nodes.size()) return -1; + return lua_tonumber(L, idx); + } + else if (lua_isstring(L, idx)) + { + const char* name = lua_tostring(L, idx); + if (tree->m_node_name_mapping.contains(name)) return tree->m_node_name_mapping[name]; + return -1; + } + return -1; +} + +int lua_dependency_create(lua_State* L) +{ + dependency_tree* tree = (dependency_tree*)lua_newuserdata(L, sizeof(dependency_tree)); + new (tree) dependency_tree(); + luaL_getmetatable(L, "lbs.dependency_tree"); + lua_setmetatable(L, -2); + return 1; +} + +int lua_dependency_destroy(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, -1); + if (tree == nullptr) return 0; + tree->~dependency_tree(); + return 0; +} + +int lua_dependency_add_node(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING, 2, "Expected first argument to be a string of the dependency nodes name"); + int t = lua_type(L, 3); + luaL_argcheck(L, lua_type(L, 3) == LUA_TTABLE || lua_type(L, 3) <= LUA_TNIL, 3, "Expected second argument to be a table of the names or references of the dependents or nil"); + + const char* node_name = lua_tostring(L, 2); + + if (lua_type(L, 3) == LUA_TTABLE) + { + static dynarray dependent_nodes; + dependent_nodes.resize(0); + dependent_nodes.reserve(lua_objlen(L, 3)); + for (size_t i = 1; i < lua_objlen(L, 3); ++i) + { + lua_rawgeti(L, 3, i); + if (lua_type(L, -1) != LUA_TSTRING && lua_type(L, -1) != LUA_TNUMBER) + return luaL_error(L, "Expected value %d in second argument to be a string or reference, not %s", i, lua_typename(L, lua_type(L, -1))); + dependency_tree::ref_t n = get_lua_dependency_ref(tree, L, -1); + if (n == -1) + return luaL_error(L, "Expected value %d in second argument to be a valid node name or reference", i); + dependent_nodes.push_back(n); + lua_pop(L, 1); + } + lua_pushinteger(L, tree->add_node(node_name, dependent_nodes)); + } + else + lua_pushinteger(L, tree->add_node(node_name)); + + return 1; +} + +int lua_dependency_add_dependency(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lua_type(L, 2) == LUA_TNUMBER, 2, "Expected first argument to be a string of the dependency nodes name or a reference to it"); + luaL_argcheck(L, lua_type(L, 3) == LUA_TSTRING || lua_type(L, 3) == LUA_TNUMBER, 3, "Expected second argument to be a string of the dependent node's name or a reference to it"); + dependency_tree::ref_t dependency = get_lua_dependency_ref(tree, L, 2); + if (dependency == -1) + return luaL_error(L, "Expected first argument to be a valid name or reference to a node"); + dependency_tree::ref_t dependent = get_lua_dependency_ref(tree, L, 3); + if (dependent == -1) + return luaL_error(L, "Expected second argument to be a valid name or reference to a node"); + tree->add_node_dependency(dependency, dependent); + return 0; +} + +int lua_dependency_remove_dependency(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t dependency = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, dependency != -1, 2, "Expected first argument to be a valid node name or reference"); + dependency_tree::ref_t dependent = get_lua_dependency_ref(tree, L, 3); + luaL_argcheck(L, dependent != -1, 3, "Expected second argument to be a valid node name or reference"); + tree->m_nodes[dependent].m_dependents.erase(dependency); + return 0; +} + +int lua_dependency_add_dependencies(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t dependency = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, dependency != -1, 1, "Expected first argument to be a valid name or reference to a node"); + + static dynarray dependents; + dependents.resize(0); // Clear prior values + dependents.reserve(lua_gettop(L)); // This is approx. equal to top-2 + for (size_t idx = 3; idx < lua_gettop(L); ++idx) + { + if (lua_type(L, idx) == LUA_TTABLE) + { + int tbl_len = lua_objlen(L, idx); + for (size_t i = 1; i < tbl_len; ++i) + { + lua_rawgeti(L, idx, i); + dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, idx); + if (ref == -1) + return luaL_error(L, "Expected argument %d[%d] to be a valid name or reference of a node", idx - 2, i); + dependents.push_back(ref); + lua_pop(L, 1); + } + } + else + { + dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, idx); + if (ref == -1) + return luaL_error(L, "Expected argument %d to be a valid name or reference of a node", idx-2); + dependents.push_back(ref); + } + } + + tree->add_node_dependencies(dependency, dependents); + return 0; +} + +int lua_dependency_remove_dependencies(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t dependency = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, dependency != -1, 2, "Expected first argument to be a valid node name or reference"); + + for (size_t idx = 3; idx < lua_gettop(L); ++idx) + { + if (lua_type(L, idx) == LUA_TTABLE) + { + int tbl_len = lua_objlen(L, 3); + for (int i = 1; i < tbl_len; ++i) + { + lua_rawgeti(L, idx, i); + dependency_tree::ref_t dependent = get_lua_dependency_ref(tree, L, -1); + if (dependent != -1) + return luaL_error(L, "Expected argument %d[%d] to be a valid name or reference to a node", idx-2,i); + tree->m_nodes[dependent].m_dependents.erase(dependency); + lua_pop(L, 1); + } + } + else + { + const dependency_tree::ref_t dependent = get_lua_dependency_ref(tree, L, idx); + if (dependent != -1) + return luaL_error(L, "Expected argument %d to be a valid name or reference to a node", idx - 2); + tree->m_nodes[dependent].m_dependents.erase(dependency); + } + } + + return 0; +} + +int lua_dependency_get_leafs(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dynarray leafs; + if (lua_isboolean(L, 2) && (lua_toboolean(L, 2) > 0)) + leafs = tree->get_leaf_nodes(); + else + leafs = tree->get_leaf_nodes(false); + + lua_newtable(L); + for (const dependency_tree::ref_t leaf : leafs) + { + lua_pushinteger(L, leaf); + lua_rawseti(L, -2, lua_objlen(L, -2) + 1); + } + + return 1; +} + +int lua_dependency_match_nodes(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER || lua_type(L, 2) <= LUA_TNIL, 2, "Expected first argument to be a number or nil. This is what states need to be true\n\tFulfiled = 1\n\tFailed = 2\n\tWorking = 4\n\tWaiting = 8"); + luaL_argcheck(L, lua_type(L, 3) == LUA_TNUMBER || lua_type(L, 3) <= LUA_TNIL, 3, "Expected second argument to be a number or nil. This is the least number of pending dependencies a node needs to have to pass"); + luaL_argcheck(L, lua_type(L, 4) == LUA_TBOOLEAN || lua_type(L, 4) <= LUA_TNIL, 4, "Expected third argument to be a boolean or nil. This controls whether the number of pending dependencies is greater or less-equal"); + + dynarray matches = tree->filter_nodes( + lua_tointeger(L, 2) & 0b1111, + lua_tointeger(L, 3), + lua_toboolean(L, 3) > 0 + ); + + lua_newtable(L); + for (const dependency_tree::ref_t match : matches) + { + lua_pushinteger(L, match); + lua_rawseti(L, -2, lua_objlen(L, -2)+1); + } + + return 1; +} + +int lua_dependency_set_node_state(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t node = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, node != -1, 2, "Expected first argument to be a valid node name or reference"); + luaL_argcheck(L, lua_type(L, 3) == LUA_TNUMBER || lua_type(L, 3) == LUA_TSTRING, 3, "Expected second argument to be a number or string of the state\n\t0 = FULFILLED\n\t1 = FAILED\n\t2 = WAITING\n\t3 = WORKING"); + + uint8_t new_state; + if (lua_type(L, 3) == LUA_TNUMBER) + new_state = lua_tointeger(L, 3) & 0b11; + else + { + const char* state_name = lua_tostring(L, 3); + if (strcmp(state_name, "FULFILLED") == 0 || strcmp(state_name, "fulfilled") == 0) + new_state = dependency_tree::FULFILLED; + else if (strcmp(state_name, "FAILED") == 0 || strcmp(state_name, "failed") == 0) + new_state = dependency_tree::FAILED; + else if (strcmp(state_name, "WAITING") == 0 || strcmp(state_name, "waiting") == 0) + new_state = dependency_tree::WAITING; + else if (strcmp(state_name, "WORKING") == 0 || strcmp(state_name, "working") == 0) + new_state = dependency_tree::WORKING; + else return luaL_error(L, "Expected seconda argument to be fulfilled, failed, waiting or working not %s", state_name); + } + + tree->set_node_state(node, new_state); + return 0; +} + +int lua_dependency_get_node_userval(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, ref != -1, 2, "Expected first argument to be a valid node name or reference"); + + lua_pushinteger(L, tree->m_nodes[ref].m_userdata); + + return 1; +} + +int lua_dependency_set_node_userval(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, ref != -1, 2, "Expected first argument to be a valid node name or reference"); + luaL_argcheck(L, lua_type(L, 3) == LUA_TNUMBER, 3, "Expected second argument to be a 30-bit number"); + + tree->m_nodes[ref].m_userdata = lua_tointeger(L, 3) & 0x3FFFFFFF; + + return 0; +} + +int lua_dependency_get_node_state(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, ref != -1, 2, "Expected first argument to be a valid node name or reference"); + + const dependency_tree::node& node = tree->m_nodes[ref]; + if (node.m_state == dependency_tree::FULFILLED) lua_pushstring(L, "fulfilled"); + else if (node.m_state == dependency_tree::FAILED) lua_pushstring(L, "failed"); + else if (node.m_state == dependency_tree::WAITING) lua_pushstring(L, "waiting"); + else if (node.m_state == dependency_tree::WORKING) lua_pushstring(L, "working"); + + return 1; +} + +int lua_dependency_get_node_name(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, ref != -1, 2, "Expected first argument to be a valid node name or reference"); + + lua_pushstring(L, tree->m_nodes[ref].m_name); + + return 1; +} + +int lua_dependency_get_node(lua_State* L) +{ + dependency_tree* tree = get_lua_dependency_tree(L, 1); + luaL_argcheck(L, tree != nullptr, 1, "Expected implicit argument to be userdata"); + const dependency_tree::ref_t ref = get_lua_dependency_ref(tree, L, 2); + luaL_argcheck(L, ref != -1, 2, "Expected first argument to be a valid node name or reference"); + + const dependency_tree::node& node = tree->m_nodes[ref]; + lua_newtable(L); + + lua_pushstring(L, "name"); + lua_pushstring(L, node.m_name); + lua_settable(L, -3); + + lua_pushstring(L, "pending_dependencies"); + lua_pushinteger(L, node.m_num_pending_dependencies); + lua_settable(L, -3); + + lua_pushstring(L, "state"); + if (node.m_state == dependency_tree::FULFILLED) lua_pushstring(L, "fulfilled"); + else if (node.m_state == dependency_tree::FAILED) lua_pushstring(L, "failed"); + else if (node.m_state == dependency_tree::WAITING) lua_pushstring(L, "waiting"); + else if (node.m_state == dependency_tree::WORKING) lua_pushstring(L, "working"); + lua_settable(L, -3); + + lua_pushstring(L, "userdata"); + lua_pushinteger(L, node.m_userdata); + lua_settable(L, -3); + + lua_pushstring(L, "dependents"); + lua_newtable(L); + for (const dependency_tree::ref_t dependent : node.m_dependents) + { + lua_pushinteger(L, dependent); + lua_rawseti(L, -2, lua_objlen(L, -2)); + } + lua_settable(L, -3); + + return 1; +} diff --git a/src/lua_filesystem.cpp b/src/lua_filesystem.cpp new file mode 100644 index 0000000..f57ba01 --- /dev/null +++ b/src/lua_filesystem.cpp @@ -0,0 +1,331 @@ +#include "lua_filesystem.h" +#include + +int luaopen_filesystem(lua_State* L) +{ + luaL_newmetatable(L, "fs.file_handle"); + + // Push iterator state metatables + luaL_newmetatable(L, "fs.stdlibcpp.filesystem.directory_iterator"); + lua_pushcfunction(L, lua_fs_foreach_dir_dtor); + lua_setfield(L, -2, "__gc"); + + luaL_newmetatable(L, "fs.stdlibcpp.filesystem.recursive_directory_iterator"); + lua_pushcfunction(L, lua_fs_forall_dir_dtor); + lua_setfield(L, -2, "__gc"); + + + // Push functions + lua_newtable(L); + + lua_pushcfunction(L, lua_fs_absolute); + lua_setfield(L, -2, "absolute"); + + lua_pushcfunction(L, lua_fs_create_dir); + lua_setfield(L, -2, "create_dir"); + + lua_pushcfunction(L, lua_fs_exists); + lua_setfield(L, -2, "exists"); + + lua_pushcfunction(L, lua_fs_file_directory); + lua_setfield(L, -2, "dir"); + + lua_pushcfunction(L, lua_fs_file_extension); + lua_setfield(L, -2, "extension"); + + lua_pushcfunction(L, lua_fs_file_name); + lua_setfield(L, -2, "file_name"); + + lua_pushcfunction(L, lua_fs_forall_dir); + lua_setfield(L, -2, "forall"); + + lua_pushcfunction(L, lua_fs_foreach_dir); + lua_setfield(L, -2, "foreach"); + + lua_pushcfunction(L, lua_fs_is_dir); + lua_setfield(L, -2, "is_dir"); + + lua_pushcfunction(L, lua_fs_is_file); + lua_setfield(L, -2, "is_file"); + + lua_pushcfunction(L, lua_fs_last_modified); + lua_setfield(L, -2, "last_modified"); + + lua_pushcfunction(L, lua_fs_list_dir); + lua_setfield(L, -2, "list_dir"); + + lua_pushcfunction(L, lua_fs_list_alldirs); + lua_setfield(L, -2, "list_alldirs"); + + lua_pushcfunction(L, lua_fs_relative); + lua_setfield(L, -2, "relative_to"); + + lua_pushvalue(L, -1); + lua_setfield(L, LUA_GLOBALSINDEX, "fs"); + + return 1; +} + +int lua_fs_list_dir(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a path to make the second argument relative to"); + const char* fpath = lua_tostring(L, 1); + lua_newtable(L); + uint32_t i = 1; + for (const std::filesystem::path& p : std::filesystem::directory_iterator(fpath)) + { + const std::string x = p.string(); + lua_pushlstring(L, x.c_str(), x.size()); + lua_rawseti(L, -2, i); + } + return 1; +} + +int lua_fs_list_alldirs(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a path to make the second argument relative to"); + const char* fpath = lua_tostring(L, 1); + lua_newtable(L); + uint32_t i = 1; + for (const std::filesystem::path& p : std::filesystem::directory_iterator(fpath)) + { + const std::string x = p.string(); + lua_pushlstring(L, x.c_str(), x.size()); + lua_rawseti(L, -2, i); + } + return 1; +} + +int lua_fs_foreach_dir(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a path"); + const char* path = lua_tostring(L, 1); + + void* ud = lua_newuserdata(L, sizeof(std::filesystem::directory_iterator)); + new (ud) std::filesystem::directory_iterator(path); + + luaL_getmetatable(L, "fs.stdlibcpp.filesystem.directory_iterator"); + lua_setmetatable(L, -2); + lua_pushcclosure(L, lua_fs_foreach_dir_next, 1); + return 1; +} + +int lua_fs_forall_dir(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a path"); + const char* path = lua_tostring(L, 1); + + void* ud = lua_newuserdata(L, sizeof(std::filesystem::recursive_directory_iterator)); + new (ud) std::filesystem::recursive_directory_iterator(path); + + luaL_getmetatable(L, "fs.stdlibcpp.filesystem.recursive_directory_iterator"); + lua_setmetatable(L, -2); + lua_pushcclosure(L, lua_fs_forall_dir_next, 1); + return 1; +} + +int lua_fs_foreach_dir_next(lua_State* L) +{ + int idx = lua_upvalueindex(1); + luaL_argcheck(L, lua_type(L, idx) == LUA_TUSERDATA, 1, "Expected first argument to fs.foreach_dir_next to be userdata"); + void* ud = luaL_checkudata(L, idx, "fs.stdlibcpp.filesystem.directory_iterator"); + luaL_argcheck(L, ud != nullptr, 1, "Expected first argument's userdata type to be a std::filesystem::directory_iterator"); + std::filesystem::directory_iterator& x = *(std::filesystem::directory_iterator*)ud; + if (x._At_end()) + return 0; + std::string p = (*x).path().string(); + lua_pushlstring(L, p.c_str(), p.size()); + x++; + return 1; +} + +int lua_fs_forall_dir_next(lua_State* L) +{ + int idx = lua_upvalueindex(1); + luaL_argcheck(L, lua_type(L, idx) == LUA_TUSERDATA, 1, "Expected first argument to fs.forall_dir_next to be userdata"); + void* ud = luaL_checkudata(L, idx, "fs.stdlibcpp.filesystem.recursive_directory_iterator"); + luaL_argcheck(L, ud != nullptr, 1, "Expected first argument's userdata type to be a std::filesystem::recursive_directory_iterator"); + std::filesystem::recursive_directory_iterator& x = *(std::filesystem::recursive_directory_iterator*)ud; + if (x == std::filesystem::recursive_directory_iterator{}) + return 0; + std::string p = (*x).path().string(); + lua_pushlstring(L, p.c_str(), p.size()); + x++; + return 1; +} + +int lua_fs_foreach_dir_dtor(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TUSERDATA, 1, "Expected first argument to fs.foreach_dir_next to be userdata"); + void* ud = luaL_checkudata(L, 1, "fs.stdlibcpp.filesystem.directory_iterator"); + luaL_argcheck(L, ud != nullptr, 1, "Expected first argument's userdata type to be a std::filesystem::directory_iterator"); + std::filesystem::directory_iterator& x = *(std::filesystem::directory_iterator*)ud; + x.~directory_iterator(); + return 0; +} + +int lua_fs_forall_dir_dtor(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TUSERDATA, 1, "Expected first argument to fs.forall_dir_next to be userdata"); + void* ud = luaL_checkudata(L, 1, "fs.stdlibcpp.filesystem.recursive_directory_iterator"); + luaL_argcheck(L, ud != nullptr, 1, "Expected first argument's userdata type to be a std::filesystem::recursive_directory_iterator"); + std::filesystem::recursive_directory_iterator& x = *(std::filesystem::recursive_directory_iterator*)ud; + x.~recursive_directory_iterator(); + return 0; +} + +int lua_fs_relative(lua_State* L) +{ + try { + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a path to make the second argument relative to"); + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING, 2, "Expected second argument to be a string representing a full file path"); + const std::string f = std::filesystem::relative(std::filesystem::path(lua_tostring(L, 1)), std::filesystem::path(lua_tostring(L, 2))).string(); + lua_pushlstring(L, f.c_str(), f.size()); + return 1; + } catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } +} + +int lua_fs_absolute(lua_State* L) +{ + try { + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + const std::string f = std::filesystem::absolute(std::filesystem::path(lua_tostring(L, 1))).string(); + lua_pushlstring(L, f.c_str(), f.size()); + return 1; + } catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } +} + +int lua_fs_file_name(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + const std::string f = std::filesystem::path(lua_tostring(L, 1)).filename().string(); + lua_pushlstring(L, f.c_str(), f.size()); + return 1; + } catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } +} + +int lua_fs_file_extension(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + const std::string f = std::filesystem::path(lua_tostring(L, 1)).extension().string(); + lua_pushlstring(L, f.c_str(), f.size()); + } catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 1; +} + +int lua_fs_file_directory(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + const std::string f = std::filesystem::path(lua_tostring(L, 1)).remove_filename().string(); + lua_pushlstring(L, f.c_str(), f.size()); + return 1; + } catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 0; +} + +int lua_fs_last_modified(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + lua_pushinteger(L, std::filesystem::last_write_time(std::filesystem::path(lua_tostring(L, 1))).time_since_epoch().count()); + return 1; + } catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 0; +} + +int lua_fs_exists(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + lua_pushboolean(L, std::filesystem::exists(lua_tostring(L, 1))); + return 1; + } + catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 0; +} + +int lua_fs_is_dir(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + lua_pushboolean(L, std::filesystem::is_directory(lua_tostring(L, 1))); + return 1; + } + catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 0; +} + +int lua_fs_is_file(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + lua_pushboolean(L, std::filesystem::is_regular_file(lua_tostring(L, 1))); + return 1; + } + catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 0; +} + +int lua_fs_create_dir(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a full file path"); + try { + lua_pushboolean(L, std::filesystem::create_directories(lua_tostring(L, 1))); + return 1; + } + catch (const std::exception& e) + { + lua_pushnil(L); + lua_pushstring(L, e.what()); + return 2; + } + return 0; +} diff --git a/src/lua_platform.cpp b/src/lua_platform.cpp new file mode 100644 index 0000000..913fb47 --- /dev/null +++ b/src/lua_platform.cpp @@ -0,0 +1,191 @@ +#include "lua_platform.h" +#include "platform_agnostic.h" +#include "dynarray.hpp" +#include "mutex" +#include +#include "simple_string.h" + +int luaopen_platform(lua_State* L) +{ + lua_newtable(L); + + lua_pushcfunction(L, lua_platform_exec); + lua_setfield(L, -2, "exec"); + lua_pushcfunction(L, lua_platform_exec_parallel); + lua_setfield(L, -2, "exec_parallel"); + + lua_pushvalue(L, -1); + lua_setfield(L, LUA_GLOBALSINDEX, "platform"); + return 1; +} + +int lua_platform_exec(lua_State* L) +{ + luaL_argcheck(L, lua_isstring(L, 1), 1, "Expected first argument to be a string"); + luaL_argcheck(L, lua_istable(L, 2), 2, "Expected second argument to be a table of strings"); + std::string cmd(lua_tostring(L, 1)); + size_t nargs = lua_objlen(L, 2); + for (size_t i = 0; i < nargs; ++i) + { + lua_rawgeti(L, -1, i + 1); + if (!lua_isstring(L, -1)) + return luaL_error(L, "Expected all values in argument table to be a string. Index %d was a %s", i, lua_typename(L, lua_type(L, -1))); + cmd.append(" ").append(lua_tostring(L, -1)); + } + + FILE* f = popen(cmd.c_str(), "r"); + char buf[64]; + std::string result; + while (fgets(buf, sizeof(buf), f) != NULL) + result.append(buf); + + lua_pushinteger(L, pclose(f)); + lua_pushlstring(L, result.c_str(), result.size()); + return 2; +} + + +struct command_stack +{ + std::string base_cmd; + dynarray> commands; + std::recursive_mutex mux; + + size_t num_cmds(void) const noexcept + { return commands.size(); } + bool is_empty(void) const noexcept + { return commands.size() == 0; } + std::pair, size_t> get_command(void) noexcept + { + std::scoped_lock lock(mux); + const size_t sz = commands.size(); + return std::make_pair(std::move(commands.pop_back()),sz-1); + } +}; +struct result_stack +{ + struct result + { + std::string program_output; + int program_ret; + }; + + std::recursive_mutex mux; + dynarray results; + + void add_result(const size_t idx, const std::string& out, const int ret) + { + std::scoped_lock lock(mux); + new (results.data() + idx) result(out, ret); + } +}; + + +void worker_thread(std::stop_token token, const std::string& base_cmd, command_stack& in, result_stack& out) +{ + char buf[64]; + while (!token.stop_requested()) + { + std::string cmd = base_cmd; + if (in.is_empty()) return; + auto [arg, idx] = in.get_command(); + // Get a command + + for (const std::string& a : arg) + cmd.append(" ").append(a); + + // Run the command + FILE* f = popen(cmd.c_str(), "r"); + + // Capture the commands stdout + std::string pout = ""; + memset(buf, '\0', sizeof(buf)); + while (fgets(buf, sizeof(buf), f) != NULL) + { + using namespace std::chrono_literals; + std::this_thread::sleep_for(10us); + pout.append(buf); + } + + // Await the program to exit + int rc = pclose(f); + out.add_result(idx, pout, rc); + } +} + +int lua_platform_exec_parallel(lua_State* L) +{ + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "Expected first argument to be a string representing a command"); + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "Expected second argument to be an integer representing the maximum number of parallel jobs to start"); + luaL_argcheck(L, lua_type(L, 3) == LUA_TTABLE, 3, "Expected third argument to be a table of table of strings representing the arguments"); + std::string base_cmd(lua_tostring(L, 1)); + const size_t max_parallel = lua_tointeger(L, 2); + const size_t num_cmds = lua_objlen(L, 3); + + command_stack in; + in.base_cmd = base_cmd; + result_stack out; + for (size_t i = 0; i < num_cmds; ++i) + out.results.push_back(result_stack::result{"Not Executed",-1}); + + // Generate the command stack from lua arguments + for (size_t i = 0; i < num_cmds; ++i) + { + lua_rawgeti(L, 3, i + 1); + if (lua_istable(L, -1)) + { + const size_t num_args = lua_objlen(L, -1); + in.commands.push_back(dynarray()); + dynarray& back = in.commands.back(); + for (size_t j = 0; j < num_args; ++j) + { + lua_rawgeti(L, -1, j + 1); + if (!lua_isstring(L, -1)) + return luaL_error(L, "Expected program argument to be a string. args[%d][%d] was a %s, not a string!\n", i, j, lua_typename(L, lua_type(L, -1))); + std::string str(lua_tostring(L, -1), lua_objlen(L, -1)); + back.push_back(std::move(str)); + lua_pop(L, 1); + } + } + else if (lua_type(L, -1) == LUA_TSTRING) + { + in.commands.push_back(dynarray()); + in.commands.back().push_back(lua_tostring(L, -1)); + } + else + return luaL_error(L, "Expected a table of program arguments. args[%d] was a %s, not a table!\n", i, lua_typename(L, lua_type(L, -1))); + lua_pop(L, 1); + } + + // Dispatch worker threads + std::vector workers; + for (size_t i = 0; i < max_parallel; ++i) + workers.push_back(std::jthread(worker_thread, + std::ref(base_cmd), + std::ref(in), + std::ref(out) + )); + + using namespace std::chrono_literals; + while (!in.is_empty()) + std::this_thread::sleep_for(1ms); + + for (size_t i = 0; i < workers.size(); ++i) + workers[i].request_stop(); + for (size_t i = 0; i < workers.size(); ++i) + workers[i].join(); + + // Join thread results together and push them to lua stack + lua_newtable(L); // rc + lua_newtable(L); // stdout + for (size_t i = 0; i < out.results.size(); ++i) + { + const result_stack::result& x = out.results[i]; + lua_pushinteger(L, x.program_ret); + lua_rawseti(L, -3, i+1); + lua_pushlstring(L, x.program_output.c_str(), x.program_output.size()); + lua_rawseti(L, -2, i + 1); + } + + return 2; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f0bf15f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,116 @@ +#include "lua_dependency_tree.h" +#include "lua_platform.h" +#include "lua_filesystem.h" +#include +#include + +// From lua +static int msghandler(lua_State* L) { + if (!lua_isstring(L, 1)) /* 'message' not a string? */ + return 1; /* keep it intact */ + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback */ + return 1; +} + +int main(int argc, char** argv) +{ + const char* exec_file = "lbs.lua"; + int argstart = 1; + if (argc > 2 && ( + strcmp(argv[1], "--buildscript") == 0 || + strcmp(argv[1], "-s") == 0)) + { + exec_file = argv[2]; + argstart += 2; + } + if (!std::filesystem::exists(exec_file)) + { + fprintf(stderr, "Failed to find buildscript %s to execute\n", exec_file); + return EXIT_FAILURE; + } + + lua_State* L = luaL_newstate(); + luaL_openlibs(L); + luaopen_dependency_tree(L); + luaopen_platform(L); + luaopen_filesystem(L); + + + luaL_loadfile(L, exec_file); + for (int i = (argstart+1); i < argc; ++i) + lua_pushstring(L, argv[i]); + int err = LUA_OK; + int msg_base = lua_gettop(L) - argc - argstart - 1; + lua_pushcfunction(L, msghandler); + lua_insert(L, msg_base); + if ( (err = lua_pcall(L, argc - argstart - 1, LUA_MULTRET, msg_base)) != LUA_OK) + { + if (err == LUA_ERRRUN) + fprintf(stderr, "A runtime error when running the script %s has occured\n%s\n", exec_file, lua_tostring(L, -1)); + else if (err == LUA_ERRMEM) + fprintf(stderr, "A memory allocation error when running the script %s has occured\n%s\n", exec_file, lua_tostring(L, -1)); + else if (err == LUA_ERRERR) + fprintf(stderr, "A error handling function has errored when running the script %s\n%s\n", exec_file, lua_tostring(L, -1)); + else + fprintf(stderr, "An unknown error has occured when running the script %s\n%s\n", exec_file, lua_tostring(L, -1)); + lua_close(L); + return EXIT_FAILURE; + } + lua_remove(L, msg_base); + + + const char* lbs_cmd = argv[argstart]; + if (argstart >= argc) + lbs_cmd = "main"; + lua_getglobal(L, lbs_cmd); + if (lua_isnil(L, -1)) + { + fprintf(stderr, "Failed to find command %s in buildscript %s to execute\n", lbs_cmd, exec_file); + lua_close(L); + return EXIT_FAILURE; + } + for (int i = (argstart + 1); i < argc; ++i) + lua_pushstring(L, argv[i]); + + msg_base = lua_gettop(L) - argc - argstart - 1; + lua_pushcfunction(L, msghandler); + lua_insert(L, msg_base); + if ( (err = lua_pcall(L, argc - argstart - 1, LUA_MULTRET, msg_base)) != LUA_OK) + { + if (err == LUA_ERRRUN) + fprintf(stderr, "A runtime error when running the command %s in the script %s has occured\n%s\n", lbs_cmd, exec_file, lua_tostring(L, -1)); + else if (err == LUA_ERRMEM) + fprintf(stderr, "A memory allocation error when running the command %s in the script %s has occured\n%s\n", lbs_cmd, exec_file, lua_tostring(L, -1)); + else if (err == LUA_ERRERR) + fprintf(stderr, "A error handling function has errored when running the command %s in the script %s\n%s\n", lbs_cmd, exec_file, lua_tostring(L, -1)); + else + fprintf(stderr, "An unknown error has occured when running the command %s in the script %s\n%s\n", lbs_cmd, exec_file, lua_tostring(L, -1)); + lua_close(L); + return EXIT_FAILURE; + } + lua_remove(L, msg_base); + + // If a return value was provided, return it + if (lua_isnumber(L, -1)) + { + int rc = lua_tointeger(L, -1); + lua_close(L); + return rc; + } + + // Otherwise return success + lua_close(L); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/simple_string.cpp b/src/simple_string.cpp new file mode 100644 index 0000000..96c1e77 --- /dev/null +++ b/src/simple_string.cpp @@ -0,0 +1,216 @@ +#include "simple_string.h" + +simple_string::simple_string(const simple_string& src) +{ + m_data = (char*)malloc(src.m_size * sizeof(char)); + m_size = src.m_size; + m_capacity = src.m_capacity; + memcpy(m_data, src.m_data, src.m_size * sizeof(char)); +} + +simple_string& simple_string::operator=(const simple_string& src) +{ + if (this == &src) return *this; + this->~simple_string(); + m_data = (char*)malloc(src.m_size * sizeof(char)); + m_size = src.m_size; + m_capacity = src.m_capacity; + memcpy(m_data, src.m_data, src.m_size * sizeof(char)); + return *this; +} + +simple_string::simple_string(simple_string&& src) noexcept +{ + m_data = src.m_data; + m_size = src.m_size; + m_capacity = src.m_capacity; + src.m_data = nullptr; + src.m_size = 0; + src.m_capacity = 0; +} + +simple_string& simple_string::operator=(simple_string&& src) noexcept +{ + if (this == &src) return *this; + this->~simple_string(); + m_data = src.m_data; + m_size = src.m_size; + m_capacity = src.m_capacity; + src.m_data = nullptr; + src.m_size = 0; + src.m_capacity = 0; + return *this; +} + +simple_string::simple_string(const char* beg, const char* end) +{ + const ptrdiff_t slen = (ptrdiff_t)(end - beg); + m_size = slen; + m_capacity = slen; + m_data = (char*)malloc(slen*sizeof(char)+1); + memcpy(m_data, beg, slen * sizeof(char)); + m_data[slen - 1] = '\0'; +} + +simple_string::simple_string(const char* str, const size_t slen) +{ + m_size = slen; + m_capacity = slen; + m_data = (char*)malloc(slen * sizeof(char)+1); + memcpy(m_data, str, slen * sizeof(char)); + m_data[slen - 1] = '\0'; +} + +simple_string::simple_string(const char* str) +{ + size_t slen = strlen(str); + m_size = slen; + m_capacity = slen; + m_data = (char*)malloc(slen * sizeof(char)+1); + memcpy(m_data, str, slen * sizeof(char)); + m_data[slen - 1] = '\0'; +} + +simple_string::simple_string(const size_t initial_capacity, const char fill_value) +{ + m_size = 0; + m_capacity = initial_capacity; + m_data = (char*)malloc(initial_capacity * sizeof(char)); + memset(m_data, fill_value, m_capacity); +} + +simple_string::simple_string(const std::string& str) +{ + m_capacity = str.size(); + m_size = str.size(); + m_data = (char*)malloc(m_size*sizeof(char)+1); + memcpy(m_data, str.data(), m_size*sizeof(char)); + m_data[m_size - 1] = '\0'; +} + +simple_string::simple_string() +{ + m_data = nullptr; + m_capacity = 0; + m_size = 0; +} + +simple_string::~simple_string(void) +{ + if (m_data != nullptr) + free(m_data); +} + +simple_string& simple_string::append(const char* other) +{ + const size_t slen = strlen(other); + if (slen == 0) return *this; + if (m_size + slen + 1 > m_capacity) + this->resize(m_size + slen); + memcpy(m_data + m_size, other, slen * sizeof(char)); + m_size += slen; + m_data[m_size] = '\0'; + return *this; +} + +simple_string& simple_string::append(const char* beg, const char* end) +{ + const ptrdiff_t slen = (ptrdiff_t)(end - beg); + if (slen == 0) return *this; + if (m_size + slen + 1 > m_capacity) + this->resize(m_size + slen); + memcpy(m_data + m_size, beg, slen * sizeof(char)); + m_size += slen; + m_data[m_size] = '\0'; + return *this; +} + +simple_string& simple_string::append(const char* other, const size_t slen) +{ + if (slen == 0) return *this; + if (m_size + slen + 1> m_capacity) + this->resize(m_size + slen); + memcpy(m_data + m_size, other, slen * sizeof(char)); + m_size += slen; + m_data[m_size] = '\0'; + return *this; +} + +simple_string& simple_string::append(const std::string& other) +{ + if (other.size() == 0) return *this; + if (m_size + other.size() > m_capacity) + this->resize(m_size + other.size()); + memcpy(m_data + m_size, other.data(), other.size() * sizeof(char)); + m_size += other.size(); + return *this; +} + +simple_string& simple_string::append(const simple_string& other) +{ + if (other.m_size == 0) return *this; + if (m_size + other.m_size > m_capacity) + this->resize(m_size + other.m_size); + memcpy(m_data + m_size, other.m_data, other.m_size * sizeof(char)); + m_size += other.m_size; + return *this; +} + +simple_string& simple_string::append(const char x) +{ + if (m_size + 1 > m_capacity) + this->expand(std::max(m_capacity, 1ULL)); + m_data[m_size] = x; + ++m_size; + return *this; +} + +void simple_string::resize(const size_t new_size) +{ + if (new_size >= m_capacity) + this->expand(std::max(new_size, m_capacity)); + m_size = new_size; +} + +void simple_string::expand(const size_t extra_capacity) +{ this->reserve(m_capacity + extra_capacity); } + +void simple_string::reserve(const size_t new_capacity) +{ + if (new_capacity <= m_capacity) return; + char* new_data = (char*)malloc(new_capacity*sizeof(char)); + if (m_capacity != 0 && m_data != nullptr) + { + memcpy(new_data, m_data, m_size * sizeof(char)); + free(m_data); + } + m_data = new_data; + m_capacity = new_capacity; +} + +const char* simple_string::c_str(void) const noexcept +{ return m_data; } + +char* simple_string::data(void) noexcept +{ return m_data; } + +size_t simple_string::size(void) const noexcept +{ return m_size; } + +char* simple_string::begin(void) noexcept +{ return m_data; } + +char* simple_string::end(void) noexcept +{ return m_data + m_size; } + +const char* simple_string::cbegin(void) const noexcept +{ return m_data; } + +const char* simple_string::cend(void) const noexcept +{ return m_data; } + +char& simple_string::operator[](const size_t idx) noexcept +{ return m_data[idx]; } + +const char& simple_string::operator[](const size_t idx) const noexcept +{ return m_data[idx]; } diff --git a/thirdparty/luajit b/thirdparty/luajit new file mode 160000 index 0000000..a4f56a4 --- /dev/null +++ b/thirdparty/luajit @@ -0,0 +1 @@ +Subproject commit a4f56a459a588ae768801074b46ba0adcfb49eb1