diff --git a/src/dynamic_library/DynamicLibraryLoader.cpp b/src/dynamic_library/DynamicLibraryLoader.cpp new file mode 100644 index 0000000..792c046 --- /dev/null +++ b/src/dynamic_library/DynamicLibraryLoader.cpp @@ -0,0 +1,127 @@ +/** + * @file DynamicLibraryLoader.cpp + * @author Edward Palmer + * @date 2025-04-08 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "DynamicLibraryLoader.hpp" +#include "Exceptions.hpp" +#include "FloatObject.hpp" +#include "IntObject.hpp" +#include "LibraryFunctionObject.hpp" +#include "Logger.hpp" +#include "Stringify.hpp" + +/* TODO: - add GTests and move to Bazel to test operation */ + +DynamicLibraryLoader::DynamicLibraryLoader(std::string libPath, std::initializer_list funcDefinitions) +{ + /* Useful typedefs */ + typedef double (*DoubleFuncDoublePtr)(double); + typedef double (*DoubleFuncDoubleDoublePtr)(double, double); + typedef double (*DoubleFuncIntPtr)(int); + typedef int (*IntFuncIntPtr)(int); + typedef int (*IntFuncIntIntPtr)(int, int); + + _closures.clear(); + _closures.reserve(funcDefinitions.size()); + + _handle = dlopen(libPath.c_str(), RTLD_LAZY); + if (!_handle) + { + ThrowException("failed to load shared library with path: " + libPath + " with error: " + eucleia::stringify(dlerror())); + } + + for (auto &[funcName, funcSignature] : funcDefinitions) + { + void *ptr = dlsym(_handle, funcName.c_str()); + if (!ptr) + { + ThrowException("failed to load function with name " + funcName); + } + + /* Create each closure */ + auto closure = [ptr, funcSignature](ProgramNode &callArgs, Scope &scope) -> BaseObject * + { + switch (funcSignature) + { + case DoubleFuncDouble: + { + auto func = reinterpret_cast(ptr); + + double returnValue = func(callArgs[0]->evaluate(scope)->castObject().value); + + return scope.createManagedObject(returnValue); + } + case DoubleFuncDoubleDouble: + { + auto func = reinterpret_cast(ptr); + + double returnValue = func(callArgs[0]->evaluate(scope)->castObject().value, + callArgs[1]->evaluate(scope)->castObject().value); + + return scope.createManagedObject(returnValue); + } + case DoubleFuncInt: + { + auto func = reinterpret_cast(ptr); + + double returnValue = func(callArgs[0]->evaluate(scope)->castObject().value); + + return scope.createManagedObject(returnValue); + } + case IntFuncInt: + { + auto func = reinterpret_cast(ptr); + + int returnValue = func(callArgs[0]->evaluate(scope)->castObject().value); + + return scope.createManagedObject(returnValue); + } + case IntFuncIntInt: + { + auto func = reinterpret_cast(ptr); + + int returnValue = func(callArgs[0]->evaluate(scope)->castObject().value, + callArgs[1]->evaluate(scope)->castObject().value); + + return scope.createManagedObject(returnValue); + } + default: + { + ThrowException("unsupported function signature"); + } + } + }; + + /* Store closures for when evaluate() is called */ + _closures.emplace_back(funcName, closure); + } +} + + +BaseObject *DynamicLibraryLoader::evaluate(Scope &scope) +{ + for (const auto &[name, closure] : _closures) + { + Logger::debug("adding dynamic function " + name + " to scope"); + LibraryFunctionObject *object = scope.createManagedObject(closure); + scope.linkObject(name, object); + } + + return nullptr; +} + + +DynamicLibraryLoader::~DynamicLibraryLoader() +{ + if (!_handle) + { + return; + } + + dlclose(_handle); +} diff --git a/src/dynamic_library/DynamicLibraryLoader.hpp b/src/dynamic_library/DynamicLibraryLoader.hpp new file mode 100644 index 0000000..f273108 --- /dev/null +++ b/src/dynamic_library/DynamicLibraryLoader.hpp @@ -0,0 +1,55 @@ +/** + * @file DynamicLibraryLoader.hpp + * @author Edward Palmer + * @date 2025-04-08 + * + * @copyright Copyright (c) 2025 + * + */ + +#pragma once +#include "BaseObject.hpp" +#include "ProgramNode.hpp" +#include "Scope.hpp" +#include +#include +#include +#include + +/** + * TODO: - an improvement idea: instead of having to add all functions to the scope, would be better to instead call + * something like [module].func and then we'd realize that it's a function call. + */ + + +class DynamicLibraryLoader : public BaseNode +{ +public: + using FuncName = std::string; + using Function = std::function; + + + enum FuncSignature + { + DoubleFuncDouble, + DoubleFuncDoubleDouble, + DoubleFuncInt, + IntFuncInt, + IntFuncIntInt + }; + + using FuncDefinition = std::pair; + + DynamicLibraryLoader() = delete; + + DynamicLibraryLoader(std::string libPath, std::initializer_list funcDefinitions); + ~DynamicLibraryLoader() override; + + /* Add all functions to the scope this is evaluated in */ + BaseObject *evaluate(Scope &scope) final; + +protected: + std::vector> _closures; + + void *_handle{nullptr}; +}; diff --git a/src/parser/EucleiaParser.cpp b/src/parser/EucleiaParser.cpp index 159c3e0..9626ef8 100644 --- a/src/parser/EucleiaParser.cpp +++ b/src/parser/EucleiaParser.cpp @@ -6,6 +6,7 @@ // #include "EucleiaParser.hpp" +#include "DynamicLibraryLoader.hpp" #include "EucleiaModules.hpp" #include "Exceptions.hpp" #include "Grammar.hpp" @@ -115,7 +116,7 @@ FileNode *Parser::parseFileImport() /// This is for importing functions from a stdlib as opposed to user-defined functions /// into this scope. -ModuleNode *Parser::parseLibraryImport() +BaseNode *Parser::parseLibraryImport() { skipOperator("<"); @@ -884,7 +885,8 @@ void Parser::skipSemicolonLineEndingIfRequired(const BaseNode &node) node.isNodeType() || node.isNodeType() || node.isNodeType() || - node.isNodeType()); + node.isNodeType() || + node.isNodeType()); if (!doSkipPunctuation) skipPunctuation(";"); diff --git a/src/parser/EucleiaParser.hpp b/src/parser/EucleiaParser.hpp index 462f266..2cdde44 100644 --- a/src/parser/EucleiaParser.hpp +++ b/src/parser/EucleiaParser.hpp @@ -7,9 +7,9 @@ #pragma once #include "EucleiaModules.hpp" -#include "Tokenizer.hpp" #include "FileInfoRec.hpp" #include "Nodes.hpp" +#include "Tokenizer.hpp" #include @@ -58,7 +58,7 @@ class Parser PrefixDecrementNode *parsePrefixDecrement(); NegationNode *parseNegation(); - ModuleNode *parseLibraryImport(); + BaseNode *parseLibraryImport(); FileNode *parseFileImport(); BaseNode *parseImport();