
Custom plugins
The platform allows creating two types of plugins:
- stateless functions (monads, dyads, triads, tetrads, polyads);
- stateful reagents (async streams).
Both types are written in Rust, compiled as dynamic libraries (.dylib on macOS, .so on Linux), and loaded at runtime.
Prerequisites
Building plugins requires:
- Rust nightly toolchain
- Platform source tree — the
extcrate and its transitive dependencies are needed at compile time
The ext crate depends on several internal crates, so the full kernel source tree is required:
kernel/
kernel/
ext/ # plugin interface
ops/ # operations
vectorize/ # proc-macros (#[monad], #[dyad])
o/ffi/ # FFI types (AST, Interpreter API)
rt/ # runtime (basert, utilsrt, sync)
Stateless function plugin
Let's create a plugin that registers a custom add dyad function.
Project setup
Create a new Rust library:
cargo new --lib my_plugin
Configure Cargo.toml:
[package]
name = "my_plugin"
version = "0.1.0"
edition = "2021"
[dependencies]
ext = { path = "/kernel/ext" }
[lib]
crate-type = ["cdylib"]
ext crate path should point to kernel/ext directory inside the platform source tree. For example, if the platform source is at /home/user/theplatform , then use ext = { path = "/home/user/theplatform/kernel/ext" } . Plugin code
Create src/lib.rs:
#![feature(abi_vectorcall)]
#![feature(allocator_api)]
#![feature(c_unwind)]
extern crate ext;
use ext::*;
#[dyad]
fn add_fn(lhs: &AST, rhs: &AST, i: &Interpreter) -> AST {
match (lhs.tp(), rhs.tp()) {
(SC_LONG, SC_LONG) => {
i.new_scalar_long(lhs.long() + rhs.long())
}
_ => panic!("nyi")
}
}
declare_plugin!(
// constructor: called when plugin is loaded
|i: &Interpreter| {
let sym = i.intern_string("add");
i.insert_entity(sym, i.new_verb_dyad(add_fn));
i.new_string("add registered")
},
// destructor: called when plugin is unloaded
|| {}
);
Key elements:
#[dyad]macro marks the function as a dyadic verb (2 arguments). Use#[monad]for monadic (1 argument).declare_plugin!macro generates the plugin entry points. The constructor receives anInterpreterreference and registers functions.i.intern_string("add")creates a symbol for the function name.i.insert_entity(sym, i.new_verb_dyad(add_fn))binds the symbol to the function.
Building
Requires Rust nightly toolchain:
cargo build --release
The compiled plugin will be at target/release/libmy_plugin.dylib (macOS) or target/release/libmy_plugin.so (Linux).
Usage
o)load "/path/to/libmy_plugin.dylib";
"add registered"
o)add[1;2]
3
o)add[10;20]
30
o)add[100;200]
300
Available function arities
| Macro | Arity | Registration | Example call |
|---|---|---|---|
| `#[monad]` | 1 | `new_verb_monad(f)` | `f[x]` |
| `#[dyad]` | 2 | `new_verb_dyad(f)` | `f[x;y]` |
Interpreter API
The Interpreter reference provides methods for creating and inspecting values:
Constructors:
| Method | Description |
|---|---|
| `new_scalar_long(v)` | Create long scalar |
| `new_string(s)` | Create string |
| `new_vector_long(iter)` | Create long vector from iterator |
| `new_dict(keys, vals)` | Create dictionary |
| `new_signal(msg)` | Create error signal |
| `new_verb_monad(f)` | Wrap function as monad |
| `new_verb_dyad(f)` | Wrap function as dyad |
Accessors:
| Method | Description |
|---|---|
| `tp()` | Get value type id |
| `long()` | Extract long value |
| `float()` | Extract float value |
| `bool()` | Extract bool value |
| `string()` | Extract string slice |
Type constants:
| Constant | Type |
|---|---|
| `SC_LONG` | Scalar long |
| `SC_FLOAT` | Scalar float |
| `SC_BOOL` | Scalar bool |
| `VEC_LONG` | Vector of longs |
| `VEC_CHAR` | String (vector of chars) |
Using built-in verbs
Plugins can call existing platform verbs:
#[dyad]
fn add_fn(lhs: &AST, rhs: &AST, i: &Interpreter) -> AST {
match (lhs.tp(), rhs.tp()) {
(SC_LONG, SC_LONG) => {
let dyad = i.get_verb_dyad("+").unwrap().dyad();
unsafe { dyad(lhs, rhs, i) }
}
_ => panic!("nyi")
}
}
Stateful reagent plugin
Reagent plugins implement async I/O streams. They consist of a Sender (sink) and Receiver (stream).
Project setup
Same as function plugin, but also depends on rt crate:
[dependencies]
ext = { path = "/kernel/ext" }
rt = { path = "/kernel/o/rt" }
Plugin code
#![feature(allocator_api)]
#![feature(abi_vectorcall)]
#![feature(c_unwind)]
extern crate ext;
extern crate rt;
use core::pin::Pin;
use ext::*;
use std::io;
use std::task::{Context, Poll};
pub struct Receiver;
#[derive(Clone)]
pub struct Sender;
impl Sink for Sender {
type Error = io::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll> { self.poll_flush(cx) }
fn start_send(self: Pin<&mut Self>, item: AST)
-> Result<(), Self::Error> {
let interp = rt::task::local::interpreter::().unwrap();
let mut s = String::new();
interp.format(&mut s, &item);
println!("reagent send: {}", s);
Ok(())
}
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>)
-> Poll> { Poll::Ready(Ok(())) }
fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>)
-> Poll> { Poll::Ready(Ok(())) }
}
impl Meta for Sender {
fn meta(&self) -> Option {
MetaInfo::builder().entry("type", "my-reagent").finish()
}
}
impl SinkReagentExt for Sender {
fn boxed(&self) -> Box + Send + 'static> {
Box::new(self.clone())
}
}
impl Stream for Receiver {
type Item = Result;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>)
-> Poll Traits to implement
| Trait | Purpose |
|---|---|
| `Sink | Sending data to the reagent |
| `SinkReagentExt | Cloning for the sink |
| `Stream` | Receiving data from the reagent |
| `StreamReagentExt | Extension trait for the stream |
| `Meta` | Metadata for introspection |
Usage
o)load "/path/to/libmy_reagent.dylib";
"reagent registered"
o)r: reagent[`myreagent];
o)r["test"];
reagent send: "test"
o)react {[x:r] println[x]};