Creating modules
Before starting
You will still need to dive a bit into the documentation of the project, to know how:
- the VM API works, and what it provides
- the possibilities of the Value type (comparisons, creations)
- how to use the
UserType
Also, read the guidelines about module error handling to use the same conventions as the other modules, and the guidelines about naming conventions in ArkScript (specifically see the Modules (C++) section).
Creating a new module
In your ArkScript-lang/modules fork, run the Python script as follows python3 shell/createmodules/create.py module_name
.
This will create a new folder module_name/
for you, alongside a few folders and files needed to get you started.
Create a Main.cpp
file in module_name/src/
with the following content:
#include <Ark/Module.hpp>
Ark::Value foo(std::vector<Ark::Value>& n [[maybe_unused]], Ark::VM* vm [[maybe_unused]])
{
return Ark::Value(1);
}
ARK_API Ark::mapping* getFunctionsMapping()
{
static Ark::mapping map[] = {
{ "test:foo", foo },
// sentinel
{ nullptr, nullptr }
};
return map;
}
Let's walk through this line by line:
#include <Ark/Module.hpp>
includes basic files from ArkScript to be able to use the VM, instantiates values, and generate the entry point of the moduleArk::Value foo(std::vector<Ark::Value>& n [[maybe_unused
], Ark::VM* vm [[maybe_unused]]) {...}] defines a function for our module, taking an argument list from the VM, and a non-owning pointer to the VMARK_API Ark::mapping* getFunctionsMapping()
declares the entrypoint of our modulestatic Ark::mapping map[
= {...};] creates a mapping of elements to hold the name -> function pointer association, defining the module- closing braces are stacked together, and never preceded by a newline
- note that the given name is
"test:foo"
: this is a convention in ArkScript, every module's function must be prefixed by the module name
- note that the given name is
{ nullptr, nullptr }
: a sentinel so that the virtual machine where the end of mapping is
Building your module
Clone ArkScript wherever you like. Then, you will need to update your CMakeLists.txt to add the following code:
add_subdirectory(path/to/arkscript/ Ark)
Then, run cmake . -Bbuild
, and build your module with cmake --build build
. It should output a .arkm
file in the current working directory.
Troubleshooting
Storing values in a C++ module
Lets say you are making a module to handle a window (to draw on it). You will open it with the API for your system, for example the WinAPI, and get an handle to it. Now you want to be able to modify this window in ArkScript, the solution is simple: creating an UserType holding your handle, and then getting this user type back in your functions and playing with the handle.
If you try this as is, it won't work. Or at least, it won't work for more than a function call, because the UserType doesn't become the owner of the handle, it only holds a view (observer pointer) to your resource. That means your resource must continue to live on its own in your module. Because it's a dynamic library, making a global and storing your handle in it will be complicated and in a lot of cases it won't work at all.
Here is the trick:
// will always return the same handle once its created
Handle& get_me_a_window_handle()
{
static Handle handle = WinApi_Do_Complex_Stuff(12);
// ...
return handle;
}
Ark::UserType::ControlFuncs* get_cfs_window()
{
static Ark::UserType::ControlFuncs cfs;
cfs.ostream_func = [](std::ostream& os, const Ark::UserType& a) -> std::ostream& {
// do stuff
return os;
};
cfs.deleter = [](void* data) {
// do stuff
};
return &cfs;
}
Ark::Value create_window_handle([[maybe_unused]] std::vector<Ark::Value>& args, [[maybe_unused]] Ark::VM* vm)
{
Handle& handle = get_me_a_window_handle();
return Ark::Value(
Ark::UserType(&handle, get_cfs_window())
);
}
There is a lot of things to unpack here.
First, we have a function returning a reference to a static object, which will get initialized only once, even if we call the function a thousand times. Great, we solved the lifetime problem!
Then, we have a get_cfs_window
functions. cfs is the abbreviation for control functions in ArkScript, they are designed as a shared block of function pointers
to handle an object in ArkScript (how to display it on the screen, how to delete it once the memory needs to be fred...)
Finally, we have our C++ function which will be bind to ArkScript, creating/receiving the window handle and returning an UserType with the control functions block.