Native Plugins
Praia can load native C++ modules at runtime via loadNative(). This lets you write performance-critical code or wrap C/C++ libraries.
Scaffolding with Sand
Section titled “Scaffolding with Sand”The fastest way to start a plugin project is with the sand package manager:
sand init --pluginThis creates a ready-to-go grain with:
grain.yaml— package manifestmain.praia— entry point that loads the compiled pluginplugins/yourname.cpp— C++ plugin template with the entry point stubbed outMakefile— builds the plugin for macOS (.dylib) or Linux (.so)
Build and test:
makepraia main.praiaFrom there, add your native functions to the .cpp file and export them in main.praia.
Manual setup
Section titled “Manual setup”If you prefer to set things up by hand:
1. Write a plugin (mymodule.cpp):
#include "praia_plugin.h"
extern "C" void praia_register(PraiaMap* module) { module->entries["double"] = Value(makeNative("mymodule.double", 1, [](const std::vector<Value>& args) -> Value { if (!args[0].isNumber()) throw RuntimeError("expected a number", 0); return Value(args[0].asInt() * 2); }));}2. Build it:
make plugin SRC=mymodule.cpp OUT=mymodule.dylib # macOSmake plugin SRC=mymodule.cpp OUT=mymodule.so # Linux3. Use it in Praia:
let mod = loadNative("./mymodule")print(mod.double(21)) // 42Plugin API
Section titled “Plugin API”Entry point
Section titled “Entry point”Every plugin exports a single C function:
extern "C" void praia_register(PraiaMap* module);This receives an empty PraiaMap. Populate its entries with native functions.
Creating functions
Section titled “Creating functions”Use makeNative(name, arity, fn):
module->entries["add"] = Value(makeNative("mymod.add", 2, [](const std::vector<Value>& args) -> Value { return Value(args[0].asNumber() + args[1].asNumber()); }));name— display name for error messagesarity— number of parameters, or-1for variadicfn—std::function<Value(const std::vector<Value>&)>
The Value type
Section titled “The Value type”| Constructor | Praia type |
|---|---|
Value() | nil |
Value(true) | bool |
Value(int64_t(42)) | int |
Value(3.14) | float |
Value(std::string("hi")) | string |
Value(shared_ptr<PraiaArray>) | array |
Value(shared_ptr<PraiaMap>) | map |
Type checking and accessors:
args[0].isString() // type checkargs[0].asString() // const std::string&args[0].isNumber() // true for int or floatargs[0].asNumber() // double (converts int)args[0].isInt() // true only for intargs[0].asInt() // int64_targs[0].isArray() // true for arrayargs[0].asArray() // shared_ptr<PraiaArray>args[0].isMap() // true for mapargs[0].asMap() // shared_ptr<PraiaMap>Creating arrays and maps
Section titled “Creating arrays and maps”Use gcNew<T>() to create GC-tracked containers:
auto arr = gcNew<PraiaArray>();arr->elements.push_back(Value(1));arr->elements.push_back(Value(2));return Value(arr);
auto map = gcNew<PraiaMap>();map->entries["key"] = Value("value");return Value(map);Always use gcNew instead of std::make_shared — it registers the object with Praia’s garbage collector.
Error handling
Section titled “Error handling”Throw RuntimeError to report errors:
if (!args[0].isString()) throw RuntimeError("myFunc() requires a string", 0);Building
Section titled “Building”The Makefile provides a convenience target:
make plugin SRC=path/to/plugin.cpp OUT=path/to/plugin.dylibOr build manually:
# macOSg++ -std=c++17 -shared -fPIC -I$(praia --include-path) -undefined dynamic_lookup -o myplugin.dylib myplugin.cpp
# Linuxg++ -std=c++17 -shared -fPIC -I$(praia --include-path) -o myplugin.so myplugin.cppHeader
Section titled “Header”Include a single header:
#include "praia_plugin.h"This re-exports: value.h (Value, PraiaArray, PraiaMap, RuntimeError), gc_heap.h (gcNew), builtins.h (makeNative).
Behavior
Section titled “Behavior”- Extension auto-detection —
loadNative("./mymod")tries.dylibon macOS,.soon Linux - Caching — loading the same path twice returns the cached module
- Lifetime — plugins are never unloaded; function pointers remain valid
- GC integration — containers created with
gcNewparticipate in Praia’s GC - Thread safety — plugin code runs on the interpreter’s thread