Merge branch 'master' into unify-modules-and-classes

# Conflicts:
#	src/module/io.wren
#	src/module/io.wren.inc
#	test/api/call.wren
#	test/api/returns.wren
This commit is contained in:
Bob Nystrom
2016-01-01 11:49:00 -08:00
75 changed files with 1588 additions and 675 deletions

View File

@ -61,8 +61,8 @@ any "if I got two arguments do this..." logic.
## Getters
Some methods exist to expose some stored or computed property of an object.
These are *getters* and have no parentheses:
Some methods exist to expose a stored or computed property of an object. These
are *getters* and have no parentheses:
:::wren
"string".count //> 6
@ -70,8 +70,9 @@ These are *getters* and have no parentheses:
1.23.sin //> 0.9424888019317
[1, 2, 3].isEmpty //> false
Sometimes you have a method doesn't need any parameters, but modifies the object
or has some other side effect. For those, it's better to use empty parentheses:
Sometimes you have a method that doesn't need any parameters, but modifies the
object or has some other side effect. For those, it's better to use empty
parentheses:
:::wren
list.clear()
@ -89,7 +90,7 @@ like a getter and a `()` method like a `()` method. These don't work:
:::wren
"string".count()
list.clear
[1, 2, 3].clear
## Setters
@ -104,7 +105,7 @@ language's perspective, the above line is just a call to the `height=(_)`
method, passing in `74`.
Since the `=(_)` is in the setter's signature, an object can have both a getter
and setter with the same name without any collision. This way, you can have
and setter with the same name without a collision. This way, you can have
read/write properties.
## Operators

View File

@ -21,7 +21,7 @@ Prints a single newline to the console.
### System.**print**(object)
Prints [object] to the console followed by a newline. If not already a string,
Prints `object` to the console followed by a newline. If not already a string,
the object is converted to a string by calling `toString` on it.
:::wren
@ -29,7 +29,7 @@ the object is converted to a string by calling `toString` on it.
### System.**printAll**(sequence)
Iterates over [sequence] and prints each element, then prints a single newline
Iterates over `sequence` and prints each element, then prints a single newline
at the end. Each element is converted to a string by calling `toString` on it.
:::wren

View File

@ -0,0 +1,10 @@
^title Directory Class
A directory on the file system.
## Static Methods
### Directory.**list**(path)
Lists the contents of the directory at `path`. Returns a sorted list of path
strings for all of the contents of the directory.

View File

@ -1,7 +1,88 @@
^title File Class
**TODO**
Lets you work with files on the file system. An instance of this class
represents an open file with a file descriptor.
When you are done with a file object, it's a good idea to explicitly close it.
If you don't, the GC will close it when the file is no longer used and gets
finalized, but that may take a while. In the meantime, leaving it open wastes
a file descriptor.
## Static Methods
### File.**open**(path, fn)
Opens the file at `path` and passes it to `fn`. After the function returns, the
file is automatically closed.
:::wren
File.open("words.txt") {|file|
file.readBytes(5)
}
### File.**read**(path)
Reads the entire contents of the file at `path` and returns it as a string.
:::wren
File.read("words.txt")
The encoding or decoding is done. If the file is UTF-8, then the resulting
string will be a UTF-8 string. Otherwise, it will be a string of bytes in
whatever encoding the file uses.
### File.**size**(path)
Returns the size in bytes of the contents of the file at `path`.
### File.**stat**(path)
"Stats" the file or directory at `path`. Returns a [Stat][] object describing
the low-level details of the file system entry.
[stat]: stat.html
## Constructors
### File.**open**(path)
Opens the file at `path` for reading.
## Methods
**TODO**
### **descriptor**
The numeric file descriptor used to access the file.
### **isOpen**
Whether the file is still open or has been closed.
### **size**
The size of the contents of the file in bytes.
### **close**()
Closes the file. After calling this, you can read or write from it.
### **readBytes**(count)
Reads up to `count` bytes starting from the beginning of the file.
:::wren
// Assume this file contains "I am a file!".
File.open("example.txt") {|file|
System.print(file.readBytes(6)) //> I am a
}
### **readBytes**(count, offset)
Reads up to `count` bytes starting at `offset` bytes from the beginning of
the file.
:::wren
// Assume this file contains "I am a file!".
File.open("example.txt") {|file|
System.print(file.readBytes(6, 2)) //> am a f
}

View File

@ -1,6 +1,8 @@
^title Module "io"
**TODO**
Provides access to operating system streams and the file system.
* [Directory](directory.html)
* [File](file.html)
* [Stat](stat.html)
* [Stdin](stdin.html)

View File

@ -0,0 +1,50 @@
^title Stat Class
Contains the data returned by [File.stat()][stat].
[stat]: file.html#file.stat(path)
## Methods
### **device**
The ID of the device containing the entry.
### **inode**
The [inode][] number of the entry.
[inode]: https://en.wikipedia.org/wiki/Inode
### **mode**
A bit field describing the entry's type and protection flags.
### **linkCount**
The number of hard links to the entry.
### **user**
Numeric user ID of the file's owner.
### **group**
Numeric group ID of the file's owner.
### **specialDevice**
The device ID for the entry, if it's a special file.
### **size**
The size of the entry in bytes.
### **blockSize**
The preferred block size in bytes for interacting with the file. It may vary
from file to file.
### **blockCount**
The number of system blocks allocated on disk for the file.

View File

@ -1,7 +1,12 @@
^title Stdin Class
**TODO**
The standard input stream.
## Methods
## Static Methods
**TODO**
### **readLine**()
Reads one line of input from stdin. Blocks the current fiber until a full line
of input has been received.
Returns the string of input or `null` if stdin is closed.

View File

@ -27,6 +27,7 @@
<section>
<h2>io classes</h2>
<ul>
<li><a href="directory.html">Directory</a></li>
<li><a href="file.html">File</a></li>
<li><a href="stdin.html">Stdin</a></li>
</ul>

View File

@ -7,10 +7,12 @@
#include "scheduler.wren.inc"
#include "timer.wren.inc"
extern void directoryList(WrenVM* vm);
extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(WrenVM* vm);
extern void fileFinalize(void* data);
extern void fileOpen(WrenVM* vm);
extern void fileSizePath(WrenVM* vm);
extern void fileStatPath(WrenVM* vm);
extern void fileClose(WrenVM* vm);
extern void fileDescriptor(WrenVM* vm);
extern void fileReadBytes(WrenVM* vm);
@ -28,14 +30,14 @@ extern void timerStartTimer(WrenVM* vm);
// If you add a new method to the longest class below, make sure to bump this.
// Note that it also includes an extra slot for the sentinel value indicating
// the end of the list.
#define MAX_METHODS_PER_CLASS 9
#define MAX_METHODS_PER_CLASS 10
// The maximum number of foreign classes a single built-in module defines.
//
// If you add a new class to the largest module below, make sure to bump this.
// Note that it also includes an extra slot for the sentinel value indicating
// the end of the list.
#define MAX_CLASSES_PER_MODULE 3
#define MAX_CLASSES_PER_MODULE 4
// Describes one foreign method in a class.
typedef struct
@ -82,19 +84,24 @@ typedef struct
#define METHOD(signature, fn) { false, signature, fn },
#define STATIC_METHOD(signature, fn) { true, signature, fn },
#define FINALIZER(fn) { true, "<finalize>", (WrenForeignMethodFn)fn },
// The array of built-in modules.
static ModuleRegistry modules[] =
{
MODULE(io)
CLASS(Directory)
STATIC_METHOD("list_(_,_)", directoryList)
END_CLASS
CLASS(File)
STATIC_METHOD("<allocate>", fileAllocate)
STATIC_METHOD("<finalize>", fileFinalize)
FINALIZER(fileFinalize)
STATIC_METHOD("open_(_,_)", fileOpen)
STATIC_METHOD("sizePath_(_,_)", fileSizePath)
STATIC_METHOD("statPath_(_,_)", fileStatPath)
METHOD("close_(_)", fileClose)
METHOD("descriptor", fileDescriptor)
METHOD("readBytes_(_,_)", fileReadBytes)
METHOD("readBytes_(_,_,_)", fileReadBytes)
METHOD("size_(_)", fileSize)
END_CLASS
CLASS(Stdin)
@ -204,7 +211,7 @@ WrenForeignClassMethods bindBuiltInForeignClass(
if (clas == NULL) return methods;
methods.allocate = findMethod(clas, true, "<allocate>");
methods.finalize = findMethod(clas, true, "<finalize>");
methods.finalize = (WrenFinalizerFn)findMethod(clas, true, "<finalize>");
return methods;
}

View File

@ -38,6 +38,12 @@ typedef void* (*WrenReallocateFn)(void* memory, size_t newSize);
// A function callable from Wren code, but implemented in C.
typedef void (*WrenForeignMethodFn)(WrenVM* vm);
// A finalizer function for freeing resources owned by an instance of a foreign
// class. Unlike most foreign methods, finalizers do not have access to the VM
// and should not interact with it since it's in the middle of a garbage
// collection.
typedef void (*WrenFinalizerFn)(void* data);
// Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
@ -64,7 +70,7 @@ typedef struct
// foreign object's memory.
//
// This may be `NULL` if the foreign class does not need to finalize.
WrenForeignMethodFn finalize;
WrenFinalizerFn finalize;
} WrenForeignClassMethods;
// Returns a pair of pointers to the foreign methods used to allocate and
@ -120,7 +126,7 @@ typedef struct
//
// If this is `NULL`, Wren discards any printed text.
WrenWriteFn writeFn;
// The number of bytes Wren will allocate before triggering the first garbage
// collection.
//
@ -183,53 +189,71 @@ void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source);
// Creates a handle that can be used to invoke a method with [signature] on the
// object in [module] currently stored in top-level [variable].
// Creates a handle that can be used to invoke a method with [signature] on
// using a receiver and arguments that are set up on the stack.
//
// This handle can be used repeatedly to directly invoke that method from C
// code using [wrenCall].
//
// When done with this handle, it must be released using [wrenReleaseValue].
WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
const char* signature);
// When you are done with this handle, it must be released using
// [wrenReleaseValue].
WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// Calls [method], passing in a series of arguments whose types must match the
// specifed [argTypes]. This is a string where each character identifies the
// type of a single argument, in order. The allowed types are:
// Calls [method], using the receiver and arguments previously set up on the
// stack.
//
// - "b" - A C `int` converted to a Wren Bool.
// - "d" - A C `double` converted to a Wren Num.
// - "i" - A C `int` converted to a Wren Num.
// - "s" - A C null-terminated `const char*` converted to a Wren String. Wren
// will allocate its own string and copy the characters from this, so
// you don't have to worry about the lifetime of the string you pass to
// Wren.
// - "a" - An array of bytes converted to a Wren String. This requires two
// consecutive arguments in the argument list: `const char*` pointing
// to the array of bytes, followed by an `int` defining the length of
// the array. This is used when the passed string may contain null
// bytes, or just to avoid the implicit `strlen()` call of "s" if you
// happen to already know the length.
// - "v" - A previously acquired WrenValue*. Passing this in does not implicitly
// release the value. If the passed argument is NULL, this becomes a
// Wren NULL.
// [method] must have been created by a call to [wrenMakeCallHandle]. The
// arguments to the method must be already on the stack. The receiver should be
// in slot 0 with the remaining arguments following it, in order. It is an
// error if the number of arguments provided does not match the method's
// signature.
//
// [method] must have been created by a call to [wrenGetMethod]. If
// [returnValue] is not `NULL`, the return value of the method will be stored
// in a new [WrenValue] that [returnValue] will point to. Don't forget to
// release it, when done with it.
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method,
WrenValue** returnValue,
const char* argTypes, ...);
WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method,
WrenValue** returnValue,
const char* argTypes, va_list args);
// After this returns, you can access the return value from slot 0 on the stack.
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method);
// Releases the reference stored in [value]. After calling this, [value] can no
// longer be used.
void wrenReleaseValue(WrenVM* vm, WrenValue* value);
// The following functions are intended to be called from foreign methods or
// finalizers. The interface Wren provides to a foreign method is like a
// register machine: you are given a numbered array of slots that values can be
// read from and written to. Values always live in a slot (unless explicitly
// captured using wrenGetSlotValue(), which ensures the garbage collector can
// find them.
//
// When your foreign function is called, you are given one slot for the receiver
// and each argument to the method. The receiver is in slot 0 and the arguments
// are in increasingly numbered slots after that. You are free to read and
// write to those slots as you want. If you want more slots to use as scratch
// space, you can call wrenEnsureSlots() to add more.
//
// When your function returns, every slot except slot zero is discarded and the
// value in slot zero is used as the return value of the method. If you don't
// store a return value in that slot yourself, it will retain its previous
// value, the receiver.
//
// While Wren is dynamically typed, C is not. This means the C interface has to
// support the various types of primitive values a Wren variable can hold: bool,
// double, string, etc. If we supported this for every operation in the C API,
// there would be a combinatorial explosion of functions, like "get a
// double-valued element from a list", "insert a string key and double value
// into a map", etc.
//
// To avoid that, the only way to convert to and from a raw C value is by going
// into and out of a slot. All other functions work with values already in a
// slot. So, to add an element to a list, you put the list in one slot, and the
// element in another. Then there is a single API function wrenInsertInList()
// that takes the element out of that slot and puts it into the list.
//
// The goal of this API is to be easy to use while not compromising performance.
// The latter means it does not do type or bounds checking at runtime except
// using assertions which are generally removed from release builds. C is an
// unsafe language, so it's up to you to be careful to use it correctly. In
// return, you get a very fast FFI.
// TODO: Generalize this to look up a foreign class in any slot and place the
// object in a desired slot.
// This must be called once inside a foreign class's allocator function.
//
// It tells Wren how many bytes of raw data need to be stored in the foreign
@ -237,55 +261,60 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value);
// the foreign object's data.
void* wrenAllocateForeign(WrenVM* vm, size_t size);
// Returns the number of arguments available to the current foreign method.
int wrenGetArgumentCount(WrenVM* vm);
// Returns the number of slots available to the current foreign method.
int wrenGetSlotCount(WrenVM* vm);
// The following functions read one of the arguments passed to a foreign call.
// They may only be called while within a function provided to
// [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked.
// Ensures that the foreign method stack has at least [numSlots] available for
// use, growing the stack if needed.
//
// They retreive the argument at a given index which ranges from 0 to the number
// of parameters the method expects. The zeroth parameter is used for the
// receiver of the method. For example, given a foreign method "foo" on String
// invoked like:
// Does not shrink the stack if it has more than enough slots.
//
// "receiver".foo("one", "two", "three")
//
// The foreign function will be able to access the arguments like so:
//
// 0: "receiver"
// 1: "one"
// 2: "two"
// 3: "three"
//
// It is an error to pass an invalid argument index.
// It is an error to call this from a finalizer.
void wrenEnsureSlots(WrenVM* vm, int numSlots);
// Reads a boolean argument for a foreign call. Returns false if the argument
// is not a boolean.
bool wrenGetArgumentBool(WrenVM* vm, int index);
// Reads a boolean value from [slot].
//
// It is an error to call this if the slot does not contain a boolean value.
bool wrenGetSlotBool(WrenVM* vm, int slot);
// Reads a numeric argument for a foreign call. Returns 0 if the argument is not
// a number.
double wrenGetArgumentDouble(WrenVM* vm, int index);
// Reads a foreign object argument for a foreign call and returns a pointer to
// the foreign data stored with it. Returns NULL if the argument is not a
// foreign object.
void* wrenGetArgumentForeign(WrenVM* vm, int index);
// Reads an string argument for a foreign call. Returns NULL if the argument is
// not a string.
// Reads a byte array from [slot].
//
// The memory for the returned string is owned by Wren. You can inspect it
// while in your foreign function, but cannot keep a pointer to it after the
// while in your foreign method, but cannot keep a pointer to it after the
// function returns, since the garbage collector may reclaim it.
const char* wrenGetArgumentString(WrenVM* vm, int index);
//
// Returns a pointer to the first byte of the array and fill [length] with the
// number of bytes in the array.
//
// It is an error to call this if the slot does not contain a string.
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
// Creates a handle for the value passed as an argument to a foreign call.
// Reads a number from [slot].
//
// It is an error to call this if the slot does not contain a number.
double wrenGetSlotDouble(WrenVM* vm, int slot);
// Reads a foreign object from [slot] and returns a pointer to the foreign data
// stored with it.
//
// It is an error to call this if the slot does not contain an instance of a
// foreign class.
void* wrenGetSlotForeign(WrenVM* vm, int slot);
// Reads a string from [slot].
//
// The memory for the returned string is owned by Wren. You can inspect it
// while in your foreign method, but cannot keep a pointer to it after the
// function returns, since the garbage collector may reclaim it.
//
// It is an error to call this if the slot does not contain a string.
const char* wrenGetSlotString(WrenVM* vm, int slot);
// Creates a handle for the value stored in [slot].
//
// This will prevent the object that is referred to from being garbage collected
// until the handle is released by calling [wrenReleaseValue()].
WrenValue* wrenGetArgumentValue(WrenVM* vm, int index);
WrenValue* wrenGetSlotValue(WrenVM* vm, int slot);
// The following functions provide the return value for a foreign method back
// to Wren. Like above, they may only be called during a foreign call invoked
@ -296,27 +325,47 @@ WrenValue* wrenGetArgumentValue(WrenVM* vm, int index);
// call one of these once. It is an error to access any of the foreign calls
// arguments after one of these has been called.
// Provides a boolean return value for a foreign call.
void wrenReturnBool(WrenVM* vm, bool value);
// Stores the boolean [value] in [slot].
void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
// Provides a numeric return value for a foreign call.
void wrenReturnDouble(WrenVM* vm, double value);
// Stores the array [length] of [bytes] in [slot].
//
// The bytes are copied to a new string within Wren's heap, so you can free
// memory used by them after this is called.
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
// Provides a string return value for a foreign call.
//
// The [text] will be copied to a new string within Wren's heap, so you can
// free memory used by it after this is called.
//
// If [length] is non-zero, Wren copies that many bytes from [text], including
// any null bytes. If it is -1, then the length of [text] is calculated using
// `strlen()`. If the string may contain any null bytes in the middle, then you
// must pass an explicit length.
void wrenReturnString(WrenVM* vm, const char* text, int length);
// Stores the numeric [value] in [slot].
void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// Provides the return value for a foreign call.
// Stores a new empty list in [slot].
void wrenSetSlotNewList(WrenVM* vm, int slot);
// Stores null in [slot].
void wrenSetSlotNull(WrenVM* vm, int slot);
// Stores the string [text] in [slot].
//
// This uses the value referred to by the handle as the return value, but it
// does not release the handle.
void wrenReturnValue(WrenVM* vm, WrenValue* value);
// The [text] is copied to a new string within Wren's heap, so you can free
// memory used by it after this is called. The length is calculated using
// [strlen()]. If the string may contain any null bytes in the middle, then you
// should use [wrenSetSlotBytes()] instead.
void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
// Stores the value captured in [value] in [slot].
//
// This does not release the handle for the value.
void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value);
// Takes the value stored at [elementSlot] and inserts it into the list stored
// at [listSlot] at [index].
//
// As in Wren, negative indexes can be used to insert from the end. To append
// an element, use `-1` for the index.
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
// Looks up the top level variable with [name] in [module] and stores it in
// [slot].
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot);
#endif

View File

@ -18,7 +18,10 @@ typedef struct sFileRequestData
static const int stdinDescriptor = 0;
// Handle to Stdin.onData_(). Called when libuv provides data on stdin.
// Handle to the Stdin class object.
static WrenValue* stdinClass = NULL;
// Handle to an onData_() method call. Called when libuv provides data on stdin.
static WrenValue* stdinOnData = NULL;
// The stream used to read from stdin. Initialized on the first read.
@ -34,6 +37,12 @@ static void shutdownStdin()
stdinStream = NULL;
}
if (stdinClass != NULL)
{
wrenReleaseValue(getVM(), stdinClass);
stdinClass = NULL;
}
if (stdinOnData != NULL)
{
wrenReleaseValue(getVM(), stdinOnData);
@ -46,26 +55,6 @@ void ioShutdown()
shutdownStdin();
}
void fileAllocate(WrenVM* vm)
{
// Store the file descriptor in the foreign data, so that we can get to it
// in the finalizer.
int* fd = (int*)wrenAllocateForeign(vm, sizeof(int));
*fd = (int)wrenGetArgumentDouble(vm, 1);
}
void fileFinalize(WrenVM* vm)
{
int fd = *(int*)wrenGetArgumentForeign(vm, 0);
// Already closed.
if (fd == -1) return;
uv_fs_t request;
uv_fs_close(getLoop(), &request, fd, NULL);
uv_fs_req_cleanup(&request);
}
// If [request] failed with an error, sends the runtime error to the VM and
// frees the request.
//
@ -73,12 +62,12 @@ void fileFinalize(WrenVM* vm)
static bool handleRequestError(uv_fs_t* request)
{
if (request->result >= 0) return false;
FileRequestData* data = (FileRequestData*)request->data;
WrenValue* fiber = (WrenValue*)data->fiber;
schedulerResumeError(fiber, uv_strerror((int)request->result));
free(data);
uv_fs_req_cleanup(request);
free(request);
@ -89,10 +78,10 @@ static bool handleRequestError(uv_fs_t* request)
uv_fs_t* createRequest(WrenValue* fiber)
{
uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t));
FileRequestData* data = (FileRequestData*)malloc(sizeof(FileRequestData));
data->fiber = fiber;
request->data = data;
return request;
}
@ -104,84 +93,189 @@ WrenValue* freeRequest(uv_fs_t* request)
{
FileRequestData* data = (FileRequestData*)request->data;
WrenValue* fiber = data->fiber;
free(data);
uv_fs_req_cleanup(request);
free(request);
return fiber;
}
static void openCallback(uv_fs_t* request)
static void directoryListCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
uv_dirent_t entry;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 2);
while (uv_fs_scandir_next(request, &entry) != UV_EOF)
{
wrenSetSlotString(vm, 1, entry.name);
wrenInsertInList(vm, 2, -1, 1);
}
schedulerResume(freeRequest(request), true);
schedulerFinishResume();
}
void directoryList(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
// TODO: Check return.
uv_fs_scandir(getLoop(), request, path, 0, directoryListCallback);
}
void fileAllocate(WrenVM* vm)
{
// Store the file descriptor in the foreign data, so that we can get to it
// in the finalizer.
int* fd = (int*)wrenAllocateForeign(vm, sizeof(int));
*fd = (int)wrenGetSlotDouble(vm, 1);
}
void fileFinalize(void* data)
{
int fd = *(int*)data;
// Already closed.
if (fd == -1) return;
uv_fs_t request;
uv_fs_close(getLoop(), &request, fd, NULL);
uv_fs_req_cleanup(&request);
}
static void fileOpenCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
double fd = (double)request->result;
WrenValue* fiber = freeRequest(request);
schedulerResumeDouble(fiber, fd);
schedulerResume(freeRequest(request), true);
wrenSetSlotDouble(getVM(), 2, fd);
schedulerFinishResume();
}
void fileOpen(WrenVM* vm)
{
const char* path = wrenGetArgumentString(vm, 1);
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2));
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
// TODO: Allow controlling flags and modes.
uv_fs_open(getLoop(), request, path, O_RDONLY, 0, openCallback);
uv_fs_open(getLoop(), request, path, O_RDONLY, 0, fileOpenCallback);
}
// Called by libuv when the stat call for size completes.
static void sizeCallback(uv_fs_t* request)
static void fileSizeCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
double size = (double)request->statbuf.st_size;
WrenValue* fiber = freeRequest(request);
schedulerResumeDouble(fiber, size);
schedulerResume(freeRequest(request), true);
wrenSetSlotDouble(getVM(), 2, size);
schedulerFinishResume();
}
void fileSizePath(WrenVM* vm)
{
const char* path = wrenGetArgumentString(vm, 1);
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2));
uv_fs_stat(getLoop(), request, path, sizeCallback);
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
uv_fs_stat(getLoop(), request, path, fileSizeCallback);
}
static void closeCallback(uv_fs_t* request)
// Called by libuv when the stat call completes.
static void fileStatPathCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
WrenValue* fiber = freeRequest(request);
schedulerResume(fiber);
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 4);
wrenSetSlotNewList(vm, 2);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_dev);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_ino);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_mode);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_nlink);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_uid);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_gid);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_rdev);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_size);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blksize);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blocks);
wrenInsertInList(vm, 2, -1, 3);
// TODO: Include access, modification, and change times once we figure out
// how we want to represent it.
// time_t st_atime; /* time of last access */
// time_t st_mtime; /* time of last modification */
// time_t st_ctime; /* time of last status change */
schedulerResume(freeRequest(request), true);
schedulerFinishResume();
}
void fileStatPath(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
uv_fs_stat(getLoop(), request, path, fileStatPathCallback);
}
static void fileCloseCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
schedulerResume(freeRequest(request), false);
}
void fileClose(WrenVM* vm)
{
int* foreign = (int*)wrenGetArgumentForeign(vm, 0);
int* foreign = (int*)wrenGetSlotForeign(vm, 0);
int fd = *foreign;
// If it's already closed, we're done.
if (fd == -1)
{
wrenReturnBool(vm, true);
wrenSetSlotBool(vm, 0, true);
return;
}
// Mark it closed immediately.
*foreign = -1;
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1));
uv_fs_close(getLoop(), request, fd, closeCallback);
wrenReturnBool(vm, false);
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1));
uv_fs_close(getLoop(), request, fd, fileCloseCallback);
wrenSetSlotBool(vm, 0, false);
}
void fileDescriptor(WrenVM* vm)
{
int* foreign = (int*)wrenGetArgumentForeign(vm, 0);
int* foreign = (int*)wrenGetSlotForeign(vm, 0);
int fd = *foreign;
wrenReturnDouble(vm, fd);
wrenSetSlotDouble(vm, 0, fd);
}
static void fileReadBytesCallback(uv_fs_t* request)
@ -190,43 +284,45 @@ static void fileReadBytesCallback(uv_fs_t* request)
FileRequestData* data = (FileRequestData*)request->data;
uv_buf_t buffer = data->buffer;
WrenValue* fiber = freeRequest(request);
size_t count = request->result;
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
// embedding API supported a way to *give* it bytes that were previously
// allocated using Wren's own allocator.
schedulerResumeBytes(fiber, buffer.base, buffer.len);
schedulerResume(freeRequest(request), true);
wrenSetSlotBytes(getVM(), 2, buffer.base, count);
schedulerFinishResume();
// TODO: Likewise, freeing this after we resume is lame.
free(buffer.base);
}
void fileReadBytes(WrenVM* vm)
{
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2));
int fd = *(int*)wrenGetArgumentForeign(vm, 0);
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 3));
int fd = *(int*)wrenGetSlotForeign(vm, 0);
// TODO: Assert fd != -1.
FileRequestData* data = (FileRequestData*)request->data;
size_t length = (size_t)wrenGetArgumentDouble(vm, 1);
size_t length = (size_t)wrenGetSlotDouble(vm, 1);
size_t offset = (size_t)wrenGetSlotDouble(vm, 2);
data->buffer.len = length;
data->buffer.base = (char*)malloc(length);
// TODO: Allow passing in offset.
uv_fs_read(getLoop(), request, fd, &data->buffer, 1, 0, fileReadBytesCallback);
uv_fs_read(getLoop(), request, fd, &data->buffer, 1, offset,
fileReadBytesCallback);
}
void fileSize(WrenVM* vm)
{
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1));
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1));
int fd = *(int*)wrenGetArgumentForeign(vm, 0);
int fd = *(int*)wrenGetSlotForeign(vm, 0);
// TODO: Assert fd != -1.
uv_fs_fstat(getLoop(), request, fd, sizeCallback);
uv_fs_fstat(getLoop(), request, fd, fileSizeCallback);
}
static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
@ -240,26 +336,42 @@ static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead,
const uv_buf_t* buffer)
{
// If stdin was closed, send null to let io.wren know.
if (numRead == UV_EOF)
{
wrenCall(getVM(), stdinOnData, NULL, "v", NULL);
shutdownStdin();
return;
}
WrenVM* vm = getVM();
// TODO: Handle other errors.
if (stdinClass == NULL)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "io", "Stdin", 0);
stdinClass = wrenGetSlotValue(vm, 0);
}
if (stdinOnData == NULL)
{
stdinOnData = wrenGetMethod(getVM(), "io", "Stdin", "onData_(_)");
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
}
// If stdin was closed, send null to let io.wren know.
if (numRead == UV_EOF)
{
wrenEnsureSlots(vm, 2);
wrenSetSlotValue(vm, 0, stdinClass);
wrenSetSlotNull(vm, 1);
wrenCall(vm, stdinOnData);
shutdownStdin();
return;
}
// TODO: Handle other errors.
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
// embedding API supported a way to *give* it bytes that were previously
// allocated using Wren's own allocator.
wrenCall(getVM(), stdinOnData, NULL, "a", buffer->base, numRead);
wrenEnsureSlots(vm, 2);
wrenSetSlotValue(vm, 0, stdinClass);
wrenSetSlotBytes(vm, 1, buffer->base, numRead);
wrenCall(vm, stdinOnData);
// TODO: Likewise, freeing this after we resume is lame.
free(buffer->base);
}
@ -284,7 +396,7 @@ void stdinReadStart(WrenVM* vm)
stdinStream = (uv_stream_t*)handle;
}
}
uv_read_start(stdinStream, allocCallback, stdinReadCallback);
// TODO: Check return.
}

View File

@ -1,5 +1,16 @@
import "scheduler" for Scheduler
class Directory {
static def list(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
list_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
foreign static def list_(path, fiber)
}
foreign class File {
static def open(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
@ -33,6 +44,13 @@ foreign class File {
return Scheduler.runNextScheduled_()
}
static def stat(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
statPath_(path, Fiber.current)
return Stat.new_(Scheduler.runNextScheduled_())
}
construct new_(fd) {}
def close() {
@ -40,6 +58,8 @@ foreign class File {
Scheduler.runNextScheduled_()
}
foreign def descriptor
def isOpen { descriptor != -1 }
def size {
@ -49,25 +69,48 @@ foreign class File {
return Scheduler.runNextScheduled_()
}
def readBytes(count) {
def readBytes(count) { readBytes(count, 0) }
def readBytes(count, offset) {
if (!isOpen) Fiber.abort("File is not open.")
if (!(count is Num)) Fiber.abort("Count must be an integer.")
if (!count.isInteger) Fiber.abort("Count must be an integer.")
if (count < 0) Fiber.abort("Count cannot be negative.")
readBytes_(count, Fiber.current)
if (!(offset is Num)) Fiber.abort("Offset must be an integer.")
if (!offset.isInteger) Fiber.abort("Offset must be an integer.")
if (offset < 0) Fiber.abort("Offset cannot be negative.")
readBytes_(count, offset, Fiber.current)
return Scheduler.runNextScheduled_()
}
foreign static def open_(path, fiber)
foreign static def sizePath_(path, fiber)
foreign static def statPath_(path, fiber)
foreign def close_(fiber)
foreign def descriptor
foreign def readBytes_(count, fiber)
foreign def readBytes_(count, start, fiber)
foreign def size_(fiber)
}
class Stat {
construct new_(fields) {
_fields = fields
}
def device { _fields[0] }
def inode { _fields[1] }
def mode { _fields[2] }
def linkCount { _fields[3] }
def user { _fields[4] }
def group { _fields[5] }
def specialDevice { _fields[6] }
def size { _fields[7] }
def blockSize { _fields[8] }
def blockCount { _fields[9] }
}
class Stdin {
static def readLine() {
if (__isClosed == true) {
@ -90,6 +133,7 @@ class Stdin {
readStop_()
if (__line != null) {
// Emit the last line.
var line = __line
__line = null
if (__waitingFiber != null) __waitingFiber.transfer(line)

View File

@ -2,6 +2,17 @@
static const char* ioModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Directory {\n"
" static def list(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" list_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static def list_(path, fiber)\n"
"}\n"
"\n"
"foreign class File {\n"
" static def open(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
@ -35,6 +46,13 @@ static const char* ioModuleSource =
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" static def stat(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" statPath_(path, Fiber.current)\n"
" return Stat.new_(Scheduler.runNextScheduled_())\n"
" }\n"
"\n"
" construct new_(fd) {}\n"
"\n"
" def close() {\n"
@ -42,6 +60,8 @@ static const char* ioModuleSource =
" Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign def descriptor\n"
"\n"
" def isOpen { descriptor != -1 }\n"
"\n"
" def size {\n"
@ -51,25 +71,48 @@ static const char* ioModuleSource =
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" def readBytes(count) {\n"
" def readBytes(count) { readBytes(count, 0) }\n"
"\n"
" def readBytes(count, offset) {\n"
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
" if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\n"
" if (!count.isInteger) Fiber.abort(\"Count must be an integer.\")\n"
" if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n"
"\n"
" readBytes_(count, Fiber.current)\n"
" if (!(offset is Num)) Fiber.abort(\"Offset must be an integer.\")\n"
" if (!offset.isInteger) Fiber.abort(\"Offset must be an integer.\")\n"
" if (offset < 0) Fiber.abort(\"Offset cannot be negative.\")\n"
"\n"
" readBytes_(count, offset, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static def open_(path, fiber)\n"
" foreign static def sizePath_(path, fiber)\n"
" foreign static def statPath_(path, fiber)\n"
"\n"
" foreign def close_(fiber)\n"
" foreign def descriptor\n"
" foreign def readBytes_(count, fiber)\n"
" foreign def readBytes_(count, start, fiber)\n"
" foreign def size_(fiber)\n"
"}\n"
"\n"
"class Stat {\n"
" construct new_(fields) {\n"
" _fields = fields\n"
" }\n"
"\n"
" def device { _fields[0] }\n"
" def inode { _fields[1] }\n"
" def mode { _fields[2] }\n"
" def linkCount { _fields[3] }\n"
" def user { _fields[4] }\n"
" def group { _fields[5] }\n"
" def specialDevice { _fields[6] }\n"
" def size { _fields[7] }\n"
" def blockSize { _fields[8] }\n"
" def blockCount { _fields[9] }\n"
"}\n"
"\n"
"class Stdin {\n"
" static def readLine() {\n"
" if (__isClosed == true) {\n"
@ -92,6 +135,7 @@ static const char* ioModuleSource =
" readStop_()\n"
"\n"
" if (__line != null) {\n"
" // Emit the last line.\n"
" var line = __line\n"
" __line = null\n"
" if (__waitingFiber != null) __waitingFiber.transfer(line)\n"

View File

@ -7,30 +7,19 @@
#include "wren.h"
#include "vm.h"
// A handle to the "Scheduler" class object. Used to call static methods on it.
static WrenValue* schedulerClass;
// This method resumes a fiber that is suspended waiting on an asynchronous
// operation. The first resumes it with zero arguments, and the second passes
// one.
static WrenValue* resume;
static WrenValue* resumeWithArg;
static WrenValue* resume1;
static WrenValue* resume2;
static WrenValue* resumeError;
void schedulerCaptureMethods(WrenVM* vm)
static void resume(WrenValue* method)
{
resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)");
resumeWithArg = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_,_)");
resumeError = wrenGetMethod(vm, "scheduler", "Scheduler", "resumeError_(_,_)");
}
static void callResume(WrenValue* resumeMethod, WrenValue* fiber,
const char* argTypes, ...)
{
va_list args;
va_start(args, argTypes);
WrenInterpretResult result = wrenCallVarArgs(getVM(), resumeMethod, NULL,
argTypes, args);
va_end(args);
wrenReleaseValue(getVM(), fiber);
WrenInterpretResult result = wrenCall(getVM(), method);
// If a runtime error occurs in response to an async operation and nothing
// catches the error in the fiber, then exit the CLI.
@ -41,34 +30,50 @@ static void callResume(WrenValue* resumeMethod, WrenValue* fiber,
}
}
void schedulerResume(WrenValue* fiber)
void schedulerCaptureMethods(WrenVM* vm)
{
callResume(resume, fiber, "v", fiber);
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "scheduler", "Scheduler", 0);
schedulerClass = wrenGetSlotValue(vm, 0);
resume1 = wrenMakeCallHandle(vm, "resume_(_)");
resume2 = wrenMakeCallHandle(vm, "resume_(_,_)");
resumeError = wrenMakeCallHandle(vm, "resumeError_(_,_)");
}
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length)
void schedulerResume(WrenValue* fiber, bool hasArgument)
{
callResume(resumeWithArg, fiber, "va", fiber, bytes, length);
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 2 + (hasArgument ? 1 : 0));
wrenSetSlotValue(vm, 0, schedulerClass);
wrenSetSlotValue(vm, 1, fiber);
wrenReleaseValue(vm, fiber);
// If we don't need to wait for an argument to be stored on the stack, resume
// it now.
if (!hasArgument) resume(resume1);
}
void schedulerResumeDouble(WrenValue* fiber, double value)
void schedulerFinishResume()
{
callResume(resumeWithArg, fiber, "vd", fiber, value);
}
void schedulerResumeString(WrenValue* fiber, const char* text)
{
callResume(resumeWithArg, fiber, "vs", fiber, text);
resume(resume2);
}
void schedulerResumeError(WrenValue* fiber, const char* error)
{
callResume(resumeError, fiber, "vs", fiber, error);
schedulerResume(fiber, true);
wrenSetSlotString(getVM(), 2, error);
resume(resumeError);
}
void schedulerShutdown()
{
if (resume != NULL) wrenReleaseValue(getVM(), resume);
if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg);
if (resumeError != NULL) wrenReleaseValue(getVM(), resumeError);
// If the module was never loaded, we don't have anything to release.
if (schedulerClass == NULL) return;
WrenVM* vm = getVM();
wrenReleaseValue(vm, schedulerClass);
wrenReleaseValue(vm, resume1);
wrenReleaseValue(vm, resume2);
wrenReleaseValue(vm, resumeError);
}

View File

@ -3,10 +3,16 @@
#include "wren.h"
void schedulerResume(WrenValue* fiber);
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length);
void schedulerResumeDouble(WrenValue* fiber, double value);
void schedulerResumeString(WrenValue* fiber, const char* text);
// Sets up the API stack to call one of the resume methods on Scheduler.
//
// If [hasArgument] is false, this just sets up the stack to have another
// argument stored in slot 2 and returns. The module must store the argument
// on the stack and then call [schedulerFinishResume] to complete the call.
//
// Otherwise, the call resumes immediately. Releases [fiber] when called.
void schedulerResume(WrenValue* fiber, bool hasArgument);
void schedulerFinishResume();
void schedulerResumeError(WrenValue* fiber, const char* error);
void schedulerShutdown();

View File

@ -22,13 +22,13 @@ static void timerCallback(uv_timer_t* handle)
uv_close((uv_handle_t*)handle, timerCloseCallback);
// Run the fiber that was sleeping.
schedulerResume(fiber);
schedulerResume(fiber, false);
}
void timerStartTimer(WrenVM* vm)
{
int milliseconds = (int)wrenGetArgumentDouble(vm, 1);
WrenValue* fiber = wrenGetArgumentValue(vm, 2);
int milliseconds = (int)wrenGetSlotDouble(vm, 1);
WrenValue* fiber = wrenGetSlotValue(vm, 2);
// Store the fiber to resume when the timer completes.
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));

View File

@ -18,13 +18,11 @@ void metaCompile(WrenVM* vm)
: AS_CLOSURE(callingFn)->fn->module;
// Compile it.
ObjFn* fn = wrenCompile(vm, module, wrenGetArgumentString(vm, 1), false);
if (fn == NULL) return;
ObjFn* fn = wrenCompile(vm, module, wrenGetSlotString(vm, 1), false);
// Return the result. We can't use the public API for this since we have a
// bare ObjFn.
*vm->foreignStackStart = OBJ_VAL(fn);
vm->foreignStackStart = NULL;
vm->apiStack[0] = fn != NULL ? OBJ_VAL(fn) : NULL_VAL;
}
static WrenForeignMethodFn bindMetaForeignMethods(WrenVM* vm,

View File

@ -45,7 +45,7 @@ static void randomAllocate(WrenVM* vm)
static void randomSeed0(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)time(NULL));
for (int i = 0; i < 16; i++)
@ -56,9 +56,9 @@ static void randomSeed0(WrenVM* vm)
static void randomSeed1(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)wrenGetArgumentDouble(vm, 1));
srand((uint32_t)wrenGetSlotDouble(vm, 1));
for (int i = 0; i < 16; i++)
{
well->state[i] = rand();
@ -67,17 +67,17 @@ static void randomSeed1(WrenVM* vm)
static void randomSeed16(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
for (int i = 0; i < 16; i++)
{
well->state[i] = (uint32_t)wrenGetArgumentDouble(vm, i + 1);
well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1);
}
}
static void randomFloat(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
// A double has 53 bits of precision in its mantissa, and we'd like to take
// full advantage of that, so we need 53 bits of random source data.
@ -92,14 +92,14 @@ static void randomFloat(WrenVM* vm)
// from 0 to 1.0 (half-inclusive).
result /= 9007199254740992.0;
wrenReturnDouble(vm, result);
wrenSetSlotDouble(vm, 0, result);
}
static void randomInt0(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
wrenReturnDouble(vm, (double)advanceState(well));
wrenSetSlotDouble(vm, 0, (double)advanceState(well));
}
// TODO: The way these are wired up is pretty verbose and tedious. Also, the

View File

@ -1331,7 +1331,7 @@ static int resolveLocal(Compiler* compiler, const char* name, int length)
// Adds an upvalue to [compiler]'s function with the given properties. Does not
// add one if an upvalue for that variable is already in the list. Returns the
// index of the uvpalue.
// index of the upvalue.
static int addUpvalue(Compiler* compiler, bool isLocal, int index)
{
// Look for an existing one.

View File

@ -150,6 +150,8 @@ DEF_PRIMITIVE(fiber_suspend)
{
// Switching to a null fiber tells the interpreter to stop and exit.
vm->fiber = NULL;
vm->apiStack = NULL;
return false;
}

View File

@ -20,6 +20,9 @@ void wrenDebugPrintStackTrace(ObjFiber* fiber)
CallFrame* frame = &fiber->frames[i];
ObjFn* fn = wrenUpwrapClosure(frame->fn);
// Skip over stub functions for calling methods from the C API.
if (fn->module == NULL) continue;
// The built-in core module has no name. We explicitly omit it from stack
// traces since we don't want to highlight to a user the implementation
// detail of what part of the core module is written in C and what is Wren.
@ -50,7 +53,7 @@ static void dumpObject(Obj* obj)
case OBJ_RANGE: printf("[range %p]", obj); break;
case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break;
case OBJ_UPVALUE: printf("[upvalue %p]", obj); break;
default: printf("[unknown object]"); break;
default: printf("[unknown object %d]", obj->type); break;
}
}

View File

@ -157,7 +157,11 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
// Allocate the arrays before the fiber in case it triggers a GC.
CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES);
int stackCapacity = wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots);
// Add one slot for the unused implicit receiver slot that the compiler
// assumes all functions have.
int stackCapacity = fn == NULL
? 1
: wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots + 1);
Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity);
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
@ -179,10 +183,52 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn)
fiber->caller = NULL;
fiber->error = NULL_VAL;
fiber->callerIsTrying = false;
fiber->numFrames = 0;
// Initialize the first call frame.
fiber->numFrames = 0;
wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
if (fn != NULL) wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
}
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed)
{
if (fiber->stackCapacity >= needed) return;
int capacity = wrenPowerOf2Ceil(needed);
Value* oldStack = fiber->stack;
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
sizeof(Value) * fiber->stackCapacity,
sizeof(Value) * capacity);
fiber->stackCapacity = capacity;
// If the reallocation moves the stack, then we need to shift every pointer
// into the stack to point to its new location.
if (fiber->stack != oldStack)
{
// Top of the stack.
long offset = fiber->stack - oldStack;
if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop)
{
vm->apiStack += offset;
}
// Stack pointer for each call frame.
for (int i = 0; i < fiber->numFrames; i++)
{
fiber->frames[i].stackStart += offset;
}
// Open upvalues.
for (ObjUpvalue* upvalue = fiber->openUpvalues;
upvalue != NULL;
upvalue = upvalue->next)
{
upvalue->value += offset;
}
fiber->stackTop += offset;
}
}
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
@ -389,6 +435,8 @@ static uint32_t hashValue(Value value)
case VAL_OBJ: return hashObject(AS_OBJ(value));
default: UNREACHABLE();
}
return 0;
#endif
}
@ -1163,10 +1211,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
wrenValueBufferClear(vm, &((ObjModule*)obj)->variables);
break;
case OBJ_STRING:
case OBJ_CLOSURE:
case OBJ_INSTANCE:
case OBJ_RANGE:
case OBJ_STRING:
case OBJ_UPVALUE:
break;
}

View File

@ -74,6 +74,7 @@
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
@ -662,6 +663,9 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
frame->ip = wrenUpwrapClosure(frame->fn)->bytecode;
}
// Ensures [fiber]'s stack has at least [needed] slots.
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
// TODO: The argument list here is getting a bit gratuitous.

View File

@ -65,7 +65,7 @@ WrenVM* wrenNewVM(WrenConfiguration* config)
vm->modules = wrenNewMap(vm);
wrenInitializeCore(vm);
// TODO: Lazy load these.
#if WREN_OPT_META
wrenLoadMetaModule(vm);
@ -137,7 +137,7 @@ void wrenCollectGarbage(WrenVM* vm)
wrenGrayObj(vm, vm->tempRoots[i]);
}
// The current fiber.
// The rooted fiber.
wrenGrayObj(vm, (Obj*)vm->fiber);
// The value handles.
@ -348,19 +348,14 @@ static void bindMethod(WrenVM* vm, int methodType, int symbol,
static void callForeign(WrenVM* vm, ObjFiber* fiber,
WrenForeignMethodFn foreign, int numArgs)
{
vm->foreignStackStart = fiber->stackTop - numArgs;
vm->apiStack = fiber->stackTop - numArgs;
foreign(vm);
// Discard the stack slots for the arguments (but leave one for
// the result).
fiber->stackTop -= numArgs - 1;
// If nothing was returned, implicitly return null.
if (vm->foreignStackStart != NULL)
{
*vm->foreignStackStart = NULL_VAL;
vm->foreignStackStart = NULL;
}
// Discard the stack slots for the arguments and temporaries but leave one
// for the result.
fiber->stackTop = vm->apiStack + 1;
vm->apiStack = NULL;
}
// Handles the current fiber having aborted because of an error. Switches to
@ -469,41 +464,8 @@ static inline void callFunction(
// Grow the stack if needed.
int stackSize = (int)(fiber->stackTop - fiber->stack);
int needed = stackSize + wrenUpwrapClosure(function)->maxSlots;
wrenEnsureStack(vm, fiber, needed);
if (fiber->stackCapacity < needed)
{
int capacity = wrenPowerOf2Ceil(needed);
Value* oldStack = fiber->stack;
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
sizeof(Value) * fiber->stackCapacity,
sizeof(Value) * capacity);
fiber->stackCapacity = capacity;
// If the reallocation moves the stack, then we need to shift every pointer
// into the stack to point to its new location.
if (fiber->stack != oldStack)
{
// Top of the stack.
long offset = fiber->stack - oldStack;
fiber->stackTop += offset;
// Stack pointer for each call frame.
for (int i = 0; i < fiber->numFrames; i++)
{
fiber->frames[i].stackStart += offset;
}
// Open upvalues.
for (ObjUpvalue* upvalue = fiber->openUpvalues;
upvalue != NULL;
upvalue = upvalue->next)
{
upvalue->value += offset;
}
}
}
wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
}
@ -689,7 +651,7 @@ static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
if (methods.finalize != NULL)
{
method.fn.foreign = methods.finalize;
method.fn.foreign = (WrenForeignMethodFn)methods.finalize;
wrenBindMethod(vm, classObj, symbol, method);
}
}
@ -742,7 +704,7 @@ static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
// Pass the constructor arguments to the allocator as well.
vm->foreignStackStart = stack;
vm->apiStack = stack;
method->fn.foreign(vm);
@ -767,11 +729,8 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign.");
// Pass the constructor arguments to the allocator as well.
Value slot = OBJ_VAL(foreign);
vm->foreignStackStart = &slot;
method->fn.foreign(vm);
WrenFinalizerFn finalizer = (WrenFinalizerFn)method->fn.foreign;
finalizer(foreign->data);
}
// The main bytecode interpreter loop. This is where the magic happens. It is
@ -1179,18 +1138,23 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
// If the fiber is complete, end it.
if (fiber->numFrames == 0)
{
// See if there's another fiber to return to.
ObjFiber* callingFiber = fiber->caller;
// See if there's another fiber to return to. If not, we're done.
if (fiber->caller == NULL)
{
// Store the final result value at the beginning of the stack so the
// C API can get it.
fiber->stack[0] = result;
fiber->stackTop = fiber->stack + 1;
return WREN_RESULT_SUCCESS;
}
ObjFiber* resumingFiber = fiber->caller;
fiber->caller = NULL;
fiber = resumingFiber;
vm->fiber = resumingFiber;
fiber = callingFiber;
vm->fiber = fiber;
// If not, we're done.
if (fiber == NULL) return WREN_RESULT_SUCCESS;
// Store the result in the resuming fiber.
*(fiber->stackTop - 1) = result;
fiber->stackTop[-1] = result;
}
else
{
@ -1202,7 +1166,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
// result).
fiber->stackTop = frame->stackStart + 1;
}
LOAD_FRAME();
DISPATCH();
}
@ -1330,11 +1294,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
#undef READ_SHORT
}
// Creates an [ObjFn] that invokes a method with [signature] when called.
static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature)
{
int signatureLength = (int)strlen(signature);
// Count the number parameters the method expects.
int numParams = 0;
if (signature[signatureLength - 1] == ')')
@ -1345,167 +1308,73 @@ static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
if (*s == '_') numParams++;
}
}
// Add the signatue to the method table.
int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
signature, signatureLength);
// Create a little stub function that assumes the arguments are on the stack
// and calls the method.
uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5);
bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams);
bytecode[1] = (method >> 8) & 0xff;
bytecode[2] = method & 0xff;
bytecode[3] = CODE_RETURN;
bytecode[4] = CODE_END;
int* debugLines = ALLOCATE_ARRAY(vm, int, 5);
memset(debugLines, 1, 5);
ObjFn* fn = wrenNewFunction(vm, NULL, NULL, 0, 0, numParams + 1, 0, bytecode,
5, signature, signatureLength, debugLines);
return wrenNewFunction(vm, module, NULL, 0, 0, numParams + 1, 0, bytecode, 5,
signature, signatureLength, debugLines);
// Wrap the function in a handle.
return wrenCaptureValue(vm, OBJ_VAL(fn));
}
WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
const char* signature)
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method)
{
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
ObjModule* moduleObj = getModule(vm, moduleName);
// TODO: Handle module not being found.
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
variable, strlen(variable));
// TODO: Handle the variable not being found.
ObjFn* fn = makeCallStub(vm, moduleObj, signature);
wrenPushRoot(vm, (Obj*)fn);
// Create a single fiber that we can reuse each time the method is invoked.
ObjFiber* fiber = wrenNewFiber(vm, (Obj*)fn);
wrenPushRoot(vm, (Obj*)fiber);
// Create a handle that keeps track of the function that calls the method.
WrenValue* method = wrenCaptureValue(vm, OBJ_VAL(fiber));
// Store the receiver in the fiber's stack so we can use it later in the call.
*fiber->stackTop++ = moduleObj->variables.data[variableSlot];
wrenPopRoot(vm); // fiber.
wrenPopRoot(vm); // fn.
wrenPopRoot(vm); // moduleName.
return method;
}
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method,
WrenValue** returnValue,
const char* argTypes, ...)
{
va_list args;
va_start(args, argTypes);
WrenInterpretResult result = wrenCallVarArgs(vm, method, returnValue,
argTypes, args);
va_end(args);
return result;
}
WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method,
WrenValue** returnValue,
const char* argTypes, va_list args)
{
// TODO: Validate that the number of arguments matches what the method
// expects.
ASSERT(IS_FIBER(method->value), "Value must come from wrenGetMethod().");
ObjFiber* fiber = AS_FIBER(method->value);
// Push the arguments.
for (const char* argType = argTypes; *argType != '\0'; argType++)
{
Value value = NULL_VAL;
switch (*argType)
{
case 'a':
{
const char* bytes = va_arg(args, const char*);
int length = va_arg(args, int);
value = wrenNewString(vm, bytes, (size_t)length);
break;
}
case 'b': value = BOOL_VAL(va_arg(args, int)); break;
case 'd': value = NUM_VAL(va_arg(args, double)); break;
case 'i': value = NUM_VAL((double)va_arg(args, int)); break;
case 'n': value = NULL_VAL; va_arg(args, void*); break;
case 's':
value = wrenStringFormat(vm, "$", va_arg(args, const char*));
break;
case 'v':
{
// Allow a NULL value pointer for Wren null.
WrenValue* wrenValue = va_arg(args, WrenValue*);
if (wrenValue != NULL) value = wrenValue->value;
break;
}
default:
ASSERT(false, "Unknown argument type.");
break;
}
*fiber->stackTop++ = value;
}
Value receiver = fiber->stack[0];
Obj* fn = fiber->frames[0].fn;
wrenPushRoot(vm, (Obj*)fn);
WrenInterpretResult result = runInterpreter(vm, fiber);
if (result == WREN_RESULT_SUCCESS)
{
if (returnValue != NULL)
{
// Make sure the return value doesn't get collected while capturing it.
fiber->stackTop++;
*returnValue = wrenCaptureValue(vm, fiber->stack[0]);
}
// Reset the fiber to get ready for the next call.
wrenResetFiber(vm, fiber, fn);
// Push the receiver back on the stack.
*fiber->stackTop++ = receiver;
}
else
{
if (returnValue != NULL) *returnValue = NULL;
}
wrenPopRoot(vm);
return result;
ASSERT(method != NULL, "Method cannot be NULL.");
ASSERT(IS_FN(method->value), "Method must be a method handle.");
ASSERT(vm->fiber != NULL, "Must set up arguments for call first.");
ASSERT(vm->apiStack != NULL, "Must set up arguments for call first.");
ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method.");
ObjFn* fn = AS_FN(method->value);
ASSERT(vm->fiber->stackTop - vm->fiber->stack >= fn->arity,
"Stack must have enough arguments for method.");
// Discard any extra temporary slots. We take for granted that the stub
// function has exactly one slot for each arguments.
vm->fiber->stackTop = &vm->fiber->stack[fn->maxSlots];
callFunction(vm, vm->fiber, (Obj*)fn, 0);
return runInterpreter(vm, vm->fiber);
}
WrenValue* wrenCaptureValue(WrenVM* vm, Value value)
{
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
// Make a handle for it.
WrenValue* wrappedValue = ALLOCATE(vm, WrenValue);
wrappedValue->value = value;
if (IS_OBJ(value)) wrenPopRoot(vm);
// Add it to the front of the linked list of handles.
if (vm->valueHandles != NULL) vm->valueHandles->prev = wrappedValue;
wrappedValue->prev = NULL;
wrappedValue->next = vm->valueHandles;
vm->valueHandles = wrappedValue;
return wrappedValue;
}
void wrenReleaseValue(WrenVM* vm, WrenValue* value)
{
ASSERT(value != NULL, "NULL value.");
ASSERT(value != NULL, "Value cannot be NULL.");
// Update the VM's head pointer if we're releasing the first handle.
if (vm->valueHandles == value) vm->valueHandles = value->next;
@ -1524,14 +1393,14 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value)
void* wrenAllocateForeign(WrenVM* vm, size_t size)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
ASSERT(vm->apiStack != NULL, "Must be in foreign call.");
// TODO: Validate this. It can fail if the user calls this inside another
// foreign method, or calls one of the return functions.
ObjClass* classObj = AS_CLASS(vm->foreignStackStart[0]);
ObjClass* classObj = AS_CLASS(vm->apiStack[0]);
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
vm->foreignStackStart[0] = OBJ_VAL(foreign);
vm->apiStack[0] = OBJ_VAL(foreign);
return (void*)foreign->data;
}
@ -1658,100 +1527,170 @@ void wrenPopRoot(WrenVM* vm)
vm->numTempRoots--;
}
int wrenGetArgumentCount(WrenVM* vm)
int wrenGetSlotCount(WrenVM* vm)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
if (vm->apiStack == NULL) return 0;
// If no fiber is executing, we must be in a finalizer, in which case the
// "stack" just has one object, the object being finalized.
if (vm->fiber == NULL) return 1;
return (int)(vm->fiber->stackTop - vm->foreignStackStart);
return (int)(vm->fiber->stackTop - vm->apiStack);
}
static void validateForeignArgument(WrenVM* vm, int index)
void wrenEnsureSlots(WrenVM* vm, int numSlots)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
ASSERT(index >= 0, "index cannot be negative.");
ASSERT(index < wrenGetArgumentCount(vm), "Not that many arguments.");
// If we don't have a fiber accessible, create one for the API to use.
if (vm->apiStack == NULL)
{
vm->fiber = wrenNewFiber(vm, NULL);
vm->apiStack = vm->fiber->stack;
}
int currentSize = (int)(vm->fiber->stackTop - vm->apiStack);
if (currentSize >= numSlots) return;
// Grow the stack if needed.
int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots;
wrenEnsureStack(vm, vm->fiber, needed);
vm->fiber->stackTop = vm->apiStack + numSlots;
}
bool wrenGetArgumentBool(WrenVM* vm, int index)
// Ensures that [slot] is a valid index into the API's stack of slots.
static void validateApiSlot(WrenVM* vm, int slot)
{
validateForeignArgument(vm, index);
if (!IS_BOOL(vm->foreignStackStart[index])) return false;
return AS_BOOL(vm->foreignStackStart[index]);
ASSERT(slot >= 0, "Slot cannot be negative.");
ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots.");
}
double wrenGetArgumentDouble(WrenVM* vm, int index)
bool wrenGetSlotBool(WrenVM* vm, int slot)
{
validateForeignArgument(vm, index);
validateApiSlot(vm, slot);
ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool.");
if (!IS_NUM(vm->foreignStackStart[index])) return 0.0;
return AS_NUM(vm->foreignStackStart[index]);
return AS_BOOL(vm->apiStack[slot]);
}
void* wrenGetArgumentForeign(WrenVM* vm, int index)
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length)
{
validateForeignArgument(vm, index);
if (!IS_FOREIGN(vm->foreignStackStart[index])) return NULL;
return AS_FOREIGN(vm->foreignStackStart[index])->data;
validateApiSlot(vm, slot);
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
ObjString* string = AS_STRING(vm->apiStack[slot]);
*length = string->length;
return string->value;
}
const char* wrenGetArgumentString(WrenVM* vm, int index)
double wrenGetSlotDouble(WrenVM* vm, int slot)
{
validateForeignArgument(vm, index);
validateApiSlot(vm, slot);
ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number.");
if (!IS_STRING(vm->foreignStackStart[index])) return NULL;
return AS_CSTRING(vm->foreignStackStart[index]);
return AS_NUM(vm->apiStack[slot]);
}
WrenValue* wrenGetArgumentValue(WrenVM* vm, int index)
void* wrenGetSlotForeign(WrenVM* vm, int slot)
{
validateForeignArgument(vm, index);
validateApiSlot(vm, slot);
ASSERT(IS_FOREIGN(vm->apiStack[slot]),
"Slot must hold a foreign instance.");
return wrenCaptureValue(vm, vm->foreignStackStart[index]);
return AS_FOREIGN(vm->apiStack[slot])->data;
}
void wrenReturnBool(WrenVM* vm, bool value)
const char* wrenGetSlotString(WrenVM* vm, int slot)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
validateApiSlot(vm, slot);
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
*vm->foreignStackStart = BOOL_VAL(value);
vm->foreignStackStart = NULL;
return AS_CSTRING(vm->apiStack[slot]);
}
void wrenReturnDouble(WrenVM* vm, double value)
WrenValue* wrenGetSlotValue(WrenVM* vm, int slot)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
*vm->foreignStackStart = NUM_VAL(value);
vm->foreignStackStart = NULL;
validateApiSlot(vm, slot);
return wrenCaptureValue(vm, vm->apiStack[slot]);
}
void wrenReturnString(WrenVM* vm, const char* text, int length)
// Stores [value] in [slot] in the foreign call stack.
static void setSlot(WrenVM* vm, int slot, Value value)
{
validateApiSlot(vm, slot);
vm->apiStack[slot] = value;
}
void wrenSetSlotBool(WrenVM* vm, int slot, bool value)
{
setSlot(vm, slot, BOOL_VAL(value));
}
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length)
{
ASSERT(bytes != NULL, "Byte array cannot be NULL.");
setSlot(vm, slot, wrenNewString(vm, bytes, length));
}
void wrenSetSlotDouble(WrenVM* vm, int slot, double value)
{
setSlot(vm, slot, NUM_VAL(value));
}
void wrenSetSlotNewList(WrenVM* vm, int slot)
{
setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
}
void wrenSetSlotNull(WrenVM* vm, int slot)
{
setSlot(vm, slot, NULL_VAL);
}
void wrenSetSlotString(WrenVM* vm, int slot, const char* text)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
ASSERT(text != NULL, "String cannot be NULL.");
size_t size = length;
if (length == -1) size = strlen(text);
*vm->foreignStackStart = wrenNewString(vm, text, size);
vm->foreignStackStart = NULL;
setSlot(vm, slot, wrenNewString(vm, text, strlen(text)));
}
void wrenReturnValue(WrenVM* vm, WrenValue* value)
void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value)
{
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
ASSERT(value != NULL, "Value cannot be NULL.");
*vm->foreignStackStart = value->value;
vm->foreignStackStart = NULL;
setSlot(vm, slot, value->value);
}
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
{
validateApiSlot(vm, listSlot);
validateApiSlot(vm, elementSlot);
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list.");
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
// Negative indices count from the end.
if (index < 0) index = list->elements.count + 1 + index;
ASSERT(index <= list->elements.count, "Index out of bounds.");
wrenListInsert(vm, list, vm->apiStack[elementSlot], index);
}
// TODO: Maybe just have this always return a WrenValue* instead of having to
// deal with slots?
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot)
{
ASSERT(module != NULL, "Module cannot be NULL.");
ASSERT(module != NULL, "Variable name cannot be NULL.");
validateApiSlot(vm, slot);
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
ObjModule* moduleObj = getModule(vm, moduleName);
ASSERT(moduleObj != NULL, "Could not find module.");
wrenPopRoot(vm); // moduleName.
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
name, strlen(name));
ASSERT(variableSlot != -1, "Could not find variable.");
setSlot(vm, slot, moduleObj->variables.data[variableSlot]);
}

View File

@ -84,11 +84,14 @@ struct WrenVM
// NULL if there are no handles.
WrenValue* valueHandles;
// Foreign function data:
// During a foreign function call, this will point to the first argument (the
// receiver) of the call on the fiber's stack.
Value* foreignStackStart;
// Pointer to the bottom of the range of stack slots available for use from
// the C API. During a foreign method, this will be in the stack of the fiber
// that is executing a method.
//
// If not in a foreign method, this is initially NULL. If the user requests
// slots by calling wrenEnsureSlots(), a stack is created and this is
// initialized.
Value* apiStack;
WrenConfiguration config;

View File

@ -1,21 +1,81 @@
#include <string.h>
#include <time.h>
#include "benchmark.h"
static void arguments(WrenVM* vm)
{
double result = 0;
result += wrenGetArgumentDouble(vm, 1);
result += wrenGetArgumentDouble(vm, 2);
result += wrenGetArgumentDouble(vm, 3);
result += wrenGetArgumentDouble(vm, 4);
result += wrenGetSlotDouble(vm, 1);
result += wrenGetSlotDouble(vm, 2);
result += wrenGetSlotDouble(vm, 3);
result += wrenGetSlotDouble(vm, 4);
wrenSetSlotDouble(vm, 0, result);
}
const char* testScript =
"class Test {\n"
" static method(a, b, c, d) { a + b + c + d }\n"
"}\n";
static void call(WrenVM* vm)
{
int iterations = (int)wrenGetSlotDouble(vm, 1);
wrenReturnDouble(vm, result);
// Since the VM is not re-entrant, we can't call from within this foreign
// method. Instead, make a new VM to run the call test in.
WrenConfiguration config;
wrenInitConfiguration(&config);
WrenVM* otherVM = wrenNewVM(&config);
wrenInterpret(otherVM, testScript);
WrenValue* method = wrenMakeCallHandle(otherVM, "method(_,_,_,_)");
wrenEnsureSlots(otherVM, 1);
wrenGetVariable(otherVM, "main", "Test", 0);
WrenValue* testClass = wrenGetSlotValue(otherVM, 0);
double startTime = (double)clock() / CLOCKS_PER_SEC;
double result = 0;
for (int i = 0; i < iterations; i++)
{
wrenEnsureSlots(otherVM, 5);
wrenSetSlotValue(otherVM, 0, testClass);
wrenSetSlotDouble(otherVM, 1, 1.0);
wrenSetSlotDouble(otherVM, 2, 2.0);
wrenSetSlotDouble(otherVM, 3, 3.0);
wrenSetSlotDouble(otherVM, 4, 4.0);
wrenCall(otherVM, method);
result += wrenGetSlotDouble(otherVM, 0);
}
double elapsed = (double)clock() / CLOCKS_PER_SEC - startTime;
wrenReleaseValue(otherVM, testClass);
wrenReleaseValue(otherVM, method);
wrenFreeVM(otherVM);
if (result == (1.0 + 2.0 + 3.0 + 4.0) * iterations)
{
wrenSetSlotDouble(vm, 0, elapsed);
}
else
{
// Got the wrong result.
wrenSetSlotBool(vm, 0, false);
}
}
WrenForeignMethodFn benchmarkBindMethod(const char* signature)
{
if (strcmp(signature, "static Benchmark.arguments(_,_,_,_)") == 0) return arguments;
if (strcmp(signature, "static Benchmark.call(_)") == 0) return call;
return NULL;
}

View File

@ -5,33 +5,84 @@
void callRunTests(WrenVM* vm)
{
WrenValue* noParams = wrenGetMethod(vm, "main", "Call", "noParams");
WrenValue* zero = wrenGetMethod(vm, "main", "Call", "zero()");
WrenValue* one = wrenGetMethod(vm, "main", "Call", "one(_)");
WrenValue* two = wrenGetMethod(vm, "main", "Call", "two(_,_)");
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "main", "Call", 0);
WrenValue* callClass = wrenGetSlotValue(vm, 0);
WrenValue* noParams = wrenMakeCallHandle(vm, "noParams");
WrenValue* zero = wrenMakeCallHandle(vm, "zero()");
WrenValue* one = wrenMakeCallHandle(vm, "one(_)");
WrenValue* two = wrenMakeCallHandle(vm, "two(_,_)");
// Different arity.
wrenCall(vm, noParams, NULL, "");
wrenCall(vm, zero, NULL, "");
wrenCall(vm, one, NULL, "i", 1);
wrenCall(vm, two, NULL, "ii", 1, 2);
WrenValue* getValue = wrenGetMethod(vm, "main", "Call", "getValue(_)");
wrenEnsureSlots(vm, 1);
wrenSetSlotValue(vm, 0, callClass);
wrenCall(vm, noParams);
wrenEnsureSlots(vm, 1);
wrenSetSlotValue(vm, 0, callClass);
wrenCall(vm, zero);
wrenEnsureSlots(vm, 2);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotDouble(vm, 1, 1.0);
wrenCall(vm, one);
wrenEnsureSlots(vm, 3);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotDouble(vm, 1, 1.0);
wrenSetSlotDouble(vm, 2, 2.0);
wrenCall(vm, two);
// Returning a value.
WrenValue* value = NULL;
wrenCall(vm, getValue, &value, "v", NULL);
WrenValue* getValue = wrenMakeCallHandle(vm, "getValue()");
wrenEnsureSlots(vm, 1);
wrenSetSlotValue(vm, 0, callClass);
wrenCall(vm, getValue);
WrenValue* value = wrenGetSlotValue(vm, 0);
// Different argument types.
wrenCall(vm, two, NULL, "bb", true, false);
wrenCall(vm, two, NULL, "dd", 1.2, 3.4);
wrenCall(vm, two, NULL, "ii", 3, 4);
wrenCall(vm, two, NULL, "ss", "string", "another");
wrenCall(vm, two, NULL, "vv", NULL, value);
wrenEnsureSlots(vm, 3);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotBool(vm, 1, true);
wrenSetSlotBool(vm, 2, false);
wrenCall(vm, two);
wrenEnsureSlots(vm, 3);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotDouble(vm, 1, 1.2);
wrenSetSlotDouble(vm, 2, 3.4);
wrenCall(vm, two);
wrenEnsureSlots(vm, 3);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotString(vm, 1, "string");
wrenSetSlotString(vm, 2, "another");
wrenCall(vm, two);
wrenEnsureSlots(vm, 3);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotNull(vm, 1);
wrenSetSlotValue(vm, 2, value);
wrenCall(vm, two);
// Truncate a string, or allow null bytes.
wrenCall(vm, two, NULL, "aa", "string", 3, "b\0y\0t\0e", 7);
wrenEnsureSlots(vm, 3);
wrenSetSlotValue(vm, 0, callClass);
wrenSetSlotBytes(vm, 1, "string", 3);
wrenSetSlotBytes(vm, 2, "b\0y\0t\0e", 7);
wrenCall(vm, two);
// Call ignores with extra temporary slots on stack.
wrenEnsureSlots(vm, 10);
wrenSetSlotValue(vm, 0, callClass);
for (int i = 1; i < 10; i++)
{
wrenSetSlotDouble(vm, i, i * 0.1);
}
wrenCall(vm, one);
wrenReleaseValue(vm, callClass);
wrenReleaseValue(vm, noParams);
wrenReleaseValue(vm, zero);
wrenReleaseValue(vm, one);

View File

@ -20,13 +20,7 @@ class Call {
System.print("two %(one) %(two)")
}
static def getValue(value) {
// Return a new value if we aren't given one.
if (value == null) return ["a", "b"]
// Otherwise print it.
System.print(value)
}
static def getValue() { ["a", "b"] }
}
// expect: noParams
@ -36,7 +30,7 @@ class Call {
// expect: two true false
// expect: two 1.2 3.4
// expect: two 3 4
// expect: two string another
// expect: two null [a, b]
// expect: two str [98, 0, 121, 0, 116, 0, 101]
// expect: one 0.1

View File

@ -7,7 +7,7 @@ static int finalized = 0;
static void apiFinalized(WrenVM* vm)
{
wrenReturnDouble(vm, finalized);
wrenSetSlotDouble(vm, 0, finalized);
}
static void counterAllocate(WrenVM* vm)
@ -18,25 +18,25 @@ static void counterAllocate(WrenVM* vm)
static void counterIncrement(WrenVM* vm)
{
double* value = (double*)wrenGetArgumentForeign(vm, 0);
double increment = wrenGetArgumentDouble(vm, 1);
double* value = (double*)wrenGetSlotForeign(vm, 0);
double increment = wrenGetSlotDouble(vm, 1);
*value += increment;
}
static void counterValue(WrenVM* vm)
{
double value = *(double*)wrenGetArgumentForeign(vm, 0);
wrenReturnDouble(vm, value);
double value = *(double*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, value);
}
static void pointAllocate(WrenVM* vm)
{
double* coordinates = (double*)wrenAllocateForeign(vm, sizeof(double[3]));
// This gets called by both constructors, so sniff the argument count to see
// This gets called by both constructors, so sniff the slot count to see
// which one was invoked.
if (wrenGetArgumentCount(vm) == 1)
if (wrenGetSlotCount(vm) == 1)
{
coordinates[0] = 0.0;
coordinates[1] = 0.0;
@ -44,36 +44,41 @@ static void pointAllocate(WrenVM* vm)
}
else
{
coordinates[0] = wrenGetArgumentDouble(vm, 1);
coordinates[1] = wrenGetArgumentDouble(vm, 2);
coordinates[2] = wrenGetArgumentDouble(vm, 3);
coordinates[0] = wrenGetSlotDouble(vm, 1);
coordinates[1] = wrenGetSlotDouble(vm, 2);
coordinates[2] = wrenGetSlotDouble(vm, 3);
}
}
static void pointTranslate(WrenVM* vm)
{
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0);
coordinates[0] += wrenGetArgumentDouble(vm, 1);
coordinates[1] += wrenGetArgumentDouble(vm, 2);
coordinates[2] += wrenGetArgumentDouble(vm, 3);
double* coordinates = (double*)wrenGetSlotForeign(vm, 0);
coordinates[0] += wrenGetSlotDouble(vm, 1);
coordinates[1] += wrenGetSlotDouble(vm, 2);
coordinates[2] += wrenGetSlotDouble(vm, 3);
}
static void pointToString(WrenVM* vm)
{
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0);
double* coordinates = (double*)wrenGetSlotForeign(vm, 0);
char result[100];
sprintf(result, "(%g, %g, %g)",
coordinates[0], coordinates[1], coordinates[2]);
wrenReturnString(vm, result, (int)strlen(result));
wrenSetSlotString(vm, 0, result);
}
static void resourceAllocate(WrenVM* vm)
{
wrenAllocateForeign(vm, 0);
int* value = (int*)wrenAllocateForeign(vm, sizeof(int));
*value = 123;
}
static void resourceFinalize(WrenVM* vm)
static void resourceFinalize(void* data)
{
// Make sure we get the right data back.
int* value = (int*)data;
if (*value != 123) exit(1);
finalized++;
}

44
test/api/get_variable.c Normal file
View File

@ -0,0 +1,44 @@
#include <string.h>
#include "get_variable.h"
static void beforeDefined(WrenVM* vm)
{
wrenGetVariable(vm, "main", "A", 0);
}
static void afterDefined(WrenVM* vm)
{
wrenGetVariable(vm, "main", "A", 0);
}
static void afterAssigned(WrenVM* vm)
{
wrenGetVariable(vm, "main", "A", 0);
}
static void otherSlot(WrenVM* vm)
{
wrenEnsureSlots(vm, 3);
wrenGetVariable(vm, "main", "B", 2);
// Move it into return position.
const char* string = wrenGetSlotString(vm, 2);
wrenSetSlotString(vm, 0, string);
}
static void otherModule(WrenVM* vm)
{
wrenGetVariable(vm, "get_variable_module", "Variable", 0);
}
WrenForeignMethodFn getVariableBindMethod(const char* signature)
{
if (strcmp(signature, "static GetVariable.beforeDefined()") == 0) return beforeDefined;
if (strcmp(signature, "static GetVariable.afterDefined()") == 0) return afterDefined;
if (strcmp(signature, "static GetVariable.afterAssigned()") == 0) return afterAssigned;
if (strcmp(signature, "static GetVariable.otherSlot()") == 0) return otherSlot;
if (strcmp(signature, "static GetVariable.otherModule()") == 0) return otherModule;
return NULL;
}

3
test/api/get_variable.h Normal file
View File

@ -0,0 +1,3 @@
#include "wren.h"
WrenForeignMethodFn getVariableBindMethod(const char* signature);

View File

@ -0,0 +1,24 @@
import "get_variable_module"
class GetVariable {
foreign static def beforeDefined()
foreign static def afterDefined()
foreign static def afterAssigned()
foreign static def otherSlot()
foreign static def otherModule()
}
System.print(GetVariable.beforeDefined()) // expect: null
var A = "a"
System.print(GetVariable.afterDefined()) // expect: a
A = "changed"
System.print(GetVariable.afterAssigned()) // expect: changed
var B = "b"
System.print(GetVariable.otherSlot()) // expect: b
System.print(GetVariable.otherModule()) // expect: value

View File

@ -0,0 +1,3 @@
// nontest
var Variable = "value"

46
test/api/lists.c Normal file
View File

@ -0,0 +1,46 @@
#include <string.h>
#include "lists.h"
static void newList(WrenVM* vm)
{
wrenSetSlotNewList(vm, 0);
}
// Helper function to store a double in a slot then insert it into the list at
// slot zero.
static void insertNumber(WrenVM* vm, int index, double value)
{
wrenSetSlotDouble(vm, 1, value);
wrenInsertInList(vm, 0, index, 1);
}
static void insert(WrenVM* vm)
{
wrenSetSlotNewList(vm, 0);
wrenEnsureSlots(vm, 2);
// Appending.
insertNumber(vm, 0, 1.0);
insertNumber(vm, 1, 2.0);
insertNumber(vm, 2, 3.0);
// Inserting.
insertNumber(vm, 0, 4.0);
insertNumber(vm, 1, 5.0);
insertNumber(vm, 2, 6.0);
// Negative indexes.
insertNumber(vm, -1, 7.0);
insertNumber(vm, -2, 8.0);
insertNumber(vm, -3, 9.0);
}
WrenForeignMethodFn listsBindMethod(const char* signature)
{
if (strcmp(signature, "static Lists.newList()") == 0) return newList;
if (strcmp(signature, "static Lists.insert()") == 0) return insert;
return NULL;
}

3
test/api/lists.h Normal file
View File

@ -0,0 +1,3 @@
#include "wren.h"
WrenForeignMethodFn listsBindMethod(const char* signature);

10
test/api/lists.wren Normal file
View File

@ -0,0 +1,10 @@
class Lists {
foreign static def newList()
foreign static def insert()
}
var list = Lists.newList()
System.print(list is List) // expect: true
System.print(list.count) // expect: 0
System.print(Lists.insert()) // expect: [4, 5, 6, 1, 2, 3, 9, 8, 7]

View File

@ -6,8 +6,10 @@
#include "benchmark.h"
#include "call.h"
#include "get_variable.h"
#include "foreign_class.h"
#include "returns.h"
#include "lists.h"
#include "slots.h"
#include "value.h"
// The name of the currently executing API test.
@ -32,11 +34,17 @@ static WrenForeignMethodFn bindForeignMethod(
method = benchmarkBindMethod(fullName);
if (method != NULL) return method;
method = getVariableBindMethod(fullName);
if (method != NULL) return method;
method = foreignClassBindMethod(fullName);
if (method != NULL) return method;
method = returnsBindMethod(fullName);
method = listsBindMethod(fullName);
if (method != NULL) return method;
method = slotsBindMethod(fullName);
if (method != NULL) return method;
method = valueBindMethod(fullName);
@ -60,7 +68,7 @@ static WrenForeignClassMethods bindForeignClass(
}
static void afterLoad(WrenVM* vm) {
if (strstr(testName, "call.wren") != NULL) callRunTests(vm);
if (strstr(testName, "/call.wren") != NULL) callRunTests(vm);
}
int main(int argc, const char* argv[])

View File

@ -1,51 +0,0 @@
#include <string.h>
#include "returns.h"
static void implicitNull(WrenVM* vm)
{
// Do nothing.
}
static void returnInt(WrenVM* vm)
{
wrenReturnDouble(vm, 123456);
}
static void returnFloat(WrenVM* vm)
{
wrenReturnDouble(vm, 123.456);
}
static void returnTrue(WrenVM* vm)
{
wrenReturnBool(vm, true);
}
static void returnFalse(WrenVM* vm)
{
wrenReturnBool(vm, false);
}
static void returnString(WrenVM* vm)
{
wrenReturnString(vm, "a string", -1);
}
static void returnBytes(WrenVM* vm)
{
wrenReturnString(vm, "a\0b\0c", 5);
}
WrenForeignMethodFn returnsBindMethod(const char* signature)
{
if (strcmp(signature, "static Returns.implicitNull") == 0) return implicitNull;
if (strcmp(signature, "static Returns.returnInt") == 0) return returnInt;
if (strcmp(signature, "static Returns.returnFloat") == 0) return returnFloat;
if (strcmp(signature, "static Returns.returnTrue") == 0) return returnTrue;
if (strcmp(signature, "static Returns.returnFalse") == 0) return returnFalse;
if (strcmp(signature, "static Returns.returnString") == 0) return returnString;
if (strcmp(signature, "static Returns.returnBytes") == 0) return returnBytes;
return NULL;
}

View File

@ -1,3 +0,0 @@
#include "wren.h"
WrenForeignMethodFn returnsBindMethod(const char* signature);

View File

@ -1,23 +0,0 @@
class Returns {
foreign static def implicitNull
foreign static def returnInt
foreign static def returnFloat
foreign static def returnTrue
foreign static def returnFalse
foreign static def returnString
foreign static def returnBytes
}
System.print(Returns.implicitNull == null) // expect: true
System.print(Returns.returnInt) // expect: 123456
System.print(Returns.returnFloat) // expect: 123.456
System.print(Returns.returnTrue) // expect: true
System.print(Returns.returnFalse) // expect: false
System.print(Returns.returnString) // expect: a string
System.print(Returns.returnBytes.bytes.toList) // expect: [97, 0, 98, 0, 99]

146
test/api/slots.c Normal file
View File

@ -0,0 +1,146 @@
#include <stdio.h>
#include <string.h>
#include "slots.h"
static void noSet(WrenVM* vm)
{
// Do nothing.
}
static void getSlots(WrenVM* vm)
{
bool result = true;
if (wrenGetSlotBool(vm, 1) != true) result = false;
// TODO: Test wrenGetSlotForeign().
int length;
const char* bytes = wrenGetSlotBytes(vm, 2, &length);
if (length != 5) result = false;
if (memcmp(bytes, "by\0te", length) != 0) result = false;
if (wrenGetSlotDouble(vm, 3) != 12.34) result = false;
if (strcmp(wrenGetSlotString(vm, 4), "str") != 0) result = false;
WrenValue* value = wrenGetSlotValue(vm, 5);
if (result)
{
// Otherwise, return the value so we can tell if we captured it correctly.
wrenSetSlotValue(vm, 0, value);
wrenReleaseValue(vm, value);
}
else
{
// If anything failed, return false.
wrenSetSlotBool(vm, 0, false);
}
}
static void setSlots(WrenVM* vm)
{
WrenValue* value = wrenGetSlotValue(vm, 1);
wrenSetSlotBool(vm, 1, true);
wrenSetSlotBytes(vm, 2, "by\0te", 5);
wrenSetSlotDouble(vm, 3, 12.34);
wrenSetSlotString(vm, 4, "str");
// TODO: wrenSetSlotNull().
// Read the slots back to make sure they were set correctly.
bool result = true;
if (wrenGetSlotBool(vm, 1) != true) result = false;
int length;
const char* bytes = wrenGetSlotBytes(vm, 2, &length);
if (length != 5) result = false;
if (memcmp(bytes, "by\0te", length) != 0) result = false;
if (wrenGetSlotDouble(vm, 3) != 12.34) result = false;
if (strcmp(wrenGetSlotString(vm, 4), "str") != 0) result = false;
if (result)
{
// Move the value into the return position.
wrenSetSlotValue(vm, 0, value);
wrenReleaseValue(vm, value);
}
else
{
// If anything failed, return false.
wrenSetSlotBool(vm, 0, false);
}
}
static void ensure(WrenVM* vm)
{
int before = wrenGetSlotCount(vm);
wrenEnsureSlots(vm, 20);
int after = wrenGetSlotCount(vm);
// Use the slots to make sure they're available.
for (int i = 0; i < 20; i++)
{
wrenSetSlotDouble(vm, i, i);
}
int sum = 0;
for (int i = 0; i < 20; i++)
{
sum += (int)wrenGetSlotDouble(vm, i);
}
char result[100];
sprintf(result, "%d -> %d (%d)", before, after, sum);
wrenSetSlotString(vm, 0, result);
}
static void ensureOutsideForeign(WrenVM* vm)
{
// To test the behavior outside of a foreign method (which we're currently
// in), create a new separate VM.
WrenConfiguration config;
wrenInitConfiguration(&config);
WrenVM* otherVM = wrenNewVM(&config);
int before = wrenGetSlotCount(otherVM);
wrenEnsureSlots(otherVM, 20);
int after = wrenGetSlotCount(otherVM);
// Use the slots to make sure they're available.
for (int i = 0; i < 20; i++)
{
wrenSetSlotDouble(otherVM, i, i);
}
int sum = 0;
for (int i = 0; i < 20; i++)
{
sum += (int)wrenGetSlotDouble(otherVM, i);
}
wrenFreeVM(otherVM);
char result[100];
sprintf(result, "%d -> %d (%d)", before, after, sum);
wrenSetSlotString(vm, 0, result);
}
WrenForeignMethodFn slotsBindMethod(const char* signature)
{
if (strcmp(signature, "static Slots.noSet") == 0) return noSet;
if (strcmp(signature, "static Slots.getSlots(_,_,_,_,_)") == 0) return getSlots;
if (strcmp(signature, "static Slots.setSlots(_,_,_,_)") == 0) return setSlots;
if (strcmp(signature, "static Slots.ensure()") == 0) return ensure;
if (strcmp(signature, "static Slots.ensureOutsideForeign()") == 0) return ensureOutsideForeign;
return NULL;
}

3
test/api/slots.h Normal file
View File

@ -0,0 +1,3 @@
#include "wren.h"
WrenForeignMethodFn slotsBindMethod(const char* signature);

24
test/api/slots.wren Normal file
View File

@ -0,0 +1,24 @@
class Slots {
foreign static def noSet
foreign static def getSlots(bool, num, string, bytes, value)
foreign static def setSlots(a, b, c, d)
foreign static def ensure()
foreign static def ensureOutsideForeign()
}
// If nothing is set in the return slot, it retains its previous value, the
// receiver.
System.print(Slots.noSet == Slots) // expect: true
var value = ["value"]
System.print(Slots.getSlots(true, "by\0te", 12.34, "str", value) == value)
// expect: true
System.print(Slots.setSlots(value, 0, 0, 0) == value)
// expect: true
System.print(Slots.ensure())
// expect: 1 -> 20 (190)
System.print(Slots.ensureOutsideForeign())
// expect: 0 -> 20 (190)

View File

@ -6,12 +6,12 @@ static WrenValue* value;
static void setValue(WrenVM* vm)
{
value = wrenGetArgumentValue(vm, 1);
value = wrenGetSlotValue(vm, 1);
}
static void getValue(WrenVM* vm)
{
wrenReturnValue(vm, value);
wrenSetSlotValue(vm, 0, value);
wrenReleaseValue(vm, value);
}

View File

@ -0,0 +1,9 @@
class Benchmark {
foreign static call(iterations)
}
var result = Benchmark.call(1000000)
// Returns false if it didn't calculate the right value. Otherwise returns the
// elapsed time.
System.print(result is Num)
System.print("elapsed: %(result)")

View File

@ -0,0 +1 @@
this is a text file

View File

@ -0,0 +1 @@
this is a text file

View File

@ -0,0 +1 @@
this is a text file

View File

@ -0,0 +1,8 @@
import "io" for Directory
var entries = Directory.list("test/io/directory/dir")
// Ignore OS-specific dot files like ".DS_Store".
entries = entries.where {|entry| !entry.startsWith(".") }.toList
System.print(entries) // expect: [a.txt, b.txt, c.txt]

View File

@ -0,0 +1,3 @@
import "io" for Directory
var entries = Directory.list("test/io/directory/dir/a.txt") // expect runtime error: not a directory

View File

@ -0,0 +1,3 @@
import "io" for Directory
Directory.list("nonexistent") // expect runtime error: no such file or directory

View File

@ -0,0 +1,3 @@
import "io" for Directory
Directory.list(123) // expect runtime error: Path must be a string.

View File

@ -0,0 +1,10 @@
import "io" for File
// Don't store in a variable.
File.open("test/io/file/finalize.wren")
System.gc()
// We can't really test what the finalizer *does* from Wren, since the object
// is unreachable, but this at least ensures it doesn't crash.
System.print("ok") // expect: ok

View File

@ -10,4 +10,10 @@ System.print(file.readBytes(7)) // expect: this is
// Allows zero.
System.print(file.readBytes(0).bytes.count) // expect: 0
// A longer number reads the whole file.
System.print(file.readBytes(100)) // expect: this is a text file
// Reading past the end truncates the buffer.
System.print(file.readBytes(100).bytes.count) // expect: 19
file.close()

View File

@ -0,0 +1,20 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
// Zero starts at the beginning.
System.print(file.readBytes(3, 0)) // expect: thi
// Starts at the offset.
System.print(file.readBytes(8, 3)) // expect: s is a t
// Allows zero.
System.print(file.readBytes(0, 4).bytes.count) // expect: 0
// A longer number length reads until the end.
System.print(file.readBytes(100, 2)) // expect: is is a text file
// An offset past the end returns an empty string.
System.print(file.readBytes(100, 30).bytes.count) // expect: 0
file.close()

View File

@ -0,0 +1,6 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.close()
file.readBytes(3, 0) // expect runtime error: File is not open.

View File

@ -0,0 +1,4 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.readBytes(-1, 0) // expect runtime error: Count cannot be negative.

View File

@ -0,0 +1,4 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.readBytes(1.2, 0) // expect runtime error: Count must be an integer.

View File

@ -0,0 +1,4 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.readBytes("not num", 0) // expect runtime error: Count must be an integer.

View File

@ -0,0 +1,4 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.readBytes(1, -1) // expect runtime error: Offset cannot be negative.

View File

@ -0,0 +1,4 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.readBytes(1, 1.2) // expect runtime error: Offset must be an integer.

View File

@ -0,0 +1,4 @@
import "io" for File
var file = File.open("test/io/file/file.txt")
file.readBytes(1, "not num") // expect runtime error: Offset must be an integer.

View File

@ -1,13 +1,6 @@
import "io" for File
import "scheduler" for Scheduler
System.print(File.size("test/io/file/size.wren")) // expect: 270
// Runs asynchronously.
Scheduler.add {
System.print("async")
}
System.print(File.size("test/io/file/size.wren"))
// expect: async
// expect: 270
var file = File.open("test/io/file/file.txt")
System.print(file.size) // expect: 19
file.close()

View File

@ -0,0 +1,13 @@
import "io" for File
import "scheduler" for Scheduler
System.print(File.size("test/io/file/file.txt")) // expect: 19
// Runs asynchronously.
Scheduler.add {
System.print("async")
}
System.print(File.size("test/io/file/file.txt"))
// expect: async
// expect: 19

View File

@ -0,0 +1,16 @@
import "io" for File, Stat
import "scheduler" for Scheduler
var stat = File.stat("test/io/file/file.txt")
System.print(stat is Stat) // expect: true
System.print(stat.device is Num) // expect: true
System.print(stat.inode is Num) // expect: true
System.print(stat.mode is Num) // expect: true
System.print(stat.linkCount) // expect: 1
System.print(stat.user is Num) // expect: true
System.print(stat.group is Num) // expect: true
System.print(stat.specialDevice) // expect: 0
System.print(stat.size) // expect: 19
System.print(stat.blockSize is Num) // expect: true
System.print(stat.blockCount is Num) // expect: true

View File

@ -0,0 +1,16 @@
import "io" for File, Stat
import "scheduler" for Scheduler
var stat = File.stat("test/io/directory/dir")
System.print(stat is Stat) // expect: true
System.print(stat.device is Num) // expect: true
System.print(stat.inode is Num) // expect: true
System.print(stat.mode is Num) // expect: true
System.print(stat.linkCount) // expect: 5
System.print(stat.user is Num) // expect: true
System.print(stat.group is Num) // expect: true
System.print(stat.specialDevice) // expect: 0
System.print(stat.size) // expect: 170
System.print(stat.blockSize is Num) // expect: true
System.print(stat.blockCount is Num) // expect: true

View File

@ -0,0 +1,3 @@
import "io" for File
File.stat("nonexistent") // expect runtime error: no such file or directory

View File

@ -51,6 +51,8 @@ def BENCHMARK(name, pattern):
regex = re.compile(pattern + "\n" + r"elapsed: (\d+\.\d+)", re.MULTILINE)
BENCHMARKS.append([name, regex, None])
BENCHMARK("api_call", "true")
BENCHMARK("api_foreign_method", "100000000")
BENCHMARK("binary_trees", """stretch tree of depth 13 check: -1

View File

@ -182,6 +182,7 @@ class Test:
# Make sure the stack trace has the right line. Skip over any lines that
# come from builtin libraries.
match = False
stack_lines = error_lines[line + 1:]
match = None
for stack_line in stack_lines:

View File

@ -206,12 +206,15 @@ $(LIBUV): $(LIBUV_DIR)/build/gyp/gyp util/libuv.py
# Wren modules that get compiled into the binary as C strings.
src/optional/wren_opt_%.wren.inc: src/optional/wren_opt_%.wren util/wren_to_c_string.py
@ printf "%10s %-30s %s\n" str $<
@ ./util/wren_to_c_string.py $@ $<
src/vm/wren_%.wren.inc: src/vm/wren_%.wren util/wren_to_c_string.py
@ printf "%10s %-30s %s\n" str $<
@ ./util/wren_to_c_string.py $@ $<
src/module/%.wren.inc: src/module/%.wren util/wren_to_c_string.py
@ printf "%10s %-30s %s\n" str $<
@ ./util/wren_to_c_string.py $@ $<
.PHONY: all cli test vm

View File

@ -50,7 +50,5 @@ def main():
with open(args.output, "w") as f:
f.write(c_source)
print(" str " + args.input)
main()

View File

@ -19,6 +19,7 @@
29205C9E1AB4E6430073018D /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; };
29205C9F1AB4E6430073018D /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; };
293D46961BB43F9900200083 /* call.c in Sources */ = {isa = PBXBuildFile; fileRef = 293D46941BB43F9900200083 /* call.c */; };
2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */ = {isa = PBXBuildFile; fileRef = 2949AA8B1C2F14F000B106BA /* get_variable.c */; };
29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; };
29512C821B91F901008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; };
29729F311BA70A620099CA20 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29729F2E1BA70A620099CA20 /* io.c */; };
@ -26,6 +27,7 @@
29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; };
2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; };
29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.c */; };
29932D541C210F8D00099DEE /* lists.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D521C210F8D00099DEE /* lists.c */; };
29A427341BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; };
29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; };
29A427361BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */; };
@ -47,7 +49,7 @@
29DC14A91BBA302F008A8274 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; };
29DC14AA1BBA3032008A8274 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A61B7E3993000CE58C /* main.c */; };
29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A81B7E39A8000CE58C /* foreign_class.c */; };
29DC14AC1BBA303D008A8274 /* returns.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AA1B7E39A8000CE58C /* returns.c */; };
29DC14AC1BBA303D008A8274 /* slots.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AA1B7E39A8000CE58C /* slots.c */; };
29DC14AD1BBA3040008A8274 /* value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AC1B7E39A8000CE58C /* value.c */; };
/* End PBXBuildFile section */
@ -97,6 +99,8 @@
29205CA81AB4E65E0073018D /* wren_vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_vm.h; path = ../../src/vm/wren_vm.h; sourceTree = "<group>"; };
293D46941BB43F9900200083 /* call.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = call.c; path = ../../test/api/call.c; sourceTree = "<group>"; };
293D46951BB43F9900200083 /* call.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = call.h; path = ../../test/api/call.h; sourceTree = "<group>"; };
2949AA8B1C2F14F000B106BA /* get_variable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = get_variable.c; path = ../../test/api/get_variable.c; sourceTree = "<group>"; };
2949AA8C1C2F14F000B106BA /* get_variable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = get_variable.h; path = ../../test/api/get_variable.h; sourceTree = "<group>"; };
29512C7F1B91F86E008C10E6 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; };
29512C801B91F8EB008C10E6 /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../build/libuv.a; sourceTree = "<group>"; };
296371B31AC713D000079FDA /* wren_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = "<group>"; };
@ -106,6 +110,8 @@
2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = "<group>"; };
29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = "<group>"; };
29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = "<group>"; };
29932D521C210F8D00099DEE /* lists.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = "<group>"; };
29932D531C210F8D00099DEE /* lists.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = "<group>"; };
29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_opt_meta.c; path = ../../src/optional/wren_opt_meta.c; sourceTree = "<group>"; };
29A4272F1BDBE435001E6E22 /* wren_opt_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = "<group>"; };
29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = wren_opt_meta.wren.inc; path = ../../src/optional/wren_opt_meta.wren.inc; sourceTree = "<group>"; };
@ -118,8 +124,8 @@
29D009A61B7E3993000CE58C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = "<group>"; };
29D009A81B7E39A8000CE58C /* foreign_class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = foreign_class.c; path = ../../test/api/foreign_class.c; sourceTree = "<group>"; };
29D009A91B7E39A8000CE58C /* foreign_class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = foreign_class.h; path = ../../test/api/foreign_class.h; sourceTree = "<group>"; };
29D009AA1B7E39A8000CE58C /* returns.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = returns.c; path = ../../test/api/returns.c; sourceTree = "<group>"; };
29D009AB1B7E39A8000CE58C /* returns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = returns.h; path = ../../test/api/returns.h; sourceTree = "<group>"; };
29D009AA1B7E39A8000CE58C /* slots.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = slots.c; path = ../../test/api/slots.c; sourceTree = "<group>"; };
29D009AB1B7E39A8000CE58C /* slots.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = slots.h; path = ../../test/api/slots.h; sourceTree = "<group>"; };
29D009AC1B7E39A8000CE58C /* value.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = value.c; path = ../../test/api/value.c; sourceTree = "<group>"; };
29D009AD1B7E39A8000CE58C /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = value.h; path = ../../test/api/value.h; sourceTree = "<group>"; };
29F384111BD19706002F84E0 /* io.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/module/io.h; sourceTree = "<group>"; };
@ -249,8 +255,12 @@
293D46951BB43F9900200083 /* call.h */,
29D009A81B7E39A8000CE58C /* foreign_class.c */,
29D009A91B7E39A8000CE58C /* foreign_class.h */,
29D009AA1B7E39A8000CE58C /* returns.c */,
29D009AB1B7E39A8000CE58C /* returns.h */,
2949AA8B1C2F14F000B106BA /* get_variable.c */,
2949AA8C1C2F14F000B106BA /* get_variable.h */,
29932D521C210F8D00099DEE /* lists.c */,
29932D531C210F8D00099DEE /* lists.h */,
29D009AA1B7E39A8000CE58C /* slots.c */,
29D009AB1B7E39A8000CE58C /* slots.h */,
29D009AC1B7E39A8000CE58C /* value.c */,
29D009AD1B7E39A8000CE58C /* value.h */,
);
@ -358,7 +368,9 @@
files = (
29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */,
29729F321BA70A620099CA20 /* io.c in Sources */,
29932D541C210F8D00099DEE /* lists.c in Sources */,
291647C81BA5EC5E006142EE /* modules.c in Sources */,
2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */,
29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */,
29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */,
29932D511C20D8C900099DEE /* benchmark.c in Sources */,
@ -376,7 +388,7 @@
293D46961BB43F9900200083 /* call.c in Sources */,
29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */,
29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */,
29DC14AC1BBA303D008A8274 /* returns.c in Sources */,
29DC14AC1BBA303D008A8274 /* slots.c in Sources */,
29DC14AD1BBA3040008A8274 /* value.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;