1
0
forked from Mirror/wren
Files
wren/doc/site/embedding/index.markdown

261 lines
9.8 KiB
Markdown

^title Embedding
Wren is designed to be a scripting language that lives inside a host
application, so the embedding API is as important as any of its language
features. Designing this API well requires satisfying several constraints:
1. **Wren is dynamically typed, but C is not.** A variable can hold a value of
any type in Wren, but that's definitely not the case in C unless you define
some sort of variant type, which ultimately just kicks the problem down the
road. Eventually, we have to move data across the boundary between statically and dynamically typed code.
2. **Wren uses garbage collection, but C manages memory manually.** GC adds a
few constraints on the API. The VM must be able to find every Wren object
that is still usable, even if that object is being referenced from native C
code. Otherwise, Wren could free an object that's still in use.
Also, we ideally don't want to let native C code see a bare pointer to a
chunk of memory managed by Wren. Many garbage collection strategies involve
[moving objects][] in memory. If we allow C code to point directly to an
object, that pointer will be left dangling when the object moves. Wren's GC
doesn't move objects today, but we would like to keep that option for the
future.
3. **The embedding API needs to be fast.** Users may add layers of abstraction
on top of the API to make it more pleasant to work with, but the base API
defines the *maximum* performance you can get out of the system. It's the
bottom of the stack, so there's no way for a user to optimize around it if
it's too slow. There is no lower level alternative.
4. **We want the API to be pleasant to use.** This is the last constraint
because it's the softest. Of course, we want a beautiful, usable API. But we
really *need* to handle the above, so we're willing to make things a bit more
of a chore to reach the first three goals.
[moving objects]: https://en.wikipedia.org/wiki/Tracing_garbage_collection#Copying_vs._mark-and-sweep_vs._mark-and-don.27t-sweep
Fortunately, we aren't the first people to tackle this. If you're familiar with
[Lua's C API][lua], you'll find Wren's similar.
[lua]: https://www.lua.org/pil/24.html
### Performance and safety
When code is safely snuggled within the confines of the VM, it's pretty safe.
Method calls are dynamically checked and generate runtime errors which can be
caught and handled. The stack grows if it gets close to overflowing. In general,
when you're within Wren code, it tries very hard to avoid crashing and burning.
This is why you use a high level language after all—it's safer and more
productive than C. C, meanwhile, really assumes you know what you're doing. You
can cast pointers in invalid ways, misinterpret bits, use memory after freeing
it, etc. What you get in return is blazing performance. Many of the reasons C is
fast are because it takes all the governors and guardrails off.
Wren's embedding API defines the border between those worlds, and takes on some
of the characteristics of C. When you call any of the embedding API functions,
it assumes you are calling them correctly. If you invoke a Wren method from C
that expects three arguments, it trusts that you gave it three arguments.
In debug builds, Wren has assertions to check as many things as it can, but in
release builds, Wren expects you to do the right thing. This means you need to
take care when using the embedding API, just like you do in all C code you
write. In return, you get an API that is quite fast.
## Including Wren
There are two (well, three) ways to get the Wren VM into your program:
1. **Link to the static or dynamic library.** When you [build Wren][build], it
generates both shared and static libraries in `lib` that you can link to.
2. **Include the source directly in your application.** If you want to include
the source directly in your program, you don't need to run any build steps.
Just add the source files in `src/vm` to your project. They should compile
cleanly as C99 or C++98 or anything later.
[build]: ../getting-started.html
In either case, you also want to add `src/include` to your include path so you
can find the [public header for Wren][wren.h]:
[wren.h]: https://github.com/wren-lang/wren/blob/main/src/include/wren.h
<pre class="snippet" data-lang="c">
#include "wren.h"
</pre>
Wren depends only on the C standard library, so you don't usually need to link
to anything else. On some platforms (at least BSD and Linux) some of the math
functions in `math.h` are implemented in a separate library, [libm][], that you
have to explicitly link to.
[libm]: https://en.wikipedia.org/wiki/C_mathematical_functions#libm
If your program is in C++ but you are linking to the Wren library compiled as C,
this header handles the differences in calling conventions between C and C++:
<pre class="snippet" data-lang="c">
#include "wren.hpp"
</pre>
## Creating a Wren VM
Once you've integrated the code into your executable, you need to create a
virtual machine. To do that, you create a `WrenConfiguration` object and
initialize it.
<pre class="snippet" data-lang="c">
WrenConfiguration config;
wrenInitConfiguration(&config);
</pre>
This gives you a basic configuration that has reasonable defaults for
everything. We'll [learn more][configuration] about what you can configure later,
but for now we'll just add the `writeFn`, so that we can print text.
First we need a function that will do something with the output
that Wren sends us from `System.print` (or `System.write`). *Note that it doesn't
include a newline in the output.*
<pre class="snippet" data-lang="c">
void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}
</pre>
And then, we update the configuration to point to it.
<pre class="snippet" data-lang="c">
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
</pre>
[configuration]: configuring-the-vm.html
With this ready, you can create the VM:
<pre class="snippet" data-lang="c">
WrenVM* vm = wrenNewVM(&config);
</pre>
This allocates memory for a new VM and initializes it. The Wren C implementation
has no global state, so every single bit of data Wren uses is bundled up inside
a WrenVM. You can have multiple Wren VMs running independently of each other
without any problems, even concurrently on different threads.
`wrenNewVM()` stores its own copy of the configuration, so after calling it, you
can discard the WrenConfiguration struct you filled in. Now you have a live
VM, waiting to run some code!
## Executing Wren code
You execute a string of Wren source code like so:
<pre class="snippet" data-lang="c">
WrenInterpretResult result = wrenInterpret(
vm,
"my_module",
"System.print(\"I am running in a VM!\")");
</pre>
The string is a series of one or more statements separated by newlines. Wren
copies the string, so you can free it after calling this. When you call
`wrenInterpret()`, Wren first compiles your source to bytecode. If an error
occurs, it returns immediately with `WREN_RESULT_COMPILE_ERROR`.
Otherwise, Wren spins up a new [fiber][] and executes the code in that. Your
code can in turn spawn whatever other fibers it wants. It keeps running fibers
until they all complete or one [suspends].
[fiber]: ../concurrency.html
[suspends]: ../modules/core/fiber.html#fiber.suspend()
If a [runtime error][] occurs (and another fiber doesn't handle it), Wren aborts
fibers all the way back to the main one and returns `WREN_RESULT_RUNTIME_ERROR`.
Otherwise, when the last fiber successfully returns, it returns
`WREN_RESULT_SUCCESS`.
[runtime error]: ../error-handling.html
All code passed to `wrenInterpret()` runs in a special "main" module. That way,
top-level names defined in one call can be accessed in later ones. It's similar
to a REPL session.
## Shutting down a VM
Once the party is over and you're ready to end your relationship with a VM, you
need to free any memory it allocated. You do that like so:
<pre class="snippet" data-lang="c">
wrenFreeVM(vm);
</pre>
After calling that, you obviously cannot use the `WrenVM*` you passed to it
again. It's dead.
Note that Wren will yell at you if you still have any live [WrenHandle][handle]
objects when you call this. This makes sure you haven't lost track of any of
them (which leaks memory) and you don't try to use any of them after the VM has
been freed.
## A complete example
Below is a complete example of the above.
You can find this file in the [example](https://github.com/wren-lang/wren/tree/main/example/embedding) folder.
<pre class="snippet" data-lang="c">
//For more details, visit https://wren.io/embedding/
#include <stdio.h>
#include "wren.h"
static void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}
void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
switch (errorType) {
case WREN_ERROR_COMPILE: {
printf("[%s line %d] [Error] %s\n", module, line, msg);
} break;
case WREN_ERROR_STACK_TRACE: {
printf("[%s line %d] in %s\n", module, line, msg);
} break;
case WREN_ERROR_RUNTIME: {
printf("[Runtime Error] %s\n", msg);
} break;
}
}
int main() {
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* script = "System.print(\"I am running in a VM!\")";
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result) {
case WREN_RESULT_COMPILE_ERROR: { printf("Compile Error!\n"); } break;
case WREN_RESULT_RUNTIME_ERROR: { printf("Runtime Error!\n"); } break;
case WREN_RESULT_SUCCESS: { printf("Success!\n"); } break;
}
wrenFreeVM(vm);
}
</pre>
[handle]: slots-and-handles.html#handles
Next, we'll learn to make that VM do useful stuff...
<a class="right" href="slots-and-handles.html">Slots and Handles &rarr;</a>