Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/dynamic_library/DynamicLibraryLoader.cpp
Original file line number Diff line number Diff line change
@@ -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<FuncDefinition> 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<DoubleFuncDoublePtr>(ptr);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we create a template class where T = some function type to handle this better?


double returnValue = func(callArgs[0]->evaluate(scope)->castObject<FloatObject>().value);

return scope.createManagedObject<FloatObject>(returnValue);
}
case DoubleFuncDoubleDouble:
{
auto func = reinterpret_cast<DoubleFuncDoubleDoublePtr>(ptr);

double returnValue = func(callArgs[0]->evaluate(scope)->castObject<FloatObject>().value,
callArgs[1]->evaluate(scope)->castObject<FloatObject>().value);

return scope.createManagedObject<FloatObject>(returnValue);
}
case DoubleFuncInt:
{
auto func = reinterpret_cast<DoubleFuncIntPtr>(ptr);

double returnValue = func(callArgs[0]->evaluate(scope)->castObject<IntObject>().value);

return scope.createManagedObject<FloatObject>(returnValue);
}
case IntFuncInt:
{
auto func = reinterpret_cast<IntFuncIntPtr>(ptr);

int returnValue = func(callArgs[0]->evaluate(scope)->castObject<IntObject>().value);

return scope.createManagedObject<IntObject>(returnValue);
}
case IntFuncIntInt:
{
auto func = reinterpret_cast<IntFuncIntIntPtr>(ptr);

int returnValue = func(callArgs[0]->evaluate(scope)->castObject<IntObject>().value,
callArgs[1]->evaluate(scope)->castObject<IntObject>().value);

return scope.createManagedObject<IntObject>(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<LibraryFunctionObject>(closure);
scope.linkObject(name, object);
}

return nullptr;
}


DynamicLibraryLoader::~DynamicLibraryLoader()
{
if (!_handle)
{
return;
}

dlclose(_handle);
}
55 changes: 55 additions & 0 deletions src/dynamic_library/DynamicLibraryLoader.hpp
Original file line number Diff line number Diff line change
@@ -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 <dlfcn.h>
#include <functional>
#include <string>
#include <vector>

/**
* 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<BaseObject *(ProgramNode &callArgs, Scope &scope)>;


enum FuncSignature
{
DoubleFuncDouble,
DoubleFuncDoubleDouble,
DoubleFuncInt,
IntFuncInt,
IntFuncIntInt
};

using FuncDefinition = std::pair<FuncName, FuncSignature>;

DynamicLibraryLoader() = delete;

DynamicLibraryLoader(std::string libPath, std::initializer_list<FuncDefinition> funcDefinitions);
~DynamicLibraryLoader() override;

/* Add all functions to the scope this is evaluated in */
BaseObject *evaluate(Scope &scope) final;

protected:
std::vector<std::pair<FuncName, Function>> _closures;

void *_handle{nullptr};
};
6 changes: 4 additions & 2 deletions src/parser/EucleiaParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

#include "EucleiaParser.hpp"
#include "DynamicLibraryLoader.hpp"
#include "EucleiaModules.hpp"
#include "Exceptions.hpp"
#include "Grammar.hpp"
Expand Down Expand Up @@ -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("<");

Expand Down Expand Up @@ -884,7 +885,8 @@ void Parser::skipSemicolonLineEndingIfRequired(const BaseNode &node)
node.isNodeType<WhileNode>() ||
node.isNodeType<DoWhileNode>() ||
node.isNodeType<ForLoopNode>() ||
node.isNodeType<FunctionNode>());
node.isNodeType<FunctionNode>() ||
node.isNodeType<DynamicLibraryLoader>());

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create some custom way for user to load dynamic libs:

i.e. import "some path" as dylib

dylib.someFunction(arg1, arg2, ...)

-> lookup in dylib for "someFunction" and attempt to perform cast based on arg types and expected return type


if (!doSkipPunctuation)
skipPunctuation(";");
Expand Down
4 changes: 2 additions & 2 deletions src/parser/EucleiaParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

#pragma once
#include "EucleiaModules.hpp"
#include "Tokenizer.hpp"
#include "FileInfoRec.hpp"
#include "Nodes.hpp"
#include "Tokenizer.hpp"
#include <unordered_set>


Expand Down Expand Up @@ -58,7 +58,7 @@ class Parser
PrefixDecrementNode *parsePrefixDecrement();
NegationNode *parseNegation();

ModuleNode *parseLibraryImport();
BaseNode *parseLibraryImport();
FileNode *parseFileImport();
BaseNode *parseImport();

Expand Down