🔍

Plugins

The information on this page lags behind the platform and will be changed soon!

The platform allows creating two types of plugins:

  • stateless functions;
  • stateful reagents.

The first one can be used to define custom monads/dyads/triads/tetrads/polyads. Let's implement our own add function.

Firstly, make some preparations:

#[macro_use]
extern crate ext;
use ext::{Interpreter, AST};

Define your own function:

#[unwind(allowed)]
extern  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())
        }
        _ => i.new_signal(i.new_string("nyi"))
    }
}

Finally, register a newly created function:

declare_plugin!(
    // constructor
    |i: &Interpreter| {
        // register own function inside interpreter's environment
        let sym = i.intern_string("add");
        i.insert_entity(sym, i.new_verb_dyad(add_fn));
        i.new_string("add registered")
    },
    // destructor
    || {}
);

Let's test our function through the interpreter:

o)load "libsimple_plugin.so"
"add registered"
o)add[1;2]
3
o)add[1;`2]
nyi
o)1 add 2
3
o)1 add `2
nyi
o)

That's all! Simple, right? Ok, now we are ready to create a stateful reagent. Let it be our own implementation of REPL.

The preparation stage is a bit longer:

use ext::{In, Out, Pipeline, State, AST, Interpreter, ErrorKind};
use mio::unix::EventedFd;
use mio::*;
use rt::rtalloc::ushared::extend_lifetime;
use std::cell::UnsafeCell;
use std::io;
use std::io::Read;

Define the size of buffer for holding data to be read from STDIN:

const BUFFER_SIZE: usize = 64000000;

The core part of each reagent is a PIPE - sender and receiver:

pub struct Receiver {
    i: &'static Interpreter,
    buf: UnsafeCell<Vec<u8>>,
}

#[derive(Clone)]
pub struct Sender {
    i: &'static Interpreter,
}

To make them actual reagents, implement some traits:

impl Out<AST> for Sender {
    fn push(&self, v: AST) -> Result<(), ErrorKind> {
        self.i.print_format(&v);
        Ok(())
    }
    fn try_push(&self, _v: AST) -> Option<AST> { None }
    fn box_clone(&self) -> Box<Out<AST>> { Box::new((*self).clone()) }
}

impl Evented for Receiver {
    fn register(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> {
        EventedFd(&0).register(poll, token, interest, opts)
    }
    fn reregister(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> {
        EventedFd(&0).reregister(poll, token, interest, opts)
    }
    fn deregister(&self, poll: &Poll) -> io::Result<()> { EventedFd(&0).deregister(poll) }
}

impl In<AST> for Receiver {
    fn pop(&self) -> AST {
        unsafe {
            let input = self.buf.get();
            let size = io::stdin().read(&mut (*input)).expect("STDIN error.");
            let ast = self.i.parse(&(*input)[..size]);
            self.i.eval(&ast)
        }
    }
    fn try_pop(&self) -> Option<AST> { None }
    fn peek(&self) -> Option<&AST> { None }
    fn ready(&self, _readiness: Ready) -> State { State::Ready }
}

Define registration function that will be called on reagent creation:

#[unwind(allowed)]
unsafe extern "C" fn simple_reagent(_: &[AST], i: &Interpreter) -> Pipeline<AST> {
    let tx = box Sender { i: extend_lifetime(i) };
    let rx = box Receiver {
        i: extend_lifetime(i),
        buf: UnsafeCell::new(vec![0u8; BUFFER_SIZE]),
    };
    Pipeline::new_async(rx, tx)
}

Almost done, now we need to register our plugin:

declare_plugin!(
    // constructor
    |i: &Interpreter| {
        i.register_reagent("simple", simple_reagent);
        i.new_string("simple reagent registered")
    },
    // destructor
    || {}
);

Finally, we can load our plugin into interpreter:

load "platform/plugins/simple_reagent/target/debug/libsimple_reagent.so";
repl: reagent[`simple];
ps1:  {print["o)"]};
out:  {repl[x]; ps1[]};
react {[x:repl] out[x]};
ps1[];
join 0;