diff --git a/doc/site/embedding/application-lifecycle.markdown b/doc/site/embedding/application-lifecycle.markdown
deleted file mode 100644
index bb713ccc..00000000
--- a/doc/site/embedding/application-lifecycle.markdown
+++ /dev/null
@@ -1,9 +0,0 @@
-^title Application Lifecycle
-
-**TODO: Write these docs.**
-
-Until these are written, you can read the docs in [wren.h][].
-
-[wren.h]: https://github.com/munificent/wren/blob/master/src/include/wren.h
-
-← Configuring the VM
diff --git a/doc/site/embedding/calling-c-from-wren.markdown b/doc/site/embedding/calling-c-from-wren.markdown
index 2f83e1f4..0c071ebf 100644
--- a/doc/site/embedding/calling-c-from-wren.markdown
+++ b/doc/site/embedding/calling-c-from-wren.markdown
@@ -37,8 +37,10 @@ statement itself is evaluated -- the VM asks the host application for the C
function that should be used for the foreign method.
It does this through the `bindForeignMethodFn` callback you give it when you
-first configure the VM. This callback isn't the foreign method itself. It's the
-binding function your app uses to *look up* the foreign methods.
+first [configure the VM][config]. This callback isn't the foreign method itself.
+It's the binding function your app uses to *look up* foreign methods.
+
+[config]: configuring-the-vm.html
Its signature is:
@@ -112,8 +114,7 @@ object is in slot zero, and arguments are in consecutive slots after that.
You use the slot API to read those arguments, and then perform whatever work you
want to in C. If you want the foreign method to return a value, place it in slot
-zero. that slot, the foreign method will implicitly return the receiver, since
-that's what is already in there.) Like so:
+zero. Like so:
:::c
void mathAdd(WrenVM* vm)
@@ -124,7 +125,9 @@ that's what is already in there.) Like so:
}
While your foreign method is executed, the VM is completely suspended. No other
-fibers will run until your foreign method returns.
+fibers will run until your foreign method returns. You should *not* try to
+resume the VM from within a foreign method by calling `wrenCall()` or
+`wrenInterpret()`. The VM is not re-entrant.
This covers foreign behavior, but what about foreign *state*? For that, we need
a foreign *class*...
diff --git a/doc/site/embedding/calling-wren-from-c.markdown b/doc/site/embedding/calling-wren-from-c.markdown
index 3b2dd75e..319de52b 100644
--- a/doc/site/embedding/calling-wren-from-c.markdown
+++ b/doc/site/embedding/calling-wren-from-c.markdown
@@ -7,7 +7,7 @@ compiler, but that's still a good bit of work.
It's also not an effective way to communicate. You can't pass arguments to
Wren—at least, not without doing something nasty like converting them to
-literals in a string of source code—and we can't get a result value back.
+literals in a string of source code—and you can't get a result value back.
`wrenInterpret()` is great for loading code into the VM, but it's not the best
way to execute code that's already been loaded. What we want to do is invoke
@@ -26,7 +26,7 @@ C, first we need a few things:
[signature]: ../method-calls.html#signature
-* **The receiver to invoke the method on.** It's the receiver's class that
+* **The receiver object to invoke the method on.** The receiver's class
determines which method is actually called.
* **The arguments to pass to the method.**
diff --git a/doc/site/embedding/configuring-the-vm.markdown b/doc/site/embedding/configuring-the-vm.markdown
index 1d737756..3c2b9a12 100644
--- a/doc/site/embedding/configuring-the-vm.markdown
+++ b/doc/site/embedding/configuring-the-vm.markdown
@@ -2,7 +2,7 @@
When you create a Wren VM, you tweak it by passing in a pointer to a
WrenConfiguration structure. Since Wren has no global state, you can configure
-each VM them differently if your application happens to run multiple.
+each VM differently if your application happens to run multiple.
The struct looks like:
@@ -20,7 +20,8 @@ The struct looks like:
int heapGrowthPercent;
} WrenConfiguration;
-Most fields have useful defaults, which you can (and should) set by calling:
+Most fields have useful defaults, which you can (and should) initialize by
+calling:
:::c
wrenInitConfiguration(&configuration);
@@ -51,7 +52,7 @@ host should return the source code for that module. Memory for the source should
be allocated using the same allocator that the VM uses for other allocation (see
below). Wren will take ownership of the returned string and free it later.
-The module loader will only be called once for any given module name. Wren caches
+The module loader is only be called once for any given module name. Wren caches
the result internally so subsequent imports of the same module use the
previously loaded code.
@@ -63,21 +64,19 @@ If you don't use any `import` statements, you can leave this `NULL`.
### `bindForeignMethodFn`
The callback Wren uses to find a foreign method and bind it to a class. See
-[this page][foreign method] for details.
+[this page][foreign method] for details. If your application defines no foreign
+methods, you can leave this `NULL`.
[foreign method]: /embedding/calling-c-from-wren.html
-If your application defines no foreign methods, you can leave this `NULL`.
-
### `bindForeignClassFn`
The callback Wren uses to find a foreign class and get its foreign methods. See
-[this page][foreign class] for details.
+[this page][foreign class] for details. If your application defines no foreign
+classes, you can leave this `NULL`.
[foreign class]: /embedding/storing-c-data.html
-If your application defines no foreign classes, you can leave this `NULL`.
-
## Diagnostics
These let you wire up some minimal output so you can tell if your code is doing
@@ -108,7 +107,7 @@ is:
int line,
const char* message)
-The type parameter is one of:
+The `type` parameter is one of:
:::c
typedef enum
@@ -197,5 +196,4 @@ frequent garbage collections.
If set to zero, the VM uses a default of 50.
-Application Lifecycle →
← Storing C Data
diff --git a/doc/site/embedding/index.markdown b/doc/site/embedding/index.markdown
index 0207c004..71355787 100644
--- a/doc/site/embedding/index.markdown
+++ b/doc/site/embedding/index.markdown
@@ -92,15 +92,10 @@ 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,
-then you'll need to handle the calling convention differences like so:
+this header handles the differences in calling conventions between C and C++:
:::c
- extern "C" {
- #include "wren.h"
- }
-
-(Wren's source can be compiled as either C or C++, so you can skip this by
-compiling the whole thing yourself as C++. Whatever floats your boat.)
+ #include "wren.hpp"
## Creating a Wren VM
@@ -115,7 +110,7 @@ This gives you a basic configuration that has reasonable defaults for
everything. If you don't need to tweak stuff, you can leave it at that. We'll
[learn more][configuration] about what you can configure later.
-[configuration]: configuration.html
+[configuration]: configuring-the-vm.html
With this ready, you can create the VM:
@@ -128,7 +123,7 @@ 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
+can discard the WrenConfiguration struct you filled in. Now you have a live
VM, waiting to run some code!
## Executing Wren code
diff --git a/doc/site/embedding/slots-and-handles.markdown b/doc/site/embedding/slots-and-handles.markdown
index 0e3da5fd..9ae0f65a 100644
--- a/doc/site/embedding/slots-and-handles.markdown
+++ b/doc/site/embedding/slots-and-handles.markdown
@@ -14,8 +14,8 @@ of the VM. These functions are based on two fundamental concepts: **slots** and
When you want to send data to Wren, read data from it, or generally monkey
around with Wren objects from C, you do so by going through an array of slots.
-You can think of it as a shared message board that both the VM and your C code
-leave notes on for the other side to process.
+Think of it as a shared message board that both the VM and your C code leave
+notes on for the other side to process.
The array is zero-based, and each slot can hold a value of any type. It is
dynamically sized, but it's your responsibility to ensure there are enough slots
@@ -58,8 +58,9 @@ enough slots for the objects it is sending you.
### Writing slots
-Once you have some slots, you can store data in them using a number of functions all named `wrenSetSlot()` where `` is the kind of data.
-We'll start with the simple ones:
+Once you have some slots, you store data in them using a number of functions all
+named `wrenSetSlot()` where `` is the kind of data. We'll start with
+the simple ones:
:::c
void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
@@ -231,15 +232,13 @@ A handle is an opaque wrapper around an object of any type, but just as
important, it's a *persistent* one. When Wren gives you a pointer to a
WrenHandle, it guarantees that that pointer remains valid. You can keep it
around as long as you want. Even if a garbage collection occurs, Wren will
-ensure all of the handle and the objects they wrap are kept safely in memory.
+ensure the handle and the object it wraps are kept safely in memory.
Internally, Wren keeps a list of all of the WrenHandles that have been created.
That way, during garbage collection, it can find them all and make sure their
-objects aren't freed.
-
-But what if you don't want it to be kept around any more? Since C relies on
-manual memory management, WrenHandle does too. When you are done with one, you
-must explicitly release it by calling:
+objects aren't freed. But what if you don't want it to be kept around any more?
+Since C relies on manual memory management, WrenHandle does too. When you are
+done with one, you must explicitly release it by calling:
:::c
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
diff --git a/doc/site/embedding/storing-c-data.markdown b/doc/site/embedding/storing-c-data.markdown
index f6b4aac7..cecae803 100644
--- a/doc/site/embedding/storing-c-data.markdown
+++ b/doc/site/embedding/storing-c-data.markdown
@@ -1,66 +1,366 @@
^title Storing C Data
-**TODO: Write these docs.**
+An embedded language often needs to work with native data. You may want a
+pointer to some memory managed in the C heap, or maybe you want to store a chunk
+of data more efficiently than Wren's dynamism allows. Often, you want to have a
+Wren object that manages an external resource like a file handle.
-
+## Defining a Foreign Class
-Until these are written, you can read the docs in [wren.h][].
+You define one like so:
-[wren.h]: https://github.com/munificent/wren/blob/master/src/include/wren.h
+ :::wren
+ foreign class Point {
+ // ...
+ }
+
+The `foreign` keyword tells Wren to loop in the host application when it
+constructs an instance of the class. The host tells Wren how many bytes of extra
+memory the foreign instance should contain and in return, Wren gives the host
+the opportunity to initialize that data.
+
+To talk to the host app, Wren needs a C function it can call when it constructs
+an instance of the foreign class. This function is found through a binding
+process similar to how foreign methods are bound. When you [configure the VM][],
+you set the `bindForeignClassFn` field in WrenConfiguration to point to a C
+callback you define. Its signature must be:
+
+[configure the vm]: configuring-the-vm.html
+
+ :::c
+ WrenForeignClassMethods bindForeignClass(
+ WrenVM* vm, const char* module, const char* className);
+
+Wren invokes this callback once when a foreign class declaration is executed.
+Wren passes in the name of the module containing the foreign class, and the name
+of the class being declared. The host's responsibility is to return one of these
+structs:
+
+ :::c
+ typedef struct
+ {
+ WrenForeignMethodFn allocate;
+ WrenFinalizerFn finalize;
+ } WrenForeignClassMethods;
+
+It's a pair of function pointers. The first, `allocate`, is called by Wren
+whenever an instance of the foreign class is created. (We'll get to the optional
+`finalize` callback later.) The allocation callback has the same signature as a
+foreign method:
+
+ :::c
+ void allocate(WrenVM* vm);
+
+## Initializing an Instance
+
+When you create an instance of a foreign class by calling one its
+[constructors][], Wren invokes the `allocate` callback you gave it when binding
+the foreign class. Your primary responsibility in that callback is to tell Wren
+how many bytes of raw memory you need. You do that by calling:
+
+[constructors]: ../classes.html#constructors
+
+ :::c
+ void* wrenSetSlotNewForeign(WrenVM* vm,
+ int slot, int classSlot, size_t size);
+
+Like other [slot manipulation functions][slot], it both reads from and writes to
+the slot array. It has a few parameters to make it more general purpose since it
+can also be used in other foreign methods:
+
+[slot]: slots-and-handles.html
+
+* The `slot` parameter is the destination slot where the new foreign object
+ should be placed. When you're calling this in a foreign class's allocate
+ callback, this should be 0.
+
+* The `classSlot` parameter is the slot where the foreign class being
+ constructed can be found. When the VM calls an allocate callback for a
+ foreign class, the class itself is already in slot 0, so you'll pass 0 for
+ this too.
+
+* Finally, the `size` parameter is the interesting one. Here, you pass in the
+ number of extra raw bytes of data you want the foreign instance to store.
+ This is the memory you get to play with from C.
+
+So, for example, if you wanted to create a foreign instance that wraps an extra
+eight bytes of data, you'd call:
+
+ :::c
+ void* data = wrenSetSlotNewForeign(vm, 0, 0, 8);
+
+The value returned by `wrenSetSlotNewForeign()` is the raw pointer to the
+requested bytes. You can cast that to whatever C type makes sense (as long as it
+fits within the requested number of bytes) and initialize it as you see fit.
+
+Any parameters passed to the constructor are also available in subsequent slots
+in the slot array. That way you can initialize the foreign data based on values
+passed to the constructor from Wren.
+
+After the allocate callback returns, the class's constructor in Wren is run and
+execution proceeds like normal. From here on out, within Wren, it appears you
+have a normal instance of a class. It just happens to have some extra bytes
+hiding inside it that can be accessed from foreign methods.
+
+## Accessing Foreign Data
+
+Typically, the way you make use of the data stored in an instance of a foreign
+class is through other foreign methods. Those are usually defined on the same
+foreign class, but can be defined on other classes as well. Wren doesn't care.
+
+If you call a foreign method on a foreign instance, or you pass in a foreign
+instance as a parameter to the method, then it will be in the slot array. If the
+foreign instance is the receiver of the method, it's in slot zero. Parameters go
+in order after that. Given a foreign instance in a slot, you can access the raw
+bytes it stores by calling:
+
+ :::c
+ void* wrenGetSlotForeign(WrenVM* vm, int slot);
+
+You pass in the slot index containing the foreign object and it gives you back a
+pointer to the raw memory the object wraps. As usual, the C API doesn't do any
+type or bounds checking, so it's on you to make sure the object in that slot
+actually *is* an instance of a foreign class and contains as much memory as you
+access.
+
+Given that void pointer, you can now freely read and modify the data it points
+to. They're your bits, Wren just holds them for you.
+
+## Freeing Resources
+
+If your foreign instances are just holding memory and you're OK with Wren's
+garbage collector managing the lifetime of that memory, then you're done. Wren
+will keep the bytes around as long as their is still a reference to them. When
+the instance is no longer reachable, eventually the garbage collector will do
+its thing and free the memory.
+
+But, often, your foreign data refers to some resource whose lifetime needs to
+be explicitly managed. For example, if you have a foreign object that wraps an
+open file handle, you need to ensure that handle doesn't get left open when the
+GC frees the foreign instance.
+
+Of course, you can (and usually should) add a method on your foreign class, like
+`close()` so the user can explicitly release the resource managed by the object.
+But if they forget to do that and the object is no longer reachable, you want to
+make sure the resource isn't leaked.
+
+To that end, you can also provide a *finalizer* function when binding the
+foreign class. That's the other callback in the WrenForeignClassMethods struct.
+If you provide that callback, then Wren will invoke it when an instance of your
+foreign class is about to be freed by the garbage collector. This gives you one
+last chance to clean up the object's resources.
+
+Because this is called during the middle of a garbage collection, you do not
+have unfettered access to the VM. It's not like a normal foreign method where
+you can monkey around with slots and other stuff. Doing that while the GC is
+running could leave Wren in a weird state.
+
+Instead, the finalize callback's signature is only:
+
+ :::c
+ void finalize(void* data);
+
+Wren gives you the pointer to your foreign function's memory, and that's it. The
+*only* thing you should do inside a finalizer is release any external resources
+referenced by that memory.
+
+## A Full Example
+
+That's a lot to take in, so let's walk through a full example of a foreign class
+with a finalizer and a couple of methods. We'll do a File class that wraps the
+C standard file API.
+
+In Wren, the class we want looks like this:
+
+ :::wren
+ foreign class File {
+ construct create(path) {}
+
+ foreign write(text)
+ foreign close()
+ }
+
+So you can create a new file given a path. Once you have one, you can write to
+it and then explicitly close it if you want. We also need to make sure the file
+gets closed if the user forgets to and the GC cleans up the object.
+
+### Setting up the VM
+
+Over in the host, first we'll set up the VM:
+
+ :::c
+ #include "wren.h"
+
+ int main(int argc, const char* argv[])
+ {
+ WrenConfiguration config;
+ wrenInitConfiguration(&config);
+
+ config.bindForeignClassFn = bindForeignClass;
+ config.bindForeignMethodFn = bindForeignMethod;
+
+ WrenVM* vm = wrenNewVM(&config);
+ wrenInterpret(vm, "some code...");
+
+ return 0;
+ }
+
+### Binding the foreign class
+
+We give the VM two callbacks. The first is for wiring up the foreign class
+itself:
+
+ :::c
+ WrenForeignClassMethods bindForeignClass(
+ WrenVM* vm, const char* module, const char* className)
+ {
+ WrenForeignClassMethods methods;
+
+ if (strcmp(className, "File") == 0)
+ {
+ methods->allocate = fileAllocate;
+ methods->finalize = fileFinalize;
+ }
+ else
+ {
+ // Unknown class.
+ methods->allocate = NULL;
+ methods->finalize = NULL;
+ }
+
+ return methods;
+ }
+
+When our binding callback is invoked for the File class, we return the allocate
+and finalize functions the VM should call. Allocation looks like:
+
+ :::c
+ #include
+ #include "wren.h"
+
+ void fileAllocate(WrenVM* vm)
+ {
+ FILE** file = (FILE**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(FILE*));
+ const char* path = wrenGetSlotString(vm, 1);
+ *file = fopen(path, "w");
+ }
+
+First we create the instance by calling `wrenSetSlotNewForeign()`. We tell it to
+add enough extra bytes to store a `FILE*` in it, which is C's representation of
+a file handle. We're given back a pointer to the bytes. Since the file handle is
+itself a pointer, we end up with a double indirection, hence the `FILE**`. In
+most cases, you'll just have a single `*`.
+
+We also pull the file path from the slot array. Then we tell C to create a new
+file at that path. That gives us back a new file handle -- a `FILE*` -- and we
+store that back into the foreign instance using `*file`. Now we have a foreign
+object that wraps an open file handle.
+
+The finalizer simply casts the foreign instance's data back to the proper type
+and closes the file:
+
+ :::c
+ void fileFinalize(void* data)
+ {
+ closeFile((FILE**)file);
+ }
+
+It uses this little utility function:
+
+ :::c
+ static void closeFile(FILE** file)
+ {
+ // Already closed.
+ if (*file == NULL) return;
+
+ fclose(*file);
+ *file = NULL;
+ }
+
+This closes the file (if it's not already closed) and also nulls out the pointer
+so that we don't try to use the file after it's been closed.
+
+### Binding the foreign methods
+
+That's the foreign *class* part. Now we have a couple of foreign *methods* to
+handle. The host tells the VM how to find them by giving Wren a pointer to this
+function:
+
+ :::c
+ WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
+ const char* className, bool isStatic, const char* signature)
+ {
+ if (strcmp(className, "File") == 0)
+ {
+ if (!isStatic && strcmp(signature, "write(_)") == 0)
+ {
+ return fileWrite;
+ }
+
+ if (!isStatic && strcmp(signature, "close()") == 0)
+ {
+ return fileClose;
+ }
+ }
+
+ // Unknown method.
+ return NULL;
+ }
+
+When Wren calls this, we look at the class and method name to figure out which
+method its binding, and then return a pointer to the appropriate function. The
+foreign method for writing to the file is:
+
+ :::c
+ void fileWrite(WrenVM* vm)
+ {
+ FILE** file = (FILE**)wrenGetSlotForeign(vm, 0);
+
+ // Make sure the file is still open.
+ if (*file == NULL)
+ {
+ wrenSetSlotString(vm, 0, "Cannot write to a closed file.");
+ wrenAbortFiber(vm, 0);
+ return;
+ }
+
+ const char* text = wrenGetSlotString(vm, 1);
+ fwrite(text, sizeof(char), strlen(text), *file);
+ }
+
+We use `wrenGetSlotForeign()` to pull the foreign data out of the slot array.
+Since this method is called on the file itself, the foreign object is in slot
+zero. We take the resulting pointer and cast it to a pointer of the proper type.
+Again, because our foreign data is *itself* a pointer, we get a pointer to a
+pointer.
+
+We do a little sanity checking to make sure the user isn't writing to a file
+they already closed. If not, we call `fwrite()` to write to the file.
+
+The other method is `close()` to let them explicitly close the file:
+
+ void fileClose(WrenVM* vm)
+ {
+ closeFile(file);
+ }
+
+It uses the same helper we defined above. And that's it, a complete foreign
+class with a finalizer and a couple of foreign methods. In Wren, you can use it
+like so:
+
+ :::wren
+ var file = File.create("some/path.txt")
+ file.write("some text")
+ file.close()
Configuring the VM →
← Calling C from Wren
diff --git a/doc/site/embedding/template.html b/doc/site/embedding/template.html
index d5a63018..e0fc88dd 100644
--- a/doc/site/embedding/template.html
+++ b/doc/site/embedding/template.html
@@ -29,7 +29,6 @@
Calling C from Wren
Storing C Data
Configuring the VM
- Application Lifecycle
@@ -49,7 +48,6 @@
Calling C from Wren
Storing C Data
Configuring the VM
- Application Lifecycle
|
|