diff --git a/ExtendingElectra.md b/ExtendingElectra.md new file mode 100644 index 0000000..6eb324d --- /dev/null +++ b/ExtendingElectra.md @@ -0,0 +1,66 @@ +## Extending Electra Using C++ +You can add your own components to electra using C++ shared libraries. +When the electra interpreter loads a shared library, it looks for two functions: +- `void load(ComponentInformation&)` +- `bool work(std::vector>&, Current::Ptr, std::vector&)` + +#### Load Function +The purpose of the `load` function is simple; It takes a reference to a ComponentInformation +object and modifies it. Let's look at an example: +```cpp +void load(ComponentInformation& c) +{ + c.symbol = U'9'; + c.directions = {Direction::EAST, Direction::WEST}; + c.componentType = ComponentInformation::ComponentType::CLONING; +} +``` + +- The `symbol` field is the unicode character representation of the component. It overwrites +any component with the same symbol. +- The `directions` field is a list of supported directions. +- The `componentType` field specifies the component's type. The `componentType` can take +two values: `ComponentInformation::ComponentType::CLONING` or `ComponentInformation::ComponentType::NON_CLONING`. +`NON_CLONING` components do not clone the current after they've done their job like portals. +And as you may guess, `CLONING` components do clone the current after they've done their job like any other component in electra. + +#### Work Function +The `work` function is the function specifies the actual job of the component. It has three parameters +- The first parameter is a reference to the memory of electra. +- The second parameter is a shared pointer to the current that triggered the component +- The third parameter is a vector of currents, if you want to create new currents, you can do so +by pushing new currents to this vector. + +If the `work` function returns false, the current gets killed and no cloning occurs. +(Note: If you manually push some currents to the last parameter, they'll still be created) + +### Full Example +Let's look at an example to better understand how you can create a custom component: +```cpp +#include "ComponentInformation.hpp" +#include "Current.hpp" +#include "Direction.hpp" +#include +//g++ helloworld.cpp -fPIC -shared -o libhelloworld.so + +extern "C" +{ + +// We create a custom component. It supports west and east directions and '5' is its symbol. +// It also clones currents after it's done its work +void load(ComponentInformation& c) +{ + c.symbol = U'5'; + c.directions = {Direction::EAST, Direction::WEST}; + c.componentType = ComponentInformation::ComponentType::CLONING; +} + +// The component just prints hello world to the screen +bool work(std::vector>& stacks, Current::Ptr currentPtr, std::vector& currentVector) +{ + std::cout << "hello world\n"; + return true; +} + +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 17a1e8f..b3b49e6 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ cmake --build . --config="Release" Electra has three main components: Currents, Generators and Components. Electra uses a list of 64 stacks of doubles for its memory management. (This implementation has a command line argument that lets you change the stack count) -## **Comments and Including Other Files** +## **Comments, Including Other Files and Extending Electra With C++** In Electra, you can comment out your code using question marks. ``` ? This is a comment ? >-+ @@ -86,6 +86,10 @@ To include other files in your code, use quotation marks. "!foo.ec" 5:12 ? You can always do a force include ? ``` +But be careful, files ending with `.dll`, `.so` or `.dylib` will be treated as dynamic components +(depends on your platform, on windows only the `.dll` files will be treated as dynamic components). +Dynamic components allow you to extend electra using C++. For more info, click [here](ExtendingElectra.md). + ## **Currents** Currents are instruction pointers in Electra. They all have a direction, a position, a stack that holds visited portals and a stack pointer. A direction can take one of these eight values: East, Northeast, North, Northwest, West, Southwest, South, Southeast. diff --git a/examples/HQ9Plus_interpreter/99bottles.cpp b/examples/HQ9Plus_interpreter/99bottles.cpp index c37c6cf..411e614 100644 --- a/examples/HQ9Plus_interpreter/99bottles.cpp +++ b/examples/HQ9Plus_interpreter/99bottles.cpp @@ -14,7 +14,7 @@ void load(ComponentInformation& c) c.componentType = ComponentInformation::ComponentType::CLONING; } -bool work(Current::Ptr currentPtr, std::vector& currentVector) +bool work(std::vector>& stacks, Current::Ptr currentPtr, std::vector& currentVector) { for(int i = 99; i > 1; --i) diff --git a/examples/HQ9Plus_interpreter/helloworld.cpp b/examples/HQ9Plus_interpreter/helloworld.cpp index b535873..91ebd9b 100644 --- a/examples/HQ9Plus_interpreter/helloworld.cpp +++ b/examples/HQ9Plus_interpreter/helloworld.cpp @@ -14,7 +14,7 @@ void load(ComponentInformation& c) c.componentType = ComponentInformation::ComponentType::CLONING; } -bool work(Current::Ptr currentPtr, std::vector& currentVector) +bool work(std::vector>& stacks, Current::Ptr currentPtr, std::vector& currentVector) { std::cout << "hello world\n"; return true; diff --git a/examples/HQ9Plus_interpreter/hq9plus.ec b/examples/HQ9Plus_interpreter/hq9plus.ec index c391c35..c3deed0 100644 --- a/examples/HQ9Plus_interpreter/hq9plus.ec +++ b/examples/HQ9Plus_interpreter/hq9plus.ec @@ -2,7 +2,7 @@ "libhelloworld.so" ? Uses 4 stacks ? -? First three are for source code, the last one is for the accumulator ? +? First three are for the source code, the last one is for the accumulator ? +-->---+ +------+ | & | | diff --git a/src/Electra.cpp b/src/Electra.cpp index dce825b..9e5a6ef 100644 --- a/src/Electra.cpp +++ b/src/Electra.cpp @@ -581,7 +581,8 @@ void Electra::loadDynamicComponent(const fs::path& path, const std::string& file dylib lib(path.string(), filename, dylib::no_filename_decorations); ComponentInformation componentInformation; lib.get_function("load")(componentInformation); - auto workFunc = lib.get_function&)>("work"); + auto workFuncWithStacksParam = lib.get_function>&, Current::Ptr, std::vector&)>("work"); + auto workFunc = std::bind(workFuncWithStacksParam, std::ref(m_stacks), std::placeholders::_1, std::placeholders::_2); m_dynamicLibraries.push_back(std::move(lib)); if(componentInformation.componentType == ComponentInformation::ComponentType::NON_CLONING) @@ -606,7 +607,8 @@ bool Electra::loadDynamicComponent(const std::string& filename) dylib lib(filename, dylib::no_filename_decorations); ComponentInformation componentInformation; lib.get_function("load")(componentInformation); - auto workFunc = lib.get_function&)>("work"); + auto workFuncWithStacksParam = lib.get_function>&, Current::Ptr, std::vector&)>("work"); + auto workFunc = std::bind(workFuncWithStacksParam, std::ref(m_stacks), std::placeholders::_1, std::placeholders::_2); m_dynamicLibraries.push_back(std::move(lib)); if(componentInformation.componentType == ComponentInformation::ComponentType::NON_CLONING)