From e56e6de9118edd1d9a60f13ddf7be36c7cd9a42d Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Mon, 25 Aug 2025 17:35:16 +1000 Subject: [PATCH] Experimental external library support --- docs/syntax.md | 6 +- src/main.cpp | 213 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 216 insertions(+), 3 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index 4a00b0d..f1e88ba 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -282,10 +282,12 @@ Note: Ground will check the directory where the program is being run from when t Usage: `use $stringvalue` -#### extern (WORK IN PROGRESS) +#### extern (Experimental, please report bugs!) Attempts to import a shared object library written for Ground. All functions in the external library will be usable with `call`. -Note: Ground will check the directory where the program is being run from when trying to find external programs. If that fails, it will check the directory set in the $GROUND_LIBS environment variable set by your system. The '.so', '.dll', etc extension is appended automatically. +Note: Ground will check the directory set in the $GROUND_LIBS environment variable set by your system. The '.so' (Linux), '.dylib' (macOS), or '.dll' (Windows) extension is appended automatically. + +Documentation on how to do external libraries coming soon. Usage: `extern $stringvalue` diff --git a/src/main.cpp b/src/main.cpp index ab1f522..d6eb46c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,6 +43,23 @@ #include #include +// Headers for external libraries +#ifdef _WIN32 + // Note: Windows support is experiemental. Maybe try using a superior + // operating system? (cough cough, Linux?) + #include + #define DLOPEN(path) LoadLibrary(path) + #define DLSYM(handle, name) GetProcAddress(handle, name) + #define DLCLOSE(handle) FreeLibrary(handle) + #define DLERROR() "Windows DLL Error" +#else + #include + #define DLOPEN(path) dlopen(path, RTLD_LAZY) + #define DLSYM(handle, name) dlsym(handle, name) + #define DLCLOSE(handle) dlclose(handle) + #define DLERROR() dlerror() +#endif + using namespace std; /* @@ -256,6 +273,26 @@ struct Function { map localLabels; }; +// C-compatible enum and types for developing libraries for Ground in C +typedef enum { + GROUND_INT, + GROUND_DOUBLE, + GROUND_BOOL, + GROUND_STRING, + GROUND_CHAR +} GroundType; + +typedef struct { + GroundType type; + union { + int int_val; + double double_val; + int bool_val; + char* string_val; + char char_val; + } data; +} GroundValue; + /* functions map Contains the code of functions and types of their values @@ -268,6 +305,58 @@ map functions; */ vector fnArgs; +// External library functions and other things + +// Handle to loaded libraries +map loadedLibraries; + +// Map of function name to function pointer +map externalFunctions; + +// Conversion functions +GroundValue literalToGroundValue(const Literal& lit) { + GroundValue gv; + if (holds_alternative(lit.val)) { + gv.type = GROUND_INT; + gv.data.int_val = get(lit.val); + } else if (holds_alternative(lit.val)) { + gv.type = GROUND_DOUBLE; + gv.data.double_val = get(lit.val); + } else if (holds_alternative(lit.val)) { + gv.type = GROUND_BOOL; + gv.data.bool_val = get(lit.val) ? 1 : 0; + } else if (holds_alternative(lit.val)) { + gv.type = GROUND_STRING; + gv.data.string_val = const_cast(get(lit.val).c_str()); + } else if (holds_alternative(lit.val)) { + gv.type = GROUND_CHAR; + gv.data.char_val = get(lit.val); + } + return gv; +} + +Literal groundValueToLiteral(const GroundValue& gv) { + Literal lit; + switch (gv.type) { + case GROUND_INT: + lit.val = gv.data.int_val; + break; + case GROUND_DOUBLE: + lit.val = gv.data.double_val; + break; + case GROUND_BOOL: + lit.val = (gv.data.bool_val != 0); + break; + case GROUND_STRING: + lit.val = string(gv.data.string_val); + break; + case GROUND_CHAR: + lit.val = gv.data.char_val; + break; + } + return lit; +} + /* error function Takes a string (which is a debug message) and prints it to the console, letting the @@ -1631,6 +1720,31 @@ Literal exec(vector in, bool executingFunction) { error("Second argument of call must be a direct reference"); } + // Check for external function + if (externalFunctions.find(ref.fnName) != externalFunctions.end()) { + // Call external function + typedef GroundValue (*ExtFunc)(GroundValue*, int); + ExtFunc extFunc = (ExtFunc)externalFunctions[ref.fnName]; + + // Convert arguments + vector gvArgs; + for (const Literal& arg : fnArgs) { + gvArgs.push_back(literalToGroundValue(arg)); + } + + // Call function + GroundValue result = extFunc(gvArgs.data(), gvArgs.size()); + + // Convert result back + Literal resultLit = groundValueToLiteral(result); + + // Clear arguments and store result + fnArgs.clear(); + variables[returnRef.varName] = resultLit; + + break; + } + // Check if function exists if (functions.find(ref.fnName) == functions.end()) { error("Function " + ref.fnName + " not found"); @@ -1710,7 +1824,104 @@ Literal exec(vector in, bool executingFunction) { Literal ret = exec(parser(lexer(in)), false); } break; case Instructions::Extern: - cout << "Still to be implemented" << endl; + if (l.args.size() < 1) { + error("Could not find all arguments for Extern inbuilt"); + } + { + string libName; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + libName = get(get(l.args[0]).val); + } else { + error("First argument for extern requires a string literal"); + } + } else { + error("First argument for extern requires a string literal"); + } + + // Add appropriate extension + string fullLibName = libName; + #ifdef _WIN32 + fullLibName += ".dll"; + #elif __APPLE__ + fullLibName += ".dylib"; + #else + fullLibName += ".so"; + #endif + + // Check multiple locations for the library + string libPath; + bool found = false; + + // Check GROUND_LIBS directory + const char* groundLibsDir = getenv("GROUND_LIBS"); + if (groundLibsDir) { + string envPath = string(groundLibsDir); + // Add trailing slash if not present + if (!envPath.empty() && envPath.back() != '/' && envPath.back() != '\\') { + envPath += "/"; + } + string fullEnvPath = envPath + fullLibName; + if (filesystem::exists(fullEnvPath)) { + libPath = fullEnvPath; + found = true; + } + } + + if (!found) { + error("Could not find external library: " + fullLibName + + " (searched current directory and GROUND_LIBS)"); + } + + // Try to open the library + void* handle = DLOPEN(libPath.c_str()); + if (!handle) { + error("Failed to load library " + libPath + ": " + string(DLERROR())); + } + + // Store handle for cleanup later + loadedLibraries[libName] = handle; + + // Get required functions + typedef const char** (*GetFunctionsFunc)(); + typedef void* (*GetFunctionFunc)(const char*); + + GetFunctionsFunc getFunctions = (GetFunctionsFunc)DLSYM(handle, "ground_get_functions"); + GetFunctionFunc getFunction = (GetFunctionFunc)DLSYM(handle, "ground_get_function"); + + if (!getFunctions || !getFunction) { + error("Library " + libName + " is not Ground-compatible (missing required functions: ground_get_functions or ground_get_function)"); + } + + // Optional initialization + typedef void (*InitFunc)(); + InitFunc initFunc = (InitFunc)DLSYM(handle, "ground_lib_init"); + if (initFunc) { + initFunc(); + } + + // Register all functions + const char** functionNames = getFunctions(); + if (!functionNames) { + error("Library " + libName + " returned null function list"); + } + + int functionCount = 0; + for (int i = 0; functionNames[i] != nullptr; i++) { + void* funcPtr = getFunction(functionNames[i]); + if (funcPtr) { + externalFunctions[string(functionNames[i])] = funcPtr; + functionCount++; + } else { + error("Failed to get function pointer for: " + string(functionNames[i])); + } + } + + if (functionCount == 0) { + error("No functions were loaded from library: " + libName); + } + } break; default: cout << "Still to be implemented" << endl;