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

View File

@ -21,7 +21,7 @@ Prints a single newline to the console.
### System.**print**(object) ### 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. the object is converted to a string by calling `toString` on it.
:::wren :::wren
@ -29,7 +29,7 @@ the object is converted to a string by calling `toString` on it.
### System.**printAll**(sequence) ### 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. at the end. Each element is converted to a string by calling `toString` on it.
:::wren :::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 ^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 ## 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" ^title Module "io"
**TODO** Provides access to operating system streams and the file system.
* [Directory](directory.html)
* [File](file.html) * [File](file.html)
* [Stat](stat.html)
* [Stdin](stdin.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 ^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> <section>
<h2>io classes</h2> <h2>io classes</h2>
<ul> <ul>
<li><a href="directory.html">Directory</a></li>
<li><a href="file.html">File</a></li> <li><a href="file.html">File</a></li>
<li><a href="stdin.html">Stdin</a></li> <li><a href="stdin.html">Stdin</a></li>
</ul> </ul>

View File

@ -7,10 +7,12 @@
#include "scheduler.wren.inc" #include "scheduler.wren.inc"
#include "timer.wren.inc" #include "timer.wren.inc"
extern void directoryList(WrenVM* vm);
extern void fileAllocate(WrenVM* vm); extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(WrenVM* vm); extern void fileFinalize(void* data);
extern void fileOpen(WrenVM* vm); extern void fileOpen(WrenVM* vm);
extern void fileSizePath(WrenVM* vm); extern void fileSizePath(WrenVM* vm);
extern void fileStatPath(WrenVM* vm);
extern void fileClose(WrenVM* vm); extern void fileClose(WrenVM* vm);
extern void fileDescriptor(WrenVM* vm); extern void fileDescriptor(WrenVM* vm);
extern void fileReadBytes(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. // 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 // Note that it also includes an extra slot for the sentinel value indicating
// the end of the list. // 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. // 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. // 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 // Note that it also includes an extra slot for the sentinel value indicating
// the end of the list. // the end of the list.
#define MAX_CLASSES_PER_MODULE 3 #define MAX_CLASSES_PER_MODULE 4
// Describes one foreign method in a class. // Describes one foreign method in a class.
typedef struct typedef struct
@ -82,19 +84,24 @@ typedef struct
#define METHOD(signature, fn) { false, signature, fn }, #define METHOD(signature, fn) { false, signature, fn },
#define STATIC_METHOD(signature, fn) { true, signature, fn }, #define STATIC_METHOD(signature, fn) { true, signature, fn },
#define FINALIZER(fn) { true, "<finalize>", (WrenForeignMethodFn)fn },
// The array of built-in modules. // The array of built-in modules.
static ModuleRegistry modules[] = static ModuleRegistry modules[] =
{ {
MODULE(io) MODULE(io)
CLASS(Directory)
STATIC_METHOD("list_(_,_)", directoryList)
END_CLASS
CLASS(File) CLASS(File)
STATIC_METHOD("<allocate>", fileAllocate) STATIC_METHOD("<allocate>", fileAllocate)
STATIC_METHOD("<finalize>", fileFinalize) FINALIZER(fileFinalize)
STATIC_METHOD("open_(_,_)", fileOpen) STATIC_METHOD("open_(_,_)", fileOpen)
STATIC_METHOD("sizePath_(_,_)", fileSizePath) STATIC_METHOD("sizePath_(_,_)", fileSizePath)
STATIC_METHOD("statPath_(_,_)", fileStatPath)
METHOD("close_(_)", fileClose) METHOD("close_(_)", fileClose)
METHOD("descriptor", fileDescriptor) METHOD("descriptor", fileDescriptor)
METHOD("readBytes_(_,_)", fileReadBytes) METHOD("readBytes_(_,_,_)", fileReadBytes)
METHOD("size_(_)", fileSize) METHOD("size_(_)", fileSize)
END_CLASS END_CLASS
CLASS(Stdin) CLASS(Stdin)
@ -204,7 +211,7 @@ WrenForeignClassMethods bindBuiltInForeignClass(
if (clas == NULL) return methods; if (clas == NULL) return methods;
methods.allocate = findMethod(clas, true, "<allocate>"); methods.allocate = findMethod(clas, true, "<allocate>");
methods.finalize = findMethod(clas, true, "<finalize>"); methods.finalize = (WrenFinalizerFn)findMethod(clas, true, "<finalize>");
return methods; 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. // A function callable from Wren code, but implemented in C.
typedef void (*WrenForeignMethodFn)(WrenVM* vm); 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]. // Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name); typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
@ -64,7 +70,7 @@ typedef struct
// foreign object's memory. // foreign object's memory.
// //
// This may be `NULL` if the foreign class does not need to finalize. // This may be `NULL` if the foreign class does not need to finalize.
WrenForeignMethodFn finalize; WrenFinalizerFn finalize;
} WrenForeignClassMethods; } WrenForeignClassMethods;
// Returns a pair of pointers to the foreign methods used to allocate and // 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. // If this is `NULL`, Wren discards any printed text.
WrenWriteFn writeFn; WrenWriteFn writeFn;
// The number of bytes Wren will allocate before triggering the first garbage // The number of bytes Wren will allocate before triggering the first garbage
// collection. // collection.
// //
@ -183,53 +189,71 @@ void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm]. // Runs [source], a string of Wren source code in a new fiber in [vm].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source); WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source);
// Creates a handle that can be used to invoke a method with [signature] on the // Creates a handle that can be used to invoke a method with [signature] on
// object in [module] currently stored in top-level [variable]. // 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 // This handle can be used repeatedly to directly invoke that method from C
// code using [wrenCall]. // code using [wrenCall].
// //
// When done with this handle, it must be released using [wrenReleaseValue]. // When you are done with this handle, it must be released using
WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable, // [wrenReleaseValue].
const char* signature); WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// Calls [method], passing in a series of arguments whose types must match the // Calls [method], using the receiver and arguments previously set up on the
// specifed [argTypes]. This is a string where each character identifies the // stack.
// type of a single argument, in order. The allowed types are:
// //
// - "b" - A C `int` converted to a Wren Bool. // [method] must have been created by a call to [wrenMakeCallHandle]. The
// - "d" - A C `double` converted to a Wren Num. // arguments to the method must be already on the stack. The receiver should be
// - "i" - A C `int` converted to a Wren Num. // in slot 0 with the remaining arguments following it, in order. It is an
// - "s" - A C null-terminated `const char*` converted to a Wren String. Wren // error if the number of arguments provided does not match the method's
// will allocate its own string and copy the characters from this, so // signature.
// 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 [wrenGetMethod]. If // After this returns, you can access the return value from slot 0 on the stack.
// [returnValue] is not `NULL`, the return value of the method will be stored WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method);
// 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);
// Releases the reference stored in [value]. After calling this, [value] can no // Releases the reference stored in [value]. After calling this, [value] can no
// longer be used. // longer be used.
void wrenReleaseValue(WrenVM* vm, WrenValue* value); 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. // 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 // 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. // the foreign object's data.
void* wrenAllocateForeign(WrenVM* vm, size_t size); void* wrenAllocateForeign(WrenVM* vm, size_t size);
// Returns the number of arguments available to the current foreign method. // Returns the number of slots available to the current foreign method.
int wrenGetArgumentCount(WrenVM* vm); int wrenGetSlotCount(WrenVM* vm);
// The following functions read one of the arguments passed to a foreign call. // Ensures that the foreign method stack has at least [numSlots] available for
// They may only be called while within a function provided to // use, growing the stack if needed.
// [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked.
// //
// They retreive the argument at a given index which ranges from 0 to the number // Does not shrink the stack if it has more than enough slots.
// 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:
// //
// "receiver".foo("one", "two", "three") // It is an error to call this from a finalizer.
// void wrenEnsureSlots(WrenVM* vm, int numSlots);
// 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.
// Reads a boolean argument for a foreign call. Returns false if the argument // Reads a boolean value from [slot].
// is not a boolean. //
bool wrenGetArgumentBool(WrenVM* vm, int index); // 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 // Reads a byte array from [slot].
// 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.
// //
// The memory for the returned string is owned by Wren. You can inspect it // 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. // 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 // This will prevent the object that is referred to from being garbage collected
// until the handle is released by calling [wrenReleaseValue()]. // 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 // 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 // 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 // call one of these once. It is an error to access any of the foreign calls
// arguments after one of these has been called. // arguments after one of these has been called.
// Provides a boolean return value for a foreign call. // Stores the boolean [value] in [slot].
void wrenReturnBool(WrenVM* vm, bool value); void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
// Provides a numeric return value for a foreign call. // Stores the array [length] of [bytes] in [slot].
void wrenReturnDouble(WrenVM* vm, double value); //
// 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. // Stores the numeric [value] in [slot].
// void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// 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);
// 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 // The [text] is copied to a new string within Wren's heap, so you can free
// does not release the handle. // memory used by it after this is called. The length is calculated using
void wrenReturnValue(WrenVM* vm, WrenValue* value); // [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 #endif

View File

@ -18,7 +18,10 @@ typedef struct sFileRequestData
static const int stdinDescriptor = 0; 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; static WrenValue* stdinOnData = NULL;
// The stream used to read from stdin. Initialized on the first read. // The stream used to read from stdin. Initialized on the first read.
@ -34,6 +37,12 @@ static void shutdownStdin()
stdinStream = NULL; stdinStream = NULL;
} }
if (stdinClass != NULL)
{
wrenReleaseValue(getVM(), stdinClass);
stdinClass = NULL;
}
if (stdinOnData != NULL) if (stdinOnData != NULL)
{ {
wrenReleaseValue(getVM(), stdinOnData); wrenReleaseValue(getVM(), stdinOnData);
@ -46,26 +55,6 @@ void ioShutdown()
shutdownStdin(); 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 // If [request] failed with an error, sends the runtime error to the VM and
// frees the request. // frees the request.
// //
@ -73,12 +62,12 @@ void fileFinalize(WrenVM* vm)
static bool handleRequestError(uv_fs_t* request) static bool handleRequestError(uv_fs_t* request)
{ {
if (request->result >= 0) return false; if (request->result >= 0) return false;
FileRequestData* data = (FileRequestData*)request->data; FileRequestData* data = (FileRequestData*)request->data;
WrenValue* fiber = (WrenValue*)data->fiber; WrenValue* fiber = (WrenValue*)data->fiber;
schedulerResumeError(fiber, uv_strerror((int)request->result)); schedulerResumeError(fiber, uv_strerror((int)request->result));
free(data); free(data);
uv_fs_req_cleanup(request); uv_fs_req_cleanup(request);
free(request); free(request);
@ -89,10 +78,10 @@ static bool handleRequestError(uv_fs_t* request)
uv_fs_t* createRequest(WrenValue* fiber) uv_fs_t* createRequest(WrenValue* fiber)
{ {
uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t)); uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t));
FileRequestData* data = (FileRequestData*)malloc(sizeof(FileRequestData)); FileRequestData* data = (FileRequestData*)malloc(sizeof(FileRequestData));
data->fiber = fiber; data->fiber = fiber;
request->data = data; request->data = data;
return request; return request;
} }
@ -104,84 +93,189 @@ WrenValue* freeRequest(uv_fs_t* request)
{ {
FileRequestData* data = (FileRequestData*)request->data; FileRequestData* data = (FileRequestData*)request->data;
WrenValue* fiber = data->fiber; WrenValue* fiber = data->fiber;
free(data); free(data);
uv_fs_req_cleanup(request); uv_fs_req_cleanup(request);
free(request); free(request);
return fiber; 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; if (handleRequestError(request)) return;
double fd = (double)request->result; double fd = (double)request->result;
WrenValue* fiber = freeRequest(request); schedulerResume(freeRequest(request), true);
wrenSetSlotDouble(getVM(), 2, fd);
schedulerResumeDouble(fiber, fd); schedulerFinishResume();
} }
void fileOpen(WrenVM* vm) void fileOpen(WrenVM* vm)
{ {
const char* path = wrenGetArgumentString(vm, 1); const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
// TODO: Allow controlling flags and modes. // 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. // 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; if (handleRequestError(request)) return;
double size = (double)request->statbuf.st_size; double size = (double)request->statbuf.st_size;
WrenValue* fiber = freeRequest(request); schedulerResume(freeRequest(request), true);
wrenSetSlotDouble(getVM(), 2, size);
schedulerResumeDouble(fiber, size); schedulerFinishResume();
} }
void fileSizePath(WrenVM* vm) void fileSizePath(WrenVM* vm)
{ {
const char* path = wrenGetArgumentString(vm, 1); const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
uv_fs_stat(getLoop(), request, path, sizeCallback); 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; if (handleRequestError(request)) return;
WrenValue* fiber = freeRequest(request); WrenVM* vm = getVM();
schedulerResume(fiber); 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) void fileClose(WrenVM* vm)
{ {
int* foreign = (int*)wrenGetArgumentForeign(vm, 0); int* foreign = (int*)wrenGetSlotForeign(vm, 0);
int fd = *foreign; int fd = *foreign;
// If it's already closed, we're done. // If it's already closed, we're done.
if (fd == -1) if (fd == -1)
{ {
wrenReturnBool(vm, true); wrenSetSlotBool(vm, 0, true);
return; return;
} }
// Mark it closed immediately. // Mark it closed immediately.
*foreign = -1; *foreign = -1;
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1)); uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1));
uv_fs_close(getLoop(), request, fd, closeCallback); uv_fs_close(getLoop(), request, fd, fileCloseCallback);
wrenReturnBool(vm, false); wrenSetSlotBool(vm, 0, false);
} }
void fileDescriptor(WrenVM* vm) void fileDescriptor(WrenVM* vm)
{ {
int* foreign = (int*)wrenGetArgumentForeign(vm, 0); int* foreign = (int*)wrenGetSlotForeign(vm, 0);
int fd = *foreign; int fd = *foreign;
wrenReturnDouble(vm, fd); wrenSetSlotDouble(vm, 0, fd);
} }
static void fileReadBytesCallback(uv_fs_t* request) static void fileReadBytesCallback(uv_fs_t* request)
@ -190,43 +284,45 @@ static void fileReadBytesCallback(uv_fs_t* request)
FileRequestData* data = (FileRequestData*)request->data; FileRequestData* data = (FileRequestData*)request->data;
uv_buf_t buffer = data->buffer; uv_buf_t buffer = data->buffer;
size_t count = request->result;
WrenValue* fiber = freeRequest(request);
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's // 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 // embedding API supported a way to *give* it bytes that were previously
// allocated using Wren's own allocator. // 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. // TODO: Likewise, freeing this after we resume is lame.
free(buffer.base); free(buffer.base);
} }
void fileReadBytes(WrenVM* vm) void fileReadBytes(WrenVM* vm)
{ {
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 3));
int fd = *(int*)wrenGetArgumentForeign(vm, 0); int fd = *(int*)wrenGetSlotForeign(vm, 0);
// TODO: Assert fd != -1. // TODO: Assert fd != -1.
FileRequestData* data = (FileRequestData*)request->data; 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.len = length;
data->buffer.base = (char*)malloc(length); data->buffer.base = (char*)malloc(length);
// TODO: Allow passing in offset. uv_fs_read(getLoop(), request, fd, &data->buffer, 1, offset,
uv_fs_read(getLoop(), request, fd, &data->buffer, 1, 0, fileReadBytesCallback); fileReadBytesCallback);
} }
void fileSize(WrenVM* vm) 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. // 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, 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, static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead,
const uv_buf_t* buffer) const uv_buf_t* buffer)
{ {
// If stdin was closed, send null to let io.wren know. WrenVM* vm = getVM();
if (numRead == UV_EOF)
{
wrenCall(getVM(), stdinOnData, NULL, "v", NULL);
shutdownStdin();
return;
}
// TODO: Handle other errors. if (stdinClass == NULL)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "io", "Stdin", 0);
stdinClass = wrenGetSlotValue(vm, 0);
}
if (stdinOnData == NULL) 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 // 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 // embedding API supported a way to *give* it bytes that were previously
// allocated using Wren's own allocator. // 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. // TODO: Likewise, freeing this after we resume is lame.
free(buffer->base); free(buffer->base);
} }
@ -284,7 +396,7 @@ void stdinReadStart(WrenVM* vm)
stdinStream = (uv_stream_t*)handle; stdinStream = (uv_stream_t*)handle;
} }
} }
uv_read_start(stdinStream, allocCallback, stdinReadCallback); uv_read_start(stdinStream, allocCallback, stdinReadCallback);
// TODO: Check return. // TODO: Check return.
} }

View File

@ -1,5 +1,16 @@
import "scheduler" for Scheduler 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 { foreign class File {
static def open(path) { static def open(path) {
if (!(path is String)) Fiber.abort("Path must be a string.") if (!(path is String)) Fiber.abort("Path must be a string.")
@ -33,6 +44,13 @@ foreign class File {
return Scheduler.runNextScheduled_() 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) {} construct new_(fd) {}
def close() { def close() {
@ -40,6 +58,8 @@ foreign class File {
Scheduler.runNextScheduled_() Scheduler.runNextScheduled_()
} }
foreign def descriptor
def isOpen { descriptor != -1 } def isOpen { descriptor != -1 }
def size { def size {
@ -49,25 +69,48 @@ foreign class File {
return Scheduler.runNextScheduled_() 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 (!isOpen) Fiber.abort("File is not open.")
if (!(count is Num)) Fiber.abort("Count must be an integer.") if (!(count is Num)) Fiber.abort("Count must be an integer.")
if (!count.isInteger) 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.") 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_() return Scheduler.runNextScheduled_()
} }
foreign static def open_(path, fiber) foreign static def open_(path, fiber)
foreign static def sizePath_(path, fiber) foreign static def sizePath_(path, fiber)
foreign static def statPath_(path, fiber)
foreign def close_(fiber) foreign def close_(fiber)
foreign def descriptor foreign def readBytes_(count, start, fiber)
foreign def readBytes_(count, fiber)
foreign def size_(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 { class Stdin {
static def readLine() { static def readLine() {
if (__isClosed == true) { if (__isClosed == true) {
@ -90,6 +133,7 @@ class Stdin {
readStop_() readStop_()
if (__line != null) { if (__line != null) {
// Emit the last line.
var line = __line var line = __line
__line = null __line = null
if (__waitingFiber != null) __waitingFiber.transfer(line) if (__waitingFiber != null) __waitingFiber.transfer(line)

View File

@ -2,6 +2,17 @@
static const char* ioModuleSource = static const char* ioModuleSource =
"import \"scheduler\" for Scheduler\n" "import \"scheduler\" for Scheduler\n"
"\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" "foreign class File {\n"
" static def open(path) {\n" " static def open(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\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" " return Scheduler.runNextScheduled_()\n"
" }\n" " }\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" " construct new_(fd) {}\n"
"\n" "\n"
" def close() {\n" " def close() {\n"
@ -42,6 +60,8 @@ static const char* ioModuleSource =
" Scheduler.runNextScheduled_()\n" " Scheduler.runNextScheduled_()\n"
" }\n" " }\n"
"\n" "\n"
" foreign def descriptor\n"
"\n"
" def isOpen { descriptor != -1 }\n" " def isOpen { descriptor != -1 }\n"
"\n" "\n"
" def size {\n" " def size {\n"
@ -51,25 +71,48 @@ static const char* ioModuleSource =
" return Scheduler.runNextScheduled_()\n" " return Scheduler.runNextScheduled_()\n"
" }\n" " }\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 (!isOpen) Fiber.abort(\"File is not open.\")\n"
" if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\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.isInteger) Fiber.abort(\"Count must be an integer.\")\n"
" if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n" " if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n"
"\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" " return Scheduler.runNextScheduled_()\n"
" }\n" " }\n"
"\n" "\n"
" foreign static def open_(path, fiber)\n" " foreign static def open_(path, fiber)\n"
" foreign static def sizePath_(path, fiber)\n" " foreign static def sizePath_(path, fiber)\n"
" foreign static def statPath_(path, fiber)\n"
"\n" "\n"
" foreign def close_(fiber)\n" " foreign def close_(fiber)\n"
" foreign def descriptor\n" " foreign def readBytes_(count, start, fiber)\n"
" foreign def readBytes_(count, fiber)\n"
" foreign def size_(fiber)\n" " foreign def size_(fiber)\n"
"}\n" "}\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" "class Stdin {\n"
" static def readLine() {\n" " static def readLine() {\n"
" if (__isClosed == true) {\n" " if (__isClosed == true) {\n"
@ -92,6 +135,7 @@ static const char* ioModuleSource =
" readStop_()\n" " readStop_()\n"
"\n" "\n"
" if (__line != null) {\n" " if (__line != null) {\n"
" // Emit the last line.\n"
" var line = __line\n" " var line = __line\n"
" __line = null\n" " __line = null\n"
" if (__waitingFiber != null) __waitingFiber.transfer(line)\n" " if (__waitingFiber != null) __waitingFiber.transfer(line)\n"

View File

@ -7,30 +7,19 @@
#include "wren.h" #include "wren.h"
#include "vm.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 // This method resumes a fiber that is suspended waiting on an asynchronous
// operation. The first resumes it with zero arguments, and the second passes // operation. The first resumes it with zero arguments, and the second passes
// one. // one.
static WrenValue* resume; static WrenValue* resume1;
static WrenValue* resumeWithArg; static WrenValue* resume2;
static WrenValue* resumeError; static WrenValue* resumeError;
void schedulerCaptureMethods(WrenVM* vm) static void resume(WrenValue* method)
{ {
resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)"); WrenInterpretResult result = wrenCall(getVM(), method);
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);
// If a runtime error occurs in response to an async operation and nothing // If a runtime error occurs in response to an async operation and nothing
// catches the error in the fiber, then exit the CLI. // 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); resume(resume2);
}
void schedulerResumeString(WrenValue* fiber, const char* text)
{
callResume(resumeWithArg, fiber, "vs", fiber, text);
} }
void schedulerResumeError(WrenValue* fiber, const char* error) void schedulerResumeError(WrenValue* fiber, const char* error)
{ {
callResume(resumeError, fiber, "vs", fiber, error); schedulerResume(fiber, true);
wrenSetSlotString(getVM(), 2, error);
resume(resumeError);
} }
void schedulerShutdown() void schedulerShutdown()
{ {
if (resume != NULL) wrenReleaseValue(getVM(), resume); // If the module was never loaded, we don't have anything to release.
if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg); if (schedulerClass == NULL) return;
if (resumeError != NULL) wrenReleaseValue(getVM(), resumeError);
WrenVM* vm = getVM();
wrenReleaseValue(vm, schedulerClass);
wrenReleaseValue(vm, resume1);
wrenReleaseValue(vm, resume2);
wrenReleaseValue(vm, resumeError);
} }

View File

@ -3,10 +3,16 @@
#include "wren.h" #include "wren.h"
void schedulerResume(WrenValue* fiber); // Sets up the API stack to call one of the resume methods on Scheduler.
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length); //
void schedulerResumeDouble(WrenValue* fiber, double value); // If [hasArgument] is false, this just sets up the stack to have another
void schedulerResumeString(WrenValue* fiber, const char* text); // 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 schedulerResumeError(WrenValue* fiber, const char* error);
void schedulerShutdown(); void schedulerShutdown();

View File

@ -22,13 +22,13 @@ static void timerCallback(uv_timer_t* handle)
uv_close((uv_handle_t*)handle, timerCloseCallback); uv_close((uv_handle_t*)handle, timerCloseCallback);
// Run the fiber that was sleeping. // Run the fiber that was sleeping.
schedulerResume(fiber); schedulerResume(fiber, false);
} }
void timerStartTimer(WrenVM* vm) void timerStartTimer(WrenVM* vm)
{ {
int milliseconds = (int)wrenGetArgumentDouble(vm, 1); int milliseconds = (int)wrenGetSlotDouble(vm, 1);
WrenValue* fiber = wrenGetArgumentValue(vm, 2); WrenValue* fiber = wrenGetSlotValue(vm, 2);
// Store the fiber to resume when the timer completes. // Store the fiber to resume when the timer completes.
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t)); 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; : AS_CLOSURE(callingFn)->fn->module;
// Compile it. // Compile it.
ObjFn* fn = wrenCompile(vm, module, wrenGetArgumentString(vm, 1), false); ObjFn* fn = wrenCompile(vm, module, wrenGetSlotString(vm, 1), false);
if (fn == NULL) return;
// Return the result. We can't use the public API for this since we have a // Return the result. We can't use the public API for this since we have a
// bare ObjFn. // bare ObjFn.
*vm->foreignStackStart = OBJ_VAL(fn); vm->apiStack[0] = fn != NULL ? OBJ_VAL(fn) : NULL_VAL;
vm->foreignStackStart = NULL;
} }
static WrenForeignMethodFn bindMetaForeignMethods(WrenVM* vm, static WrenForeignMethodFn bindMetaForeignMethods(WrenVM* vm,

View File

@ -45,7 +45,7 @@ static void randomAllocate(WrenVM* vm)
static void randomSeed0(WrenVM* vm) static void randomSeed0(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0); Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)time(NULL)); srand((uint32_t)time(NULL));
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
@ -56,9 +56,9 @@ static void randomSeed0(WrenVM* vm)
static void randomSeed1(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++) for (int i = 0; i < 16; i++)
{ {
well->state[i] = rand(); well->state[i] = rand();
@ -67,17 +67,17 @@ static void randomSeed1(WrenVM* vm)
static void randomSeed16(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++) 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) 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 // 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. // 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). // from 0 to 1.0 (half-inclusive).
result /= 9007199254740992.0; result /= 9007199254740992.0;
wrenReturnDouble(vm, result); wrenSetSlotDouble(vm, 0, result);
} }
static void randomInt0(WrenVM* vm) 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 // 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 // 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 // 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) static int addUpvalue(Compiler* compiler, bool isLocal, int index)
{ {
// Look for an existing one. // 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. // Switching to a null fiber tells the interpreter to stop and exit.
vm->fiber = NULL; vm->fiber = NULL;
vm->apiStack = NULL;
return false; return false;
} }

View File

@ -20,6 +20,9 @@ void wrenDebugPrintStackTrace(ObjFiber* fiber)
CallFrame* frame = &fiber->frames[i]; CallFrame* frame = &fiber->frames[i];
ObjFn* fn = wrenUpwrapClosure(frame->fn); 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 // 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 // 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. // 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_RANGE: printf("[range %p]", obj); break;
case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break;
case OBJ_UPVALUE: printf("[upvalue %p]", obj); 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. // Allocate the arrays before the fiber in case it triggers a GC.
CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); 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); Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity);
ObjFiber* fiber = ALLOCATE(vm, ObjFiber); ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
@ -179,10 +183,52 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn)
fiber->caller = NULL; fiber->caller = NULL;
fiber->error = NULL_VAL; fiber->error = NULL_VAL;
fiber->callerIsTrying = false; fiber->callerIsTrying = false;
fiber->numFrames = 0;
// Initialize the first call frame. // Initialize the first call frame.
fiber->numFrames = 0; if (fn != NULL) wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
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) 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)); case VAL_OBJ: return hashObject(AS_OBJ(value));
default: UNREACHABLE(); default: UNREACHABLE();
} }
return 0;
#endif #endif
} }
@ -1163,10 +1211,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); wrenValueBufferClear(vm, &((ObjModule*)obj)->variables);
break; break;
case OBJ_STRING:
case OBJ_CLOSURE: case OBJ_CLOSURE:
case OBJ_INSTANCE: case OBJ_INSTANCE:
case OBJ_RANGE: case OBJ_RANGE:
case OBJ_STRING:
case OBJ_UPVALUE: case OBJ_UPVALUE:
break; break;
} }

View File

@ -74,6 +74,7 @@
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn #define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign #define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance #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_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString #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; 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); ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
// TODO: The argument list here is getting a bit gratuitous. // TODO: The argument list here is getting a bit gratuitous.

View File

@ -65,7 +65,7 @@ WrenVM* wrenNewVM(WrenConfiguration* config)
vm->modules = wrenNewMap(vm); vm->modules = wrenNewMap(vm);
wrenInitializeCore(vm); wrenInitializeCore(vm);
// TODO: Lazy load these. // TODO: Lazy load these.
#if WREN_OPT_META #if WREN_OPT_META
wrenLoadMetaModule(vm); wrenLoadMetaModule(vm);
@ -137,7 +137,7 @@ void wrenCollectGarbage(WrenVM* vm)
wrenGrayObj(vm, vm->tempRoots[i]); wrenGrayObj(vm, vm->tempRoots[i]);
} }
// The current fiber. // The rooted fiber.
wrenGrayObj(vm, (Obj*)vm->fiber); wrenGrayObj(vm, (Obj*)vm->fiber);
// The value handles. // The value handles.
@ -348,19 +348,14 @@ static void bindMethod(WrenVM* vm, int methodType, int symbol,
static void callForeign(WrenVM* vm, ObjFiber* fiber, static void callForeign(WrenVM* vm, ObjFiber* fiber,
WrenForeignMethodFn foreign, int numArgs) WrenForeignMethodFn foreign, int numArgs)
{ {
vm->foreignStackStart = fiber->stackTop - numArgs; vm->apiStack = fiber->stackTop - numArgs;
foreign(vm); foreign(vm);
// Discard the stack slots for the arguments (but leave one for // Discard the stack slots for the arguments and temporaries but leave one
// the result). // for the result.
fiber->stackTop -= numArgs - 1; fiber->stackTop = vm->apiStack + 1;
vm->apiStack = NULL;
// If nothing was returned, implicitly return null.
if (vm->foreignStackStart != NULL)
{
*vm->foreignStackStart = NULL_VAL;
vm->foreignStackStart = NULL;
}
} }
// Handles the current fiber having aborted because of an error. Switches to // 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. // Grow the stack if needed.
int stackSize = (int)(fiber->stackTop - fiber->stack); int stackSize = (int)(fiber->stackTop - fiber->stack);
int needed = stackSize + wrenUpwrapClosure(function)->maxSlots; 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); wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
} }
@ -689,7 +651,7 @@ static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
if (methods.finalize != NULL) if (methods.finalize != NULL)
{ {
method.fn.foreign = methods.finalize; method.fn.foreign = (WrenForeignMethodFn)methods.finalize;
wrenBindMethod(vm, classObj, symbol, method); 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."); ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
// Pass the constructor arguments to the allocator as well. // Pass the constructor arguments to the allocator as well.
vm->foreignStackStart = stack; vm->apiStack = stack;
method->fn.foreign(vm); method->fn.foreign(vm);
@ -767,11 +729,8 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign.");
// Pass the constructor arguments to the allocator as well. WrenFinalizerFn finalizer = (WrenFinalizerFn)method->fn.foreign;
Value slot = OBJ_VAL(foreign); finalizer(foreign->data);
vm->foreignStackStart = &slot;
method->fn.foreign(vm);
} }
// The main bytecode interpreter loop. This is where the magic happens. It is // 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 the fiber is complete, end it.
if (fiber->numFrames == 0) if (fiber->numFrames == 0)
{ {
// See if there's another fiber to return to. // See if there's another fiber to return to. If not, we're done.
ObjFiber* callingFiber = fiber->caller; 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->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. // Store the result in the resuming fiber.
*(fiber->stackTop - 1) = result; fiber->stackTop[-1] = result;
} }
else else
{ {
@ -1202,7 +1166,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
// result). // result).
fiber->stackTop = frame->stackStart + 1; fiber->stackTop = frame->stackStart + 1;
} }
LOAD_FRAME(); LOAD_FRAME();
DISPATCH(); DISPATCH();
} }
@ -1330,11 +1294,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
#undef READ_SHORT #undef READ_SHORT
} }
// Creates an [ObjFn] that invokes a method with [signature] when called. WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature)
static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
{ {
int signatureLength = (int)strlen(signature); int signatureLength = (int)strlen(signature);
// Count the number parameters the method expects. // Count the number parameters the method expects.
int numParams = 0; int numParams = 0;
if (signature[signatureLength - 1] == ')') if (signature[signatureLength - 1] == ')')
@ -1345,167 +1308,73 @@ static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
if (*s == '_') numParams++; if (*s == '_') numParams++;
} }
} }
// Add the signatue to the method table.
int method = wrenSymbolTableEnsure(vm, &vm->methodNames, int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
signature, signatureLength); 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); uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5);
bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams); bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams);
bytecode[1] = (method >> 8) & 0xff; bytecode[1] = (method >> 8) & 0xff;
bytecode[2] = method & 0xff; bytecode[2] = method & 0xff;
bytecode[3] = CODE_RETURN; bytecode[3] = CODE_RETURN;
bytecode[4] = CODE_END; bytecode[4] = CODE_END;
int* debugLines = ALLOCATE_ARRAY(vm, int, 5); int* debugLines = ALLOCATE_ARRAY(vm, int, 5);
memset(debugLines, 1, 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, // Wrap the function in a handle.
signature, signatureLength, debugLines); return wrenCaptureValue(vm, OBJ_VAL(fn));
} }
WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable, WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method)
const char* signature)
{ {
Value moduleName = wrenStringFormat(vm, "$", module); ASSERT(method != NULL, "Method cannot be NULL.");
wrenPushRoot(vm, AS_OBJ(moduleName)); ASSERT(IS_FN(method->value), "Method must be a method handle.");
ASSERT(vm->fiber != NULL, "Must set up arguments for call first.");
ObjModule* moduleObj = getModule(vm, moduleName); ASSERT(vm->apiStack != NULL, "Must set up arguments for call first.");
// TODO: Handle module not being found. ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method.");
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, ObjFn* fn = AS_FN(method->value);
variable, strlen(variable));
// TODO: Handle the variable not being found. ASSERT(vm->fiber->stackTop - vm->fiber->stack >= fn->arity,
"Stack must have enough arguments for method.");
ObjFn* fn = makeCallStub(vm, moduleObj, signature);
wrenPushRoot(vm, (Obj*)fn); // Discard any extra temporary slots. We take for granted that the stub
// function has exactly one slot for each arguments.
// Create a single fiber that we can reuse each time the method is invoked. vm->fiber->stackTop = &vm->fiber->stack[fn->maxSlots];
ObjFiber* fiber = wrenNewFiber(vm, (Obj*)fn);
wrenPushRoot(vm, (Obj*)fiber); callFunction(vm, vm->fiber, (Obj*)fn, 0);
return runInterpreter(vm, vm->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;
} }
WrenValue* wrenCaptureValue(WrenVM* vm, Value value) WrenValue* wrenCaptureValue(WrenVM* vm, Value value)
{ {
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
// Make a handle for it. // Make a handle for it.
WrenValue* wrappedValue = ALLOCATE(vm, WrenValue); WrenValue* wrappedValue = ALLOCATE(vm, WrenValue);
wrappedValue->value = value; wrappedValue->value = value;
if (IS_OBJ(value)) wrenPopRoot(vm);
// Add it to the front of the linked list of handles. // Add it to the front of the linked list of handles.
if (vm->valueHandles != NULL) vm->valueHandles->prev = wrappedValue; if (vm->valueHandles != NULL) vm->valueHandles->prev = wrappedValue;
wrappedValue->prev = NULL; wrappedValue->prev = NULL;
wrappedValue->next = vm->valueHandles; wrappedValue->next = vm->valueHandles;
vm->valueHandles = wrappedValue; vm->valueHandles = wrappedValue;
return wrappedValue; return wrappedValue;
} }
void wrenReleaseValue(WrenVM* vm, WrenValue* value) 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. // Update the VM's head pointer if we're releasing the first handle.
if (vm->valueHandles == value) vm->valueHandles = value->next; 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) 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 // TODO: Validate this. It can fail if the user calls this inside another
// foreign method, or calls one of the return functions. // 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); ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
vm->foreignStackStart[0] = OBJ_VAL(foreign); vm->apiStack[0] = OBJ_VAL(foreign);
return (void*)foreign->data; return (void*)foreign->data;
} }
@ -1658,100 +1527,170 @@ void wrenPopRoot(WrenVM* vm)
vm->numTempRoots--; 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 return (int)(vm->fiber->stackTop - vm->apiStack);
// "stack" just has one object, the object being finalized.
if (vm->fiber == NULL) return 1;
return (int)(vm->fiber->stackTop - vm->foreignStackStart);
} }
static void validateForeignArgument(WrenVM* vm, int index) void wrenEnsureSlots(WrenVM* vm, int numSlots)
{ {
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); // If we don't have a fiber accessible, create one for the API to use.
ASSERT(index >= 0, "index cannot be negative."); if (vm->apiStack == NULL)
ASSERT(index < wrenGetArgumentCount(vm), "Not that many arguments."); {
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); ASSERT(slot >= 0, "Slot cannot be negative.");
ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots.");
if (!IS_BOOL(vm->foreignStackStart[index])) return false;
return AS_BOOL(vm->foreignStackStart[index]);
} }
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_BOOL(vm->apiStack[slot]);
return AS_NUM(vm->foreignStackStart[index]);
} }
void* wrenGetArgumentForeign(WrenVM* vm, int index) const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length)
{ {
validateForeignArgument(vm, index); validateApiSlot(vm, slot);
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
if (!IS_FOREIGN(vm->foreignStackStart[index])) return NULL;
ObjString* string = AS_STRING(vm->apiStack[slot]);
return AS_FOREIGN(vm->foreignStackStart[index])->data; *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_NUM(vm->apiStack[slot]);
return AS_CSTRING(vm->foreignStackStart[index]);
} }
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); return AS_CSTRING(vm->apiStack[slot]);
vm->foreignStackStart = NULL;
} }
void wrenReturnDouble(WrenVM* vm, double value) WrenValue* wrenGetSlotValue(WrenVM* vm, int slot)
{ {
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); validateApiSlot(vm, slot);
return wrenCaptureValue(vm, vm->apiStack[slot]);
*vm->foreignStackStart = NUM_VAL(value);
vm->foreignStackStart = NULL;
} }
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."); ASSERT(text != NULL, "String cannot be NULL.");
setSlot(vm, slot, wrenNewString(vm, text, strlen(text)));
size_t size = length;
if (length == -1) size = strlen(text);
*vm->foreignStackStart = wrenNewString(vm, text, size);
vm->foreignStackStart = NULL;
} }
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."); ASSERT(value != NULL, "Value cannot be NULL.");
*vm->foreignStackStart = value->value; setSlot(vm, slot, value->value);
vm->foreignStackStart = NULL; }
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. // NULL if there are no handles.
WrenValue* valueHandles; WrenValue* valueHandles;
// Foreign function data: // 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
// During a foreign function call, this will point to the first argument (the // that is executing a method.
// receiver) of the call on the fiber's stack. //
Value* foreignStackStart; // 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; WrenConfiguration config;

View File

@ -1,21 +1,81 @@
#include <string.h> #include <string.h>
#include <time.h>
#include "benchmark.h" #include "benchmark.h"
static void arguments(WrenVM* vm) static void arguments(WrenVM* vm)
{ {
double result = 0; double result = 0;
result += wrenGetArgumentDouble(vm, 1);
result += wrenGetArgumentDouble(vm, 2); result += wrenGetSlotDouble(vm, 1);
result += wrenGetArgumentDouble(vm, 3); result += wrenGetSlotDouble(vm, 2);
result += wrenGetArgumentDouble(vm, 4); 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) WrenForeignMethodFn benchmarkBindMethod(const char* signature)
{ {
if (strcmp(signature, "static Benchmark.arguments(_,_,_,_)") == 0) return arguments; if (strcmp(signature, "static Benchmark.arguments(_,_,_,_)") == 0) return arguments;
if (strcmp(signature, "static Benchmark.call(_)") == 0) return call;
return NULL; return NULL;
} }

View File

@ -5,33 +5,84 @@
void callRunTests(WrenVM* vm) void callRunTests(WrenVM* vm)
{ {
WrenValue* noParams = wrenGetMethod(vm, "main", "Call", "noParams"); wrenEnsureSlots(vm, 1);
WrenValue* zero = wrenGetMethod(vm, "main", "Call", "zero()"); wrenGetVariable(vm, "main", "Call", 0);
WrenValue* one = wrenGetMethod(vm, "main", "Call", "one(_)"); WrenValue* callClass = wrenGetSlotValue(vm, 0);
WrenValue* two = wrenGetMethod(vm, "main", "Call", "two(_,_)");
WrenValue* noParams = wrenMakeCallHandle(vm, "noParams");
WrenValue* zero = wrenMakeCallHandle(vm, "zero()");
WrenValue* one = wrenMakeCallHandle(vm, "one(_)");
WrenValue* two = wrenMakeCallHandle(vm, "two(_,_)");
// Different arity. // Different arity.
wrenCall(vm, noParams, NULL, ""); wrenEnsureSlots(vm, 1);
wrenCall(vm, zero, NULL, ""); wrenSetSlotValue(vm, 0, callClass);
wrenCall(vm, one, NULL, "i", 1); wrenCall(vm, noParams);
wrenCall(vm, two, NULL, "ii", 1, 2);
wrenEnsureSlots(vm, 1);
WrenValue* getValue = wrenGetMethod(vm, "main", "Call", "getValue(_)"); 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. // Returning a value.
WrenValue* value = NULL; WrenValue* getValue = wrenMakeCallHandle(vm, "getValue()");
wrenCall(vm, getValue, &value, "v", NULL); wrenEnsureSlots(vm, 1);
wrenSetSlotValue(vm, 0, callClass);
wrenCall(vm, getValue);
WrenValue* value = wrenGetSlotValue(vm, 0);
// Different argument types. // Different argument types.
wrenCall(vm, two, NULL, "bb", true, false); wrenEnsureSlots(vm, 3);
wrenCall(vm, two, NULL, "dd", 1.2, 3.4); wrenSetSlotValue(vm, 0, callClass);
wrenCall(vm, two, NULL, "ii", 3, 4); wrenSetSlotBool(vm, 1, true);
wrenCall(vm, two, NULL, "ss", "string", "another"); wrenSetSlotBool(vm, 2, false);
wrenCall(vm, two, NULL, "vv", NULL, value); 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. // 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, noParams);
wrenReleaseValue(vm, zero); wrenReleaseValue(vm, zero);
wrenReleaseValue(vm, one); wrenReleaseValue(vm, one);

View File

@ -20,13 +20,7 @@ class Call {
System.print("two %(one) %(two)") System.print("two %(one) %(two)")
} }
static def getValue(value) { static def getValue() { ["a", "b"] }
// Return a new value if we aren't given one.
if (value == null) return ["a", "b"]
// Otherwise print it.
System.print(value)
}
} }
// expect: noParams // expect: noParams
@ -36,7 +30,7 @@ class Call {
// expect: two true false // expect: two true false
// expect: two 1.2 3.4 // expect: two 1.2 3.4
// expect: two 3 4
// expect: two string another // expect: two string another
// expect: two null [a, b] // expect: two null [a, b]
// expect: two str [98, 0, 121, 0, 116, 0, 101] // 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) static void apiFinalized(WrenVM* vm)
{ {
wrenReturnDouble(vm, finalized); wrenSetSlotDouble(vm, 0, finalized);
} }
static void counterAllocate(WrenVM* vm) static void counterAllocate(WrenVM* vm)
@ -18,25 +18,25 @@ static void counterAllocate(WrenVM* vm)
static void counterIncrement(WrenVM* vm) static void counterIncrement(WrenVM* vm)
{ {
double* value = (double*)wrenGetArgumentForeign(vm, 0); double* value = (double*)wrenGetSlotForeign(vm, 0);
double increment = wrenGetArgumentDouble(vm, 1); double increment = wrenGetSlotDouble(vm, 1);
*value += increment; *value += increment;
} }
static void counterValue(WrenVM* vm) static void counterValue(WrenVM* vm)
{ {
double value = *(double*)wrenGetArgumentForeign(vm, 0); double value = *(double*)wrenGetSlotForeign(vm, 0);
wrenReturnDouble(vm, value); wrenSetSlotDouble(vm, 0, value);
} }
static void pointAllocate(WrenVM* vm) static void pointAllocate(WrenVM* vm)
{ {
double* coordinates = (double*)wrenAllocateForeign(vm, sizeof(double[3])); 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. // which one was invoked.
if (wrenGetArgumentCount(vm) == 1) if (wrenGetSlotCount(vm) == 1)
{ {
coordinates[0] = 0.0; coordinates[0] = 0.0;
coordinates[1] = 0.0; coordinates[1] = 0.0;
@ -44,36 +44,41 @@ static void pointAllocate(WrenVM* vm)
} }
else else
{ {
coordinates[0] = wrenGetArgumentDouble(vm, 1); coordinates[0] = wrenGetSlotDouble(vm, 1);
coordinates[1] = wrenGetArgumentDouble(vm, 2); coordinates[1] = wrenGetSlotDouble(vm, 2);
coordinates[2] = wrenGetArgumentDouble(vm, 3); coordinates[2] = wrenGetSlotDouble(vm, 3);
} }
} }
static void pointTranslate(WrenVM* vm) static void pointTranslate(WrenVM* vm)
{ {
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0); double* coordinates = (double*)wrenGetSlotForeign(vm, 0);
coordinates[0] += wrenGetArgumentDouble(vm, 1); coordinates[0] += wrenGetSlotDouble(vm, 1);
coordinates[1] += wrenGetArgumentDouble(vm, 2); coordinates[1] += wrenGetSlotDouble(vm, 2);
coordinates[2] += wrenGetArgumentDouble(vm, 3); coordinates[2] += wrenGetSlotDouble(vm, 3);
} }
static void pointToString(WrenVM* vm) static void pointToString(WrenVM* vm)
{ {
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0); double* coordinates = (double*)wrenGetSlotForeign(vm, 0);
char result[100]; char result[100];
sprintf(result, "(%g, %g, %g)", sprintf(result, "(%g, %g, %g)",
coordinates[0], coordinates[1], coordinates[2]); coordinates[0], coordinates[1], coordinates[2]);
wrenReturnString(vm, result, (int)strlen(result)); wrenSetSlotString(vm, 0, result);
} }
static void resourceAllocate(WrenVM* vm) 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++; 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 "benchmark.h"
#include "call.h" #include "call.h"
#include "get_variable.h"
#include "foreign_class.h" #include "foreign_class.h"
#include "returns.h" #include "lists.h"
#include "slots.h"
#include "value.h" #include "value.h"
// The name of the currently executing API test. // The name of the currently executing API test.
@ -32,11 +34,17 @@ static WrenForeignMethodFn bindForeignMethod(
method = benchmarkBindMethod(fullName); method = benchmarkBindMethod(fullName);
if (method != NULL) return method; if (method != NULL) return method;
method = getVariableBindMethod(fullName);
if (method != NULL) return method;
method = foreignClassBindMethod(fullName); method = foreignClassBindMethod(fullName);
if (method != NULL) return method; if (method != NULL) return method;
method = returnsBindMethod(fullName); method = listsBindMethod(fullName);
if (method != NULL) return method;
method = slotsBindMethod(fullName);
if (method != NULL) return method; if (method != NULL) return method;
method = valueBindMethod(fullName); method = valueBindMethod(fullName);
@ -60,7 +68,7 @@ static WrenForeignClassMethods bindForeignClass(
} }
static void afterLoad(WrenVM* vm) { 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[]) 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) static void setValue(WrenVM* vm)
{ {
value = wrenGetArgumentValue(vm, 1); value = wrenGetSlotValue(vm, 1);
} }
static void getValue(WrenVM* vm) static void getValue(WrenVM* vm)
{ {
wrenReturnValue(vm, value); wrenSetSlotValue(vm, 0, value);
wrenReleaseValue(vm, 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. // Allows zero.
System.print(file.readBytes(0).bytes.count) // expect: 0 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() 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 "io" for File
import "scheduler" for Scheduler import "scheduler" for Scheduler
System.print(File.size("test/io/file/size.wren")) // expect: 270 var file = File.open("test/io/file/file.txt")
System.print(file.size) // expect: 19
// Runs asynchronously. file.close()
Scheduler.add {
System.print("async")
}
System.print(File.size("test/io/file/size.wren"))
// expect: async
// expect: 270

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) regex = re.compile(pattern + "\n" + r"elapsed: (\d+\.\d+)", re.MULTILINE)
BENCHMARKS.append([name, regex, None]) BENCHMARKS.append([name, regex, None])
BENCHMARK("api_call", "true")
BENCHMARK("api_foreign_method", "100000000") BENCHMARK("api_foreign_method", "100000000")
BENCHMARK("binary_trees", """stretch tree of depth 13 check: -1 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 # Make sure the stack trace has the right line. Skip over any lines that
# come from builtin libraries. # come from builtin libraries.
match = False
stack_lines = error_lines[line + 1:] stack_lines = error_lines[line + 1:]
match = None match = None
for stack_line in stack_lines: 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. # 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 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 $@ $< @ ./util/wren_to_c_string.py $@ $<
src/vm/wren_%.wren.inc: src/vm/wren_%.wren 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 $@ $< @ ./util/wren_to_c_string.py $@ $<
src/module/%.wren.inc: src/module/%.wren 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 $@ $< @ ./util/wren_to_c_string.py $@ $<
.PHONY: all cli test vm .PHONY: all cli test vm

View File

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

View File

@ -19,6 +19,7 @@
29205C9E1AB4E6430073018D /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; }; 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 */; }; 29205C9F1AB4E6430073018D /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; };
293D46961BB43F9900200083 /* call.c in Sources */ = {isa = PBXBuildFile; fileRef = 293D46941BB43F9900200083 /* call.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 */; }; 29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; };
29512C821B91F901008C10E6 /* 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 */; }; 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 */; }; 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 */; }; 2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; };
29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.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 */; }; 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 */; }; 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 */; }; 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 */; }; 29DC14A91BBA302F008A8274 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; };
29DC14AA1BBA3032008A8274 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A61B7E3993000CE58C /* main.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 */; }; 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 */; }; 29DC14AD1BBA3040008A8274 /* value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AC1B7E39A8000CE58C /* value.c */; };
/* End PBXBuildFile section */ /* 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 29D009AA1B7E39A8000CE58C /* slots.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = slots.c; path = ../../test/api/slots.c; sourceTree = "<group>"; };
29D009AB1B7E39A8000CE58C /* returns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = returns.h; path = ../../test/api/returns.h; 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>"; }; 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>"; }; 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>"; }; 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 */, 293D46951BB43F9900200083 /* call.h */,
29D009A81B7E39A8000CE58C /* foreign_class.c */, 29D009A81B7E39A8000CE58C /* foreign_class.c */,
29D009A91B7E39A8000CE58C /* foreign_class.h */, 29D009A91B7E39A8000CE58C /* foreign_class.h */,
29D009AA1B7E39A8000CE58C /* returns.c */, 2949AA8B1C2F14F000B106BA /* get_variable.c */,
29D009AB1B7E39A8000CE58C /* returns.h */, 2949AA8C1C2F14F000B106BA /* get_variable.h */,
29932D521C210F8D00099DEE /* lists.c */,
29932D531C210F8D00099DEE /* lists.h */,
29D009AA1B7E39A8000CE58C /* slots.c */,
29D009AB1B7E39A8000CE58C /* slots.h */,
29D009AC1B7E39A8000CE58C /* value.c */, 29D009AC1B7E39A8000CE58C /* value.c */,
29D009AD1B7E39A8000CE58C /* value.h */, 29D009AD1B7E39A8000CE58C /* value.h */,
); );
@ -358,7 +368,9 @@
files = ( files = (
29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */, 29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */,
29729F321BA70A620099CA20 /* io.c in Sources */, 29729F321BA70A620099CA20 /* io.c in Sources */,
29932D541C210F8D00099DEE /* lists.c in Sources */,
291647C81BA5EC5E006142EE /* modules.c in Sources */, 291647C81BA5EC5E006142EE /* modules.c in Sources */,
2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */,
29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */, 29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */,
29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */, 29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */,
29932D511C20D8C900099DEE /* benchmark.c in Sources */, 29932D511C20D8C900099DEE /* benchmark.c in Sources */,
@ -376,7 +388,7 @@
293D46961BB43F9900200083 /* call.c in Sources */, 293D46961BB43F9900200083 /* call.c in Sources */,
29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */, 29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */,
29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */, 29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */,
29DC14AC1BBA303D008A8274 /* returns.c in Sources */, 29DC14AC1BBA303D008A8274 /* slots.c in Sources */,
29DC14AD1BBA3040008A8274 /* value.c in Sources */, 29DC14AD1BBA3040008A8274 /* value.c in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;