diff options
Diffstat (limited to 'docs/embedding.rst')
| -rw-r--r-- | docs/embedding.rst | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/docs/embedding.rst b/docs/embedding.rst new file mode 100644 index 0000000..0bb9dcc --- /dev/null +++ b/docs/embedding.rst @@ -0,0 +1,161 @@ +################## +Embedding Actinide +################## + +Actinide is designed to be embedded into larger Python programs. It's possible +to call into Actinide, either by providing code to be evaluated, or by +obtaining builtin functions and procedures from Actinide and invoking them. + +The ``Session`` class is the basic building block of an Actinide integration. +Creating a session creates a number of resources associated with Actinide +evaluation: a symbol table for interning symbols, and an initial top-level +environment to evaluate code in, pre-populated with the Actinide standard +library. + +Executing Actinide programs in a session consists of two steps: reading the +program in from a string or an input port, and evaluating the resulting forms. +The following example illustrates a simple infinite loop: + +.. code-block:: python + + import actinide + + session = actinide.Session() + program = session.read(''' + (begin + ; define the factorial function + (define (factorial n) + (fact n 1)) + + ; define a tail-recursive factorial function + (define (fact n a) + (if (= n 1) + a + (fact (- n 1) (* n a)))) + + ; call them both + (factorial 100)) + ''') + + # Compute the factorial of 100 + result = session.eval(program) + +As a shorthand for this common sequence of operations, the Session exposes a +``run`` method: + +.. code-block:: python + + print(*session.run('(factorial 5)')) # prints "120" + +Callers can inject variables, including new builtin functions, into the initial +environment using the ``bind``, ``bind_void``, ``bind_fn``, and +``bind_builtin`` methods of the session. + +To bind a simple value, or to manually bind a wrapped builtin, call +``session.bind``: + +.. code-block:: python + + session.bind('var', 5) + print(*session.run('var')) # prints "5" + +To bind a function whose return value should be ignored, call ``bind_void``. +This will automatically determine the name to bind the function to: + +.. code-block:: python + + session.bind_void(print) + session.run('(print "Hello, world!")') # prints "Hello, world!" using Python's print fn + +To bind a function returning one value (most functions), call ``bind_fn``. This +will automatically determine the name to bind to: + +.. code-block:: python + + def example(): + return 5 + + session.bind_fn(example) + print(*session.run('(example)')) # prints "5" + +Finally, to bind a function returning a tuple of results, call +``bind_builtin``. This will automatically determine the name to bind to: + +.. code-block:: python + + def pair(): + return 1, 2 + + session.bind_builtin(pair) + print(*session.run('(pair)')) # prints "1 2" + +Actinide functions can return zero, one, or multiple values. As a result, the +``result`` returned by ``session.eval`` is a tuple, with one value per result. + +Actinide can bind Python functions, as well as bound and unbound methods, and +nearly any other kind of callable. Under the hood, Actinide uses a thin adapter +layer to map Python return values to Actinide value lists. The ``bind_void`` +helper ultimately calls that module's ``wrap_void`` to wrap the function, and +``bind_fn`` calls ``wrap_fn``. (Tuple-returning functions do not need to be +wrapped.) If you prefer to manually bind functions using ``bind``, they must be +wrapped appropriately. An equivalent set of methods, ``macro_bind``, +``macro_bind_void``, ``macro_bind_fn``, and ``macro_bind_builtin`` bind values +to entries in the top-level macro table, instead of the top-level environment, +and allow extension of the language's syntax. + +Finally, Actinide can bind specially-crafted Python modules. If a module +contains a top-level symbol named ``An`` (for the informal chemical symbol for +the actinide series), it can be passed to the session's ``bind_module`` method. +The symbol must be bound to an instance of the ``Registry`` class from the +``actinide.builtin`` module: + +.. code-block:: python + + from actinide.builtin import Registry + An = Registry() + + five = An.bind('five', 5) + + @An.void + def python_print(*args): + print(*args) + + @An.fn + def bitwise_and(a, b): + return a & b + + @An.builtin + def two_values(): + return 1, "Two" + + # @An.macro_bind, @An.macro_void, @An.macro_fn, and @An.macro_builtin follow + # the same pattern. + +Going the other direction, values can be extracted from bindings in the session +using the ``get`` method: + +.. code-block:: python + + session.run('(define x 8)') + print(session.get('x')) # prints "8" + +If the extracted value is a built-in function or an Actinide procedure, it can +be invoked like a Python function. However, much like ``eval`` and ``run``, +Actinide functions returne a tuple of results rather than a single value: + +.. code-block:: python + + session.run(''' + (begin + ; Set a variable + (define x 5) + + ; Define a function that reads the variable + (define (get-x) x)) + ''') + + get_x = session.get('get-x') + print(*get_x()) # prints "5" + +This two-way binding mechanism makes it straightforward to define interfaces +between Actinide and the target domain. |
