Embedding hare-wren =================== Embedding hare-wren into your Hare programs is relatively straightforward and not much different from `embedding Wren into a C program `_. Pair a reading of the upstream Wren documentation with a reading of the :ref:`Hare API reference ` and the integration should be straightforward. hare-wren also provides an optional, simple Wren-facing API and an event-driven runtime and fiber scheduler, which you can use to quickly get up and running with a more sophisticated embedded Wren runtime for your application. The Hare interface for the runtime is available at :ref:`wren::api `. Getting started with wren::api ------------------------------ wren::api is based on the `hare-ev `_ event loop. Your application should provide an ``ev::loop`` instance to support the runtime, which you are free to use for other I/O and event management in your broader application. In addition to preparing an event loop, you need a suitable configuration via `wren::api::config `_, which is a superset of the Wren virtual machine configuration that adds fields for customizing the behavior of the runtime. Sensible defaults are provided via `wren::api::default_config `_. In particular, the runtime provides a :ref:`module loader ` to bind the Wren API to the virtual machine (and to load local modules). .. warning:: The Wren modules available via wren::api offer direct, unmanaged access to the host filesystem and similar features. If you want to disable the built-in modules, or offer a subset of supported modules, you have to customize the module loader -- see the next section. A simple example of embedding the hare-wren runtime in your application might look like this: .. code-block:: hare use ev; use os; use wren; use wren::api; export fn main() void = { const loop = ev::newloop()!; const rt = api::new(&loop, [])!; defer api::destroy(rt); // Interpret the startup code. In this case it's a simple "Hello // world", but a more sophisticated use-case could set up timers, // schedule async I/O, etc. match (api::interpret(rt, "main", `System.print("Hello world!")`)) { case void => void; case let err: wren::error => fmt::errorln(wren::strerror(err))!; }; // Run the Wren virtual machine until all scheduled fibers are // completed. match (api::run(rt)) { case void => void; case let err: wren::error => fmt::errorln(wren::strerror(err))!; os::exit(os::status::FAILURE); case let err: errors::error => fmt::errorln(errors::strerror(err))!; os::exit(os::status::FAILURE); }; }; .. code-block:: $ hare run -lc main.ha Hello world! Customizing the runtime ----------------------- If you want to provide your own foreign methods and classes, or sandbox the runtime and/or provide a subset of the runtime API to Wren scripts, you will need to customize the module loader. In order to do so, one should customize the appropriate fields of the `config struct `_ used to initialize the VM, in particular: * resolve_module * load_module * bind_foreign_method * bind_foreign_class You can customize the behavior of each by either replacing them entirely, or providing a replacement which ultimately calls the runtime's default implementation of the corresponding functionality as a fallback. Customizing the module loader +++++++++++++++++++++++++++++ One can customize the module loader by overriding the resolve_module and load_module functions and providing your own functionality. You can use this, for exmaple, to suppliment the available modules with additional modules providing new foreign classes and methods. The default behavior is implemented by `wren::api::load_module `_. Feel free to call this function if you want to load the default modules after your application-specific processing is complete. Customizing the runtime API +++++++++++++++++++++++++++ The default implementations for ``bind_foreign_method`` and ``bind_foreign_class`` will bind foreign classes and methods for the entire runtime API as documented in the :ref:`Wren API reference `. It is often desirable to customize this behavior, either adding additional foreign classes and/or methods, or disabling the defaults to sandbox Wren scripts. To add additional foreign methods and classes, provide your own implementations of `bind_foreign_class_fn `_ and/or `bind_foreign_method_fn `_ to bind your foreign members to the VM. If your implemenation is called with a class or method name that you do not recognize, you can call the runtime implementations to provide the standard API: * `bind_foreign_class `_ * `bind_foreign_method `_ Do not call these functions if you want to sandbox the VM. If you want to provide *limited* support for the standard API, you can also bind on a module-by-module basis through functions, for instance using ``io_bind_class`` and ``io_bind_method`` to bind *only* the :ref:`io module `. Extending the scheduler ----------------------- The runtime's :ref:`Scheduler ` is used to suspend fibers while awaiting asynchronous operations, such as I/O. It is possible for users of the Hare API to implement new foreign classes and methods (see the previous section on adding these to the runtime) which provide their own asynchronous operations. To implement a new asynchronous operation, one should utilize both the Wren and Hare APIs together. To illustrate, let's create an example similar to the :ref:`time.Timer.sleep ` method. On the Wren side, provide a convenience function and a foreign stub: .. code-block:: wren import "scheduler" for Scheduler class Timer { static sleep(ms) { Scheduler.await { Time.sleep_(Fiber.current, ms) } } foreign static sleep_(fiber, ms) } The Scheduler will suspend the fiber after this function runs, and the foreign method should retain a reference to that fiber to resume with `wren::api::scheduler_resume `_ (or the related `wren::api::scheduler_return `_ (or and `wren::api::scheduler_error `_ (or functions). Implementing the foreign method and the request lifecycle looks something like this on the Hare side: .. code-block:: hare use ev; use time; use wren; use wren::api; // State for the asynchronous operation type time_op = struct { vm: *wren::vm, timer: ev::timer, fiber: wren::handle, }; // Bind me to time.Timer.sleep_(_,_) with a foreign method loader fn time_sleep(vm: *wren::vm) void = { // Fetch the arguments const fiber = wren::get_handle(vm, 1); const ms = wren::get_f64(vm, 2): uint; // Execute the user's request and handle errors match (time_dosleep(vm, fiber, ms)) { case nomem => wren::abort_fiber(vm, "Out of memory"); case let err: errors::error => wren::abort_fiber(vm, errors::strerror(err)); case void => void; }; }; fn time_dosleep( vm: *wren::vm, fiber: wren::handle, ms: uint, ) (void | nomem | errors::error) = { let ok = false; const loop = api::getloop(vm); // Create a timer with the event loop for the operation const timer = ev::timer_init(loop, time::clock::MONOTONIC, &time_on_expired, null)?; defer if (!ok) ev::timer_finish(&timer); // Allocate state for the operation, storing the timer and the fiber // that needs to be resumed when the operation is complete const op = alloc(time_op { vm = vm, timer = timer, fiber = fiber, })?; ok = true; const delay = ms: time::duration * time::MILLISECOND; ev::req_setuser(&op.timer, op); ev::timer_configure(&op.timer, delay, 0); }; fn time_on_expired(req: *ev::timer, user: nullable *opaque) void = { const op = user: *time_op; const fiber = op.fiber; const vm = op.vm; // Free state associated with the operation ev::timer_finish(&op.timer); free(op); // Resume the pending fiber. Note that this won't return until the fiber // finishes running. scheduler_resume(vm, op.fiber); };