NAME

docs/pdds/embedding.pod - Parrot's Embedding and Extending Interface

ABSTRACT

What we believe people will do when embedding and extending Parrot, why they do it, and how.

{{ NOTE: some of this will later move into pdds 11 & 12, but for now just want to get the stub checked in. }}

VERSION

$Revision: 31023 $

DESCRIPTION

Why embed:

  • access to special features/libraries/languages Parrot provides

  • need an interpreter for a DSL or existing language

  • want to run Parrot on another platform or environment (dedicated hardware, in a web server, et cetera)

Why extend:

  • need something NCI doesn't provide

  • writing a custom PMC

Philosophical rules:

  • only ever use opaque pointers

  • should be able to communicate through PMCs

  • minimize conversions to and from C data

    • perhaps macros; Ruby does this fairly well and Perl 5 does this poorly

    • minimize the number of necessary functions

    • probably can follow core Parrot code to some extent, but beware the Perl 5 problem

      • do not expose Parrot internals that may change

        • minimize the number of headers used

        • minimize the number of Parrot types exposed

        • follow boundaries similar to those of PIR where possible

      • probably includes vtable methods on PMCs

Gotchas:

  • who handles signals?

  • who owns file descriptors and other Unix resources?

  • is there an exception boundary?

  • namespace issues -- especially key related

  • probably a continuation/control flow boundary

  • packfiles and compilation units probably too much information for either

  • do not let MMD and other implementation details escape

  • okay to require some PBC/PIR/PASM for handling round-trip data

  • Parrot should not spew errors to STDERR when embedded

  • who allocates and deallocates resources passed through the boundary level?

  • should be access to Parrot's event loop when embedded

  • passing var args to Parrot subs likely painful

    • perhaps macros/functions to add parameters to call

    • build up a call signature somehow?

    • some abstraction for a call frame?

  • compiling code from a string should return the PMC Sub entry point (:main)

  • are there still directory path, loading, and deployment issues?

  • how do dynamic oplibs and custom PMCs interact?

  • what's the best way to handle character sets and Unicode?

DEFINITIONS

Embedding - using libparrot from within another program, likely with a C/NCI/FFI interface

Extending - writing Parrot extensions, likely through C or another language

In practice, there is little difference between the two; mostly in terms of who has control. The necessary interfaces should stay the same.

IMPLEMENTATION

Implementation details.

Simplicity is the main goal; it should be almost trivial to embed Parrot in an existing application. It must be trivial to do the right thing; the APIs must make it so much easier to work correctly than to make mistakes. This means, in particular, that:

  • it should never be possible to crash or corrupt the interpreter when following the interface as documented

  • each API call or element should have a single purpose

  • names must be consistent in the API documentation and the examples

  • it should be possible to embed Parrot within Parrot through NCI, as a test both of the sanity of the external interface as well as NCI

Working with Interpreters

It is the external code's duty to create, manage, and destroy interpreters.

Parrot_new( NULL ) returns an opaque pointer to a new interpreter:

Parrot_Interp Parrot_new(Parrot_Interp parent);

parent can be NULL for the first interpreter created. All subsequent calls to this function should pass an existing interpreter.

Note: it is not clear what happens if you fail to do so; is there a way to detect this in the interface and give a warning?

Parrot_destroy ( interp ) destroys an interpreter and frees its resources.

void Parrot_destroy(Parrot_Interp);

Note: It is not clear what happens if this interpreter has active children.

Working with Source Code and PBC Files

Perhaps the most common case for working with code is loading it from an external file. This may often be PBC, but it must also be possible to load code with any registered compiler. This must be a single-stage operation:

Parrot_PMC Parrot_load_bytecode( Parrot_Interp, const char *filepath );

Parrot_PMC Parrot_load_hll_code( Parrot_Interp, const char *compiler,
                                                const char *filepath );

The PMC returned will be the Sub PMC representing the entry point into the code. That is, it will be the PMC representing the :main subroutine, if one exists, or the first subroutine in the file.

If there is an error -- such that the file does not exist, the compiler is unknown, or there was a compilation or invalid bytecode error -- the PMC should be an Exception PMC instead.

Note: I suppose NULL would work as well; it might be more C-like. Continue considering.

Note also: the current Parrot_readbc() and Parrot_loadbc() exposes the details of packfiles to the external API and uses two operations to perform a single logical operation.

Note: it may be worth reconsidering these names, if Parrot_load_bytecode() can load PBC, PIR, and PASM files without having a compiler named explicitly.

Compiling source code generated or read from the host application is also possible:

Parrot_PMC Parrot_compile_string( Parrot_Interp, const char *compiler,
                                                 const char *code );

The potential return values are the same as for loading code from disk.

Note: this declaration should move from interpreter.h to embed.h.

Working with PMCs

TBD.

Calling Functions

TBD.

Calling Opcodes

TBD.

LANGUAGE NOTES

It should be possible to register a compiler for an HLL with an interpreter such that it is possible to load source code written in that language or pass source code to an interpreter successfully.

ATTACHMENTS

Any associated documents.

FOOTNOTES

List of footnotes to the text.

REFERENCES

List of references.