vrtbl / passerine

A small extensible programming language designed for concise expression with little code.
https://passerine.io
MIT License
1.05k stars 38 forks source link

Embedding In A Rust Application #34

Open Pebaz opened 3 years ago

Pebaz commented 3 years ago

I noticed that Passerine is listed in this list for scripting languages for Rust:

https://arewegameyet.rs/ecosystem/scripting/

Can documentation be made that shows how to run Passerine code within a Rust application for scripting purposes?

slightknack commented 3 years ago

Yes! This is something that is much needed, and isn't up-to-par yet. Ideally, Passerine should expose functions at the top level that run scripts and produce results. This isn't the case yet:

Right now the bindings are pretty low level; to run some Passerine, you'd have to do something along the lines of:

let source = Source::source(/* passerine code to run */)
let compiled = lex(source)
    .and_then(parse)
    .and_then(desugar)
    .and_then(gen)

if let Some(bytecode) = compiled {
    let mut vm = VM::init(Closure::wrap(lambda));
    let ran = vm.run();
    if let Err(trace) = ran {
        eprintln!("{}", trace);
    }
}  else if let Err(syntax) = compiled {
    eprintln!("{}", syntax);
}

To bind Rust functions to passerine, you have to use the gen_with_ffi function; by default, the core FFI is included.

This above scheme is not ideal for a number of reasons:

  1. When compiling, you have to list each compilation step. A function like compile would go a long way. The good news is that this isn't too hard.
  2. When running, Passerine doesn't return a value; to get useful values out of Passerine, you'd have to inspect the stack. This is very bad. The module system should help fix this; it'll make the value returned by Passerine be a record, so that variables can be inspected. The module system will also allow Passerine to be run in a repl-like environment, where the state in the VM is preserved between runs. See #27 for more.
  3. When passing data across the FFI boundary, you have to use Passerine Data. Arbitrary Rust data can't be passed. In the future, it should be possible to pass arbitrary Rust data, or use something like #[derive(passerine::ExternalData)] which automatically 'serializes' and 'de-serializes' data for you.

Here's what needs to happen, in order of urgency, to meet these goals:

The first two items in this list could be done today (I might try to knock them out, actually). The second ones will take a bit more time. If you'd like to get a feel for how the project is structured, I think putting in either one of these as a PR would be fairly straightforward - if anyone intends to do this, let me know, and I'll answer any questions as needed.

What are your thoughts?