mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-09 21:28:39 +01:00
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:
@ -61,8 +61,8 @@ any "if I got two arguments do this..." logic.
|
||||
|
||||
## Getters
|
||||
|
||||
Some methods exist to expose some stored or computed property of an object.
|
||||
These are *getters* and have no parentheses:
|
||||
Some methods exist to expose a stored or computed property of an object. These
|
||||
are *getters* and have no parentheses:
|
||||
|
||||
:::wren
|
||||
"string".count //> 6
|
||||
@ -70,8 +70,9 @@ These are *getters* and have no parentheses:
|
||||
1.23.sin //> 0.9424888019317
|
||||
[1, 2, 3].isEmpty //> false
|
||||
|
||||
Sometimes you have a method doesn't need any parameters, but modifies the object
|
||||
or has some other side effect. For those, it's better to use empty parentheses:
|
||||
Sometimes you have a method that doesn't need any parameters, but modifies the
|
||||
object or has some other side effect. For those, it's better to use empty
|
||||
parentheses:
|
||||
|
||||
:::wren
|
||||
list.clear()
|
||||
@ -89,7 +90,7 @@ like a getter and a `()` method like a `()` method. These don't work:
|
||||
|
||||
:::wren
|
||||
"string".count()
|
||||
list.clear
|
||||
[1, 2, 3].clear
|
||||
|
||||
## Setters
|
||||
|
||||
@ -104,7 +105,7 @@ language's perspective, the above line is just a call to the `height=(_)`
|
||||
method, passing in `74`.
|
||||
|
||||
Since the `=(_)` is in the setter's signature, an object can have both a getter
|
||||
and setter with the same name without any collision. This way, you can have
|
||||
and setter with the same name without a collision. This way, you can have
|
||||
read/write properties.
|
||||
|
||||
## Operators
|
||||
|
||||
@ -21,7 +21,7 @@ Prints a single newline to the console.
|
||||
|
||||
### System.**print**(object)
|
||||
|
||||
Prints [object] to the console followed by a newline. If not already a string,
|
||||
Prints `object` to the console followed by a newline. If not already a string,
|
||||
the object is converted to a string by calling `toString` on it.
|
||||
|
||||
:::wren
|
||||
@ -29,7 +29,7 @@ the object is converted to a string by calling `toString` on it.
|
||||
|
||||
### System.**printAll**(sequence)
|
||||
|
||||
Iterates over [sequence] and prints each element, then prints a single newline
|
||||
Iterates over `sequence` and prints each element, then prints a single newline
|
||||
at the end. Each element is converted to a string by calling `toString` on it.
|
||||
|
||||
:::wren
|
||||
|
||||
10
doc/site/modules/io/directory.markdown
Normal file
10
doc/site/modules/io/directory.markdown
Normal 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.
|
||||
@ -1,7 +1,88 @@
|
||||
^title File Class
|
||||
|
||||
**TODO**
|
||||
Lets you work with files on the file system. An instance of this class
|
||||
represents an open file with a file descriptor.
|
||||
|
||||
When you are done with a file object, it's a good idea to explicitly close it.
|
||||
If you don't, the GC will close it when the file is no longer used and gets
|
||||
finalized, but that may take a while. In the meantime, leaving it open wastes
|
||||
a file descriptor.
|
||||
|
||||
## Static Methods
|
||||
|
||||
### File.**open**(path, fn)
|
||||
|
||||
Opens the file at `path` and passes it to `fn`. After the function returns, the
|
||||
file is automatically closed.
|
||||
|
||||
:::wren
|
||||
File.open("words.txt") {|file|
|
||||
file.readBytes(5)
|
||||
}
|
||||
|
||||
### File.**read**(path)
|
||||
|
||||
Reads the entire contents of the file at `path` and returns it as a string.
|
||||
|
||||
:::wren
|
||||
File.read("words.txt")
|
||||
|
||||
The encoding or decoding is done. If the file is UTF-8, then the resulting
|
||||
string will be a UTF-8 string. Otherwise, it will be a string of bytes in
|
||||
whatever encoding the file uses.
|
||||
|
||||
### File.**size**(path)
|
||||
|
||||
Returns the size in bytes of the contents of the file at `path`.
|
||||
|
||||
### File.**stat**(path)
|
||||
|
||||
"Stats" the file or directory at `path`. Returns a [Stat][] object describing
|
||||
the low-level details of the file system entry.
|
||||
|
||||
[stat]: stat.html
|
||||
|
||||
## Constructors
|
||||
|
||||
### File.**open**(path)
|
||||
|
||||
Opens the file at `path` for reading.
|
||||
|
||||
## Methods
|
||||
|
||||
**TODO**
|
||||
### **descriptor**
|
||||
|
||||
The numeric file descriptor used to access the file.
|
||||
|
||||
### **isOpen**
|
||||
|
||||
Whether the file is still open or has been closed.
|
||||
|
||||
### **size**
|
||||
|
||||
The size of the contents of the file in bytes.
|
||||
|
||||
### **close**()
|
||||
|
||||
Closes the file. After calling this, you can read or write from it.
|
||||
|
||||
### **readBytes**(count)
|
||||
|
||||
Reads up to `count` bytes starting from the beginning of the file.
|
||||
|
||||
:::wren
|
||||
// Assume this file contains "I am a file!".
|
||||
File.open("example.txt") {|file|
|
||||
System.print(file.readBytes(6)) //> I am a
|
||||
}
|
||||
|
||||
### **readBytes**(count, offset)
|
||||
|
||||
Reads up to `count` bytes starting at `offset` bytes from the beginning of
|
||||
the file.
|
||||
|
||||
:::wren
|
||||
// Assume this file contains "I am a file!".
|
||||
File.open("example.txt") {|file|
|
||||
System.print(file.readBytes(6, 2)) //> am a f
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
^title Module "io"
|
||||
|
||||
**TODO**
|
||||
Provides access to operating system streams and the file system.
|
||||
|
||||
* [Directory](directory.html)
|
||||
* [File](file.html)
|
||||
* [Stat](stat.html)
|
||||
* [Stdin](stdin.html)
|
||||
|
||||
50
doc/site/modules/io/stat.markdown
Normal file
50
doc/site/modules/io/stat.markdown
Normal 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.
|
||||
@ -1,7 +1,12 @@
|
||||
^title Stdin Class
|
||||
|
||||
**TODO**
|
||||
The standard input stream.
|
||||
|
||||
## Methods
|
||||
## Static Methods
|
||||
|
||||
**TODO**
|
||||
### **readLine**()
|
||||
|
||||
Reads one line of input from stdin. Blocks the current fiber until a full line
|
||||
of input has been received.
|
||||
|
||||
Returns the string of input or `null` if stdin is closed.
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
<section>
|
||||
<h2>io classes</h2>
|
||||
<ul>
|
||||
<li><a href="directory.html">Directory</a></li>
|
||||
<li><a href="file.html">File</a></li>
|
||||
<li><a href="stdin.html">Stdin</a></li>
|
||||
</ul>
|
||||
|
||||
@ -7,10 +7,12 @@
|
||||
#include "scheduler.wren.inc"
|
||||
#include "timer.wren.inc"
|
||||
|
||||
extern void directoryList(WrenVM* vm);
|
||||
extern void fileAllocate(WrenVM* vm);
|
||||
extern void fileFinalize(WrenVM* vm);
|
||||
extern void fileFinalize(void* data);
|
||||
extern void fileOpen(WrenVM* vm);
|
||||
extern void fileSizePath(WrenVM* vm);
|
||||
extern void fileStatPath(WrenVM* vm);
|
||||
extern void fileClose(WrenVM* vm);
|
||||
extern void fileDescriptor(WrenVM* vm);
|
||||
extern void fileReadBytes(WrenVM* vm);
|
||||
@ -28,14 +30,14 @@ extern void timerStartTimer(WrenVM* vm);
|
||||
// If you add a new method to the longest class below, make sure to bump this.
|
||||
// Note that it also includes an extra slot for the sentinel value indicating
|
||||
// the end of the list.
|
||||
#define MAX_METHODS_PER_CLASS 9
|
||||
#define MAX_METHODS_PER_CLASS 10
|
||||
|
||||
// The maximum number of foreign classes a single built-in module defines.
|
||||
//
|
||||
// If you add a new class to the largest module below, make sure to bump this.
|
||||
// Note that it also includes an extra slot for the sentinel value indicating
|
||||
// the end of the list.
|
||||
#define MAX_CLASSES_PER_MODULE 3
|
||||
#define MAX_CLASSES_PER_MODULE 4
|
||||
|
||||
// Describes one foreign method in a class.
|
||||
typedef struct
|
||||
@ -82,19 +84,24 @@ typedef struct
|
||||
|
||||
#define METHOD(signature, fn) { false, signature, fn },
|
||||
#define STATIC_METHOD(signature, fn) { true, signature, fn },
|
||||
#define FINALIZER(fn) { true, "<finalize>", (WrenForeignMethodFn)fn },
|
||||
|
||||
// The array of built-in modules.
|
||||
static ModuleRegistry modules[] =
|
||||
{
|
||||
MODULE(io)
|
||||
CLASS(Directory)
|
||||
STATIC_METHOD("list_(_,_)", directoryList)
|
||||
END_CLASS
|
||||
CLASS(File)
|
||||
STATIC_METHOD("<allocate>", fileAllocate)
|
||||
STATIC_METHOD("<finalize>", fileFinalize)
|
||||
FINALIZER(fileFinalize)
|
||||
STATIC_METHOD("open_(_,_)", fileOpen)
|
||||
STATIC_METHOD("sizePath_(_,_)", fileSizePath)
|
||||
STATIC_METHOD("statPath_(_,_)", fileStatPath)
|
||||
METHOD("close_(_)", fileClose)
|
||||
METHOD("descriptor", fileDescriptor)
|
||||
METHOD("readBytes_(_,_)", fileReadBytes)
|
||||
METHOD("readBytes_(_,_,_)", fileReadBytes)
|
||||
METHOD("size_(_)", fileSize)
|
||||
END_CLASS
|
||||
CLASS(Stdin)
|
||||
@ -204,7 +211,7 @@ WrenForeignClassMethods bindBuiltInForeignClass(
|
||||
if (clas == NULL) return methods;
|
||||
|
||||
methods.allocate = findMethod(clas, true, "<allocate>");
|
||||
methods.finalize = findMethod(clas, true, "<finalize>");
|
||||
methods.finalize = (WrenFinalizerFn)findMethod(clas, true, "<finalize>");
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
@ -38,6 +38,12 @@ typedef void* (*WrenReallocateFn)(void* memory, size_t newSize);
|
||||
// A function callable from Wren code, but implemented in C.
|
||||
typedef void (*WrenForeignMethodFn)(WrenVM* vm);
|
||||
|
||||
// A finalizer function for freeing resources owned by an instance of a foreign
|
||||
// class. Unlike most foreign methods, finalizers do not have access to the VM
|
||||
// and should not interact with it since it's in the middle of a garbage
|
||||
// collection.
|
||||
typedef void (*WrenFinalizerFn)(void* data);
|
||||
|
||||
// Loads and returns the source code for the module [name].
|
||||
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
|
||||
|
||||
@ -64,7 +70,7 @@ typedef struct
|
||||
// foreign object's memory.
|
||||
//
|
||||
// This may be `NULL` if the foreign class does not need to finalize.
|
||||
WrenForeignMethodFn finalize;
|
||||
WrenFinalizerFn finalize;
|
||||
} WrenForeignClassMethods;
|
||||
|
||||
// Returns a pair of pointers to the foreign methods used to allocate and
|
||||
@ -120,7 +126,7 @@ typedef struct
|
||||
//
|
||||
// If this is `NULL`, Wren discards any printed text.
|
||||
WrenWriteFn writeFn;
|
||||
|
||||
|
||||
// The number of bytes Wren will allocate before triggering the first garbage
|
||||
// collection.
|
||||
//
|
||||
@ -183,53 +189,71 @@ void wrenCollectGarbage(WrenVM* vm);
|
||||
// Runs [source], a string of Wren source code in a new fiber in [vm].
|
||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source);
|
||||
|
||||
// Creates a handle that can be used to invoke a method with [signature] on the
|
||||
// object in [module] currently stored in top-level [variable].
|
||||
// Creates a handle that can be used to invoke a method with [signature] on
|
||||
// using a receiver and arguments that are set up on the stack.
|
||||
//
|
||||
// This handle can be used repeatedly to directly invoke that method from C
|
||||
// code using [wrenCall].
|
||||
//
|
||||
// When done with this handle, it must be released using [wrenReleaseValue].
|
||||
WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
|
||||
const char* signature);
|
||||
// When you are done with this handle, it must be released using
|
||||
// [wrenReleaseValue].
|
||||
WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature);
|
||||
|
||||
// Calls [method], passing in a series of arguments whose types must match the
|
||||
// specifed [argTypes]. This is a string where each character identifies the
|
||||
// type of a single argument, in order. The allowed types are:
|
||||
// Calls [method], using the receiver and arguments previously set up on the
|
||||
// stack.
|
||||
//
|
||||
// - "b" - A C `int` converted to a Wren Bool.
|
||||
// - "d" - A C `double` converted to a Wren Num.
|
||||
// - "i" - A C `int` converted to a Wren Num.
|
||||
// - "s" - A C null-terminated `const char*` converted to a Wren String. Wren
|
||||
// will allocate its own string and copy the characters from this, so
|
||||
// you don't have to worry about the lifetime of the string you pass to
|
||||
// Wren.
|
||||
// - "a" - An array of bytes converted to a Wren String. This requires two
|
||||
// consecutive arguments in the argument list: `const char*` pointing
|
||||
// to the array of bytes, followed by an `int` defining the length of
|
||||
// the array. This is used when the passed string may contain null
|
||||
// bytes, or just to avoid the implicit `strlen()` call of "s" if you
|
||||
// happen to already know the length.
|
||||
// - "v" - A previously acquired WrenValue*. Passing this in does not implicitly
|
||||
// release the value. If the passed argument is NULL, this becomes a
|
||||
// Wren NULL.
|
||||
// [method] must have been created by a call to [wrenMakeCallHandle]. The
|
||||
// arguments to the method must be already on the stack. The receiver should be
|
||||
// in slot 0 with the remaining arguments following it, in order. It is an
|
||||
// error if the number of arguments provided does not match the method's
|
||||
// signature.
|
||||
//
|
||||
// [method] must have been created by a call to [wrenGetMethod]. If
|
||||
// [returnValue] is not `NULL`, the return value of the method will be stored
|
||||
// in a new [WrenValue] that [returnValue] will point to. Don't forget to
|
||||
// release it, when done with it.
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, ...);
|
||||
|
||||
WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, va_list args);
|
||||
// After this returns, you can access the return value from slot 0 on the stack.
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method);
|
||||
|
||||
// Releases the reference stored in [value]. After calling this, [value] can no
|
||||
// longer be used.
|
||||
void wrenReleaseValue(WrenVM* vm, WrenValue* value);
|
||||
|
||||
// The following functions are intended to be called from foreign methods or
|
||||
// finalizers. The interface Wren provides to a foreign method is like a
|
||||
// register machine: you are given a numbered array of slots that values can be
|
||||
// read from and written to. Values always live in a slot (unless explicitly
|
||||
// captured using wrenGetSlotValue(), which ensures the garbage collector can
|
||||
// find them.
|
||||
//
|
||||
// When your foreign function is called, you are given one slot for the receiver
|
||||
// and each argument to the method. The receiver is in slot 0 and the arguments
|
||||
// are in increasingly numbered slots after that. You are free to read and
|
||||
// write to those slots as you want. If you want more slots to use as scratch
|
||||
// space, you can call wrenEnsureSlots() to add more.
|
||||
//
|
||||
// When your function returns, every slot except slot zero is discarded and the
|
||||
// value in slot zero is used as the return value of the method. If you don't
|
||||
// store a return value in that slot yourself, it will retain its previous
|
||||
// value, the receiver.
|
||||
//
|
||||
// While Wren is dynamically typed, C is not. This means the C interface has to
|
||||
// support the various types of primitive values a Wren variable can hold: bool,
|
||||
// double, string, etc. If we supported this for every operation in the C API,
|
||||
// there would be a combinatorial explosion of functions, like "get a
|
||||
// double-valued element from a list", "insert a string key and double value
|
||||
// into a map", etc.
|
||||
//
|
||||
// To avoid that, the only way to convert to and from a raw C value is by going
|
||||
// into and out of a slot. All other functions work with values already in a
|
||||
// slot. So, to add an element to a list, you put the list in one slot, and the
|
||||
// element in another. Then there is a single API function wrenInsertInList()
|
||||
// that takes the element out of that slot and puts it into the list.
|
||||
//
|
||||
// The goal of this API is to be easy to use while not compromising performance.
|
||||
// The latter means it does not do type or bounds checking at runtime except
|
||||
// using assertions which are generally removed from release builds. C is an
|
||||
// unsafe language, so it's up to you to be careful to use it correctly. In
|
||||
// return, you get a very fast FFI.
|
||||
|
||||
// TODO: Generalize this to look up a foreign class in any slot and place the
|
||||
// object in a desired slot.
|
||||
// This must be called once inside a foreign class's allocator function.
|
||||
//
|
||||
// It tells Wren how many bytes of raw data need to be stored in the foreign
|
||||
@ -237,55 +261,60 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value);
|
||||
// the foreign object's data.
|
||||
void* wrenAllocateForeign(WrenVM* vm, size_t size);
|
||||
|
||||
// Returns the number of arguments available to the current foreign method.
|
||||
int wrenGetArgumentCount(WrenVM* vm);
|
||||
// Returns the number of slots available to the current foreign method.
|
||||
int wrenGetSlotCount(WrenVM* vm);
|
||||
|
||||
// The following functions read one of the arguments passed to a foreign call.
|
||||
// They may only be called while within a function provided to
|
||||
// [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked.
|
||||
// Ensures that the foreign method stack has at least [numSlots] available for
|
||||
// use, growing the stack if needed.
|
||||
//
|
||||
// They retreive the argument at a given index which ranges from 0 to the number
|
||||
// of parameters the method expects. The zeroth parameter is used for the
|
||||
// receiver of the method. For example, given a foreign method "foo" on String
|
||||
// invoked like:
|
||||
// Does not shrink the stack if it has more than enough slots.
|
||||
//
|
||||
// "receiver".foo("one", "two", "three")
|
||||
//
|
||||
// The foreign function will be able to access the arguments like so:
|
||||
//
|
||||
// 0: "receiver"
|
||||
// 1: "one"
|
||||
// 2: "two"
|
||||
// 3: "three"
|
||||
//
|
||||
// It is an error to pass an invalid argument index.
|
||||
// It is an error to call this from a finalizer.
|
||||
void wrenEnsureSlots(WrenVM* vm, int numSlots);
|
||||
|
||||
// Reads a boolean argument for a foreign call. Returns false if the argument
|
||||
// is not a boolean.
|
||||
bool wrenGetArgumentBool(WrenVM* vm, int index);
|
||||
// Reads a boolean value from [slot].
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a boolean value.
|
||||
bool wrenGetSlotBool(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a numeric argument for a foreign call. Returns 0 if the argument is not
|
||||
// a number.
|
||||
double wrenGetArgumentDouble(WrenVM* vm, int index);
|
||||
|
||||
// Reads a foreign object argument for a foreign call and returns a pointer to
|
||||
// the foreign data stored with it. Returns NULL if the argument is not a
|
||||
// foreign object.
|
||||
void* wrenGetArgumentForeign(WrenVM* vm, int index);
|
||||
|
||||
// Reads an string argument for a foreign call. Returns NULL if the argument is
|
||||
// not a string.
|
||||
// Reads a byte array from [slot].
|
||||
//
|
||||
// The memory for the returned string is owned by Wren. You can inspect it
|
||||
// while in your foreign function, but cannot keep a pointer to it after the
|
||||
// while in your foreign method, but cannot keep a pointer to it after the
|
||||
// function returns, since the garbage collector may reclaim it.
|
||||
const char* wrenGetArgumentString(WrenVM* vm, int index);
|
||||
//
|
||||
// Returns a pointer to the first byte of the array and fill [length] with the
|
||||
// number of bytes in the array.
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a string.
|
||||
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
|
||||
|
||||
// Creates a handle for the value passed as an argument to a foreign call.
|
||||
// Reads a number from [slot].
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a number.
|
||||
double wrenGetSlotDouble(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a foreign object from [slot] and returns a pointer to the foreign data
|
||||
// stored with it.
|
||||
//
|
||||
// It is an error to call this if the slot does not contain an instance of a
|
||||
// foreign class.
|
||||
void* wrenGetSlotForeign(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a string from [slot].
|
||||
//
|
||||
// The memory for the returned string is owned by Wren. You can inspect it
|
||||
// while in your foreign method, but cannot keep a pointer to it after the
|
||||
// function returns, since the garbage collector may reclaim it.
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a string.
|
||||
const char* wrenGetSlotString(WrenVM* vm, int slot);
|
||||
|
||||
// Creates a handle for the value stored in [slot].
|
||||
//
|
||||
// This will prevent the object that is referred to from being garbage collected
|
||||
// until the handle is released by calling [wrenReleaseValue()].
|
||||
WrenValue* wrenGetArgumentValue(WrenVM* vm, int index);
|
||||
WrenValue* wrenGetSlotValue(WrenVM* vm, int slot);
|
||||
|
||||
// The following functions provide the return value for a foreign method back
|
||||
// to Wren. Like above, they may only be called during a foreign call invoked
|
||||
@ -296,27 +325,47 @@ WrenValue* wrenGetArgumentValue(WrenVM* vm, int index);
|
||||
// call one of these once. It is an error to access any of the foreign calls
|
||||
// arguments after one of these has been called.
|
||||
|
||||
// Provides a boolean return value for a foreign call.
|
||||
void wrenReturnBool(WrenVM* vm, bool value);
|
||||
// Stores the boolean [value] in [slot].
|
||||
void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
|
||||
|
||||
// Provides a numeric return value for a foreign call.
|
||||
void wrenReturnDouble(WrenVM* vm, double value);
|
||||
// Stores the array [length] of [bytes] in [slot].
|
||||
//
|
||||
// The bytes are copied to a new string within Wren's heap, so you can free
|
||||
// memory used by them after this is called.
|
||||
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
|
||||
|
||||
// Provides a string return value for a foreign call.
|
||||
//
|
||||
// The [text] will be copied to a new string within Wren's heap, so you can
|
||||
// free memory used by it after this is called.
|
||||
//
|
||||
// If [length] is non-zero, Wren copies that many bytes from [text], including
|
||||
// any null bytes. If it is -1, then the length of [text] is calculated using
|
||||
// `strlen()`. If the string may contain any null bytes in the middle, then you
|
||||
// must pass an explicit length.
|
||||
void wrenReturnString(WrenVM* vm, const char* text, int length);
|
||||
// Stores the numeric [value] in [slot].
|
||||
void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
|
||||
|
||||
// Provides the return value for a foreign call.
|
||||
// Stores a new empty list in [slot].
|
||||
void wrenSetSlotNewList(WrenVM* vm, int slot);
|
||||
|
||||
// Stores null in [slot].
|
||||
void wrenSetSlotNull(WrenVM* vm, int slot);
|
||||
|
||||
// Stores the string [text] in [slot].
|
||||
//
|
||||
// This uses the value referred to by the handle as the return value, but it
|
||||
// does not release the handle.
|
||||
void wrenReturnValue(WrenVM* vm, WrenValue* value);
|
||||
// The [text] is copied to a new string within Wren's heap, so you can free
|
||||
// memory used by it after this is called. The length is calculated using
|
||||
// [strlen()]. If the string may contain any null bytes in the middle, then you
|
||||
// should use [wrenSetSlotBytes()] instead.
|
||||
void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
|
||||
|
||||
// Stores the value captured in [value] in [slot].
|
||||
//
|
||||
// This does not release the handle for the value.
|
||||
void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value);
|
||||
|
||||
// Takes the value stored at [elementSlot] and inserts it into the list stored
|
||||
// at [listSlot] at [index].
|
||||
//
|
||||
// As in Wren, negative indexes can be used to insert from the end. To append
|
||||
// an element, use `-1` for the index.
|
||||
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
|
||||
|
||||
// Looks up the top level variable with [name] in [module] and stores it in
|
||||
// [slot].
|
||||
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
|
||||
int slot);
|
||||
|
||||
#endif
|
||||
|
||||
280
src/module/io.c
280
src/module/io.c
@ -18,7 +18,10 @@ typedef struct sFileRequestData
|
||||
|
||||
static const int stdinDescriptor = 0;
|
||||
|
||||
// Handle to Stdin.onData_(). Called when libuv provides data on stdin.
|
||||
// Handle to the Stdin class object.
|
||||
static WrenValue* stdinClass = NULL;
|
||||
|
||||
// Handle to an onData_() method call. Called when libuv provides data on stdin.
|
||||
static WrenValue* stdinOnData = NULL;
|
||||
|
||||
// The stream used to read from stdin. Initialized on the first read.
|
||||
@ -34,6 +37,12 @@ static void shutdownStdin()
|
||||
stdinStream = NULL;
|
||||
}
|
||||
|
||||
if (stdinClass != NULL)
|
||||
{
|
||||
wrenReleaseValue(getVM(), stdinClass);
|
||||
stdinClass = NULL;
|
||||
}
|
||||
|
||||
if (stdinOnData != NULL)
|
||||
{
|
||||
wrenReleaseValue(getVM(), stdinOnData);
|
||||
@ -46,26 +55,6 @@ void ioShutdown()
|
||||
shutdownStdin();
|
||||
}
|
||||
|
||||
void fileAllocate(WrenVM* vm)
|
||||
{
|
||||
// Store the file descriptor in the foreign data, so that we can get to it
|
||||
// in the finalizer.
|
||||
int* fd = (int*)wrenAllocateForeign(vm, sizeof(int));
|
||||
*fd = (int)wrenGetArgumentDouble(vm, 1);
|
||||
}
|
||||
|
||||
void fileFinalize(WrenVM* vm)
|
||||
{
|
||||
int fd = *(int*)wrenGetArgumentForeign(vm, 0);
|
||||
|
||||
// Already closed.
|
||||
if (fd == -1) return;
|
||||
|
||||
uv_fs_t request;
|
||||
uv_fs_close(getLoop(), &request, fd, NULL);
|
||||
uv_fs_req_cleanup(&request);
|
||||
}
|
||||
|
||||
// If [request] failed with an error, sends the runtime error to the VM and
|
||||
// frees the request.
|
||||
//
|
||||
@ -73,12 +62,12 @@ void fileFinalize(WrenVM* vm)
|
||||
static bool handleRequestError(uv_fs_t* request)
|
||||
{
|
||||
if (request->result >= 0) return false;
|
||||
|
||||
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
WrenValue* fiber = (WrenValue*)data->fiber;
|
||||
|
||||
|
||||
schedulerResumeError(fiber, uv_strerror((int)request->result));
|
||||
|
||||
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
@ -89,10 +78,10 @@ static bool handleRequestError(uv_fs_t* request)
|
||||
uv_fs_t* createRequest(WrenValue* fiber)
|
||||
{
|
||||
uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t));
|
||||
|
||||
|
||||
FileRequestData* data = (FileRequestData*)malloc(sizeof(FileRequestData));
|
||||
data->fiber = fiber;
|
||||
|
||||
|
||||
request->data = data;
|
||||
return request;
|
||||
}
|
||||
@ -104,84 +93,189 @@ WrenValue* freeRequest(uv_fs_t* request)
|
||||
{
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
WrenValue* fiber = data->fiber;
|
||||
|
||||
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
static void openCallback(uv_fs_t* request)
|
||||
static void directoryListCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
uv_dirent_t entry;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewList(vm, 2);
|
||||
|
||||
while (uv_fs_scandir_next(request, &entry) != UV_EOF)
|
||||
{
|
||||
wrenSetSlotString(vm, 1, entry.name);
|
||||
wrenInsertInList(vm, 2, -1, 1);
|
||||
}
|
||||
|
||||
schedulerResume(freeRequest(request), true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void directoryList(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
|
||||
// TODO: Check return.
|
||||
uv_fs_scandir(getLoop(), request, path, 0, directoryListCallback);
|
||||
}
|
||||
|
||||
void fileAllocate(WrenVM* vm)
|
||||
{
|
||||
// Store the file descriptor in the foreign data, so that we can get to it
|
||||
// in the finalizer.
|
||||
int* fd = (int*)wrenAllocateForeign(vm, sizeof(int));
|
||||
*fd = (int)wrenGetSlotDouble(vm, 1);
|
||||
}
|
||||
|
||||
void fileFinalize(void* data)
|
||||
{
|
||||
int fd = *(int*)data;
|
||||
|
||||
// Already closed.
|
||||
if (fd == -1) return;
|
||||
|
||||
uv_fs_t request;
|
||||
uv_fs_close(getLoop(), &request, fd, NULL);
|
||||
uv_fs_req_cleanup(&request);
|
||||
}
|
||||
|
||||
static void fileOpenCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
double fd = (double)request->result;
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
|
||||
schedulerResumeDouble(fiber, fd);
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotDouble(getVM(), 2, fd);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void fileOpen(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetArgumentString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2));
|
||||
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
|
||||
// TODO: Allow controlling flags and modes.
|
||||
uv_fs_open(getLoop(), request, path, O_RDONLY, 0, openCallback);
|
||||
uv_fs_open(getLoop(), request, path, O_RDONLY, 0, fileOpenCallback);
|
||||
}
|
||||
|
||||
// Called by libuv when the stat call for size completes.
|
||||
static void sizeCallback(uv_fs_t* request)
|
||||
static void fileSizeCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
|
||||
double size = (double)request->statbuf.st_size;
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
|
||||
schedulerResumeDouble(fiber, size);
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotDouble(getVM(), 2, size);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void fileSizePath(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetArgumentString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2));
|
||||
uv_fs_stat(getLoop(), request, path, sizeCallback);
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
uv_fs_stat(getLoop(), request, path, fileSizeCallback);
|
||||
}
|
||||
|
||||
static void closeCallback(uv_fs_t* request)
|
||||
// Called by libuv when the stat call completes.
|
||||
static void fileStatPathCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
schedulerResume(fiber);
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 4);
|
||||
wrenSetSlotNewList(vm, 2);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_dev);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_ino);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_mode);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_nlink);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_uid);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_gid);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_rdev);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_size);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blksize);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blocks);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
// TODO: Include access, modification, and change times once we figure out
|
||||
// how we want to represent it.
|
||||
// time_t st_atime; /* time of last access */
|
||||
// time_t st_mtime; /* time of last modification */
|
||||
// time_t st_ctime; /* time of last status change */
|
||||
|
||||
schedulerResume(freeRequest(request), true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void fileStatPath(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
uv_fs_stat(getLoop(), request, path, fileStatPathCallback);
|
||||
}
|
||||
|
||||
static void fileCloseCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
schedulerResume(freeRequest(request), false);
|
||||
}
|
||||
|
||||
void fileClose(WrenVM* vm)
|
||||
{
|
||||
int* foreign = (int*)wrenGetArgumentForeign(vm, 0);
|
||||
int* foreign = (int*)wrenGetSlotForeign(vm, 0);
|
||||
int fd = *foreign;
|
||||
|
||||
|
||||
// If it's already closed, we're done.
|
||||
if (fd == -1)
|
||||
{
|
||||
wrenReturnBool(vm, true);
|
||||
wrenSetSlotBool(vm, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Mark it closed immediately.
|
||||
*foreign = -1;
|
||||
|
||||
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1));
|
||||
uv_fs_close(getLoop(), request, fd, closeCallback);
|
||||
wrenReturnBool(vm, false);
|
||||
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1));
|
||||
uv_fs_close(getLoop(), request, fd, fileCloseCallback);
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
}
|
||||
|
||||
void fileDescriptor(WrenVM* vm)
|
||||
{
|
||||
int* foreign = (int*)wrenGetArgumentForeign(vm, 0);
|
||||
int* foreign = (int*)wrenGetSlotForeign(vm, 0);
|
||||
int fd = *foreign;
|
||||
wrenReturnDouble(vm, fd);
|
||||
wrenSetSlotDouble(vm, 0, fd);
|
||||
}
|
||||
|
||||
static void fileReadBytesCallback(uv_fs_t* request)
|
||||
@ -190,43 +284,45 @@ static void fileReadBytesCallback(uv_fs_t* request)
|
||||
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
uv_buf_t buffer = data->buffer;
|
||||
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
size_t count = request->result;
|
||||
|
||||
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
|
||||
// embedding API supported a way to *give* it bytes that were previously
|
||||
// allocated using Wren's own allocator.
|
||||
schedulerResumeBytes(fiber, buffer.base, buffer.len);
|
||||
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotBytes(getVM(), 2, buffer.base, count);
|
||||
schedulerFinishResume();
|
||||
|
||||
// TODO: Likewise, freeing this after we resume is lame.
|
||||
free(buffer.base);
|
||||
}
|
||||
|
||||
void fileReadBytes(WrenVM* vm)
|
||||
{
|
||||
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2));
|
||||
|
||||
int fd = *(int*)wrenGetArgumentForeign(vm, 0);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 3));
|
||||
|
||||
int fd = *(int*)wrenGetSlotForeign(vm, 0);
|
||||
// TODO: Assert fd != -1.
|
||||
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
size_t length = (size_t)wrenGetArgumentDouble(vm, 1);
|
||||
size_t length = (size_t)wrenGetSlotDouble(vm, 1);
|
||||
size_t offset = (size_t)wrenGetSlotDouble(vm, 2);
|
||||
|
||||
data->buffer.len = length;
|
||||
data->buffer.base = (char*)malloc(length);
|
||||
|
||||
// TODO: Allow passing in offset.
|
||||
uv_fs_read(getLoop(), request, fd, &data->buffer, 1, 0, fileReadBytesCallback);
|
||||
|
||||
uv_fs_read(getLoop(), request, fd, &data->buffer, 1, offset,
|
||||
fileReadBytesCallback);
|
||||
}
|
||||
|
||||
void fileSize(WrenVM* vm)
|
||||
{
|
||||
uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1));
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1));
|
||||
|
||||
int fd = *(int*)wrenGetArgumentForeign(vm, 0);
|
||||
int fd = *(int*)wrenGetSlotForeign(vm, 0);
|
||||
// TODO: Assert fd != -1.
|
||||
|
||||
uv_fs_fstat(getLoop(), request, fd, sizeCallback);
|
||||
|
||||
uv_fs_fstat(getLoop(), request, fd, fileSizeCallback);
|
||||
}
|
||||
|
||||
static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
|
||||
@ -240,26 +336,42 @@ static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
|
||||
static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead,
|
||||
const uv_buf_t* buffer)
|
||||
{
|
||||
// If stdin was closed, send null to let io.wren know.
|
||||
if (numRead == UV_EOF)
|
||||
{
|
||||
wrenCall(getVM(), stdinOnData, NULL, "v", NULL);
|
||||
shutdownStdin();
|
||||
return;
|
||||
}
|
||||
WrenVM* vm = getVM();
|
||||
|
||||
// TODO: Handle other errors.
|
||||
if (stdinClass == NULL)
|
||||
{
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "io", "Stdin", 0);
|
||||
stdinClass = wrenGetSlotValue(vm, 0);
|
||||
}
|
||||
|
||||
if (stdinOnData == NULL)
|
||||
{
|
||||
stdinOnData = wrenGetMethod(getVM(), "io", "Stdin", "onData_(_)");
|
||||
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
|
||||
}
|
||||
|
||||
// If stdin was closed, send null to let io.wren know.
|
||||
if (numRead == UV_EOF)
|
||||
{
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotValue(vm, 0, stdinClass);
|
||||
wrenSetSlotNull(vm, 1);
|
||||
wrenCall(vm, stdinOnData);
|
||||
|
||||
shutdownStdin();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Handle other errors.
|
||||
|
||||
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
|
||||
// embedding API supported a way to *give* it bytes that were previously
|
||||
// allocated using Wren's own allocator.
|
||||
wrenCall(getVM(), stdinOnData, NULL, "a", buffer->base, numRead);
|
||||
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotValue(vm, 0, stdinClass);
|
||||
wrenSetSlotBytes(vm, 1, buffer->base, numRead);
|
||||
wrenCall(vm, stdinOnData);
|
||||
|
||||
// TODO: Likewise, freeing this after we resume is lame.
|
||||
free(buffer->base);
|
||||
}
|
||||
@ -284,7 +396,7 @@ void stdinReadStart(WrenVM* vm)
|
||||
stdinStream = (uv_stream_t*)handle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uv_read_start(stdinStream, allocCallback, stdinReadCallback);
|
||||
// TODO: Check return.
|
||||
}
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class Directory {
|
||||
static def list(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
|
||||
list_(path, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
foreign static def list_(path, fiber)
|
||||
}
|
||||
|
||||
foreign class File {
|
||||
static def open(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
@ -33,6 +44,13 @@ foreign class File {
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
static def stat(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
|
||||
statPath_(path, Fiber.current)
|
||||
return Stat.new_(Scheduler.runNextScheduled_())
|
||||
}
|
||||
|
||||
construct new_(fd) {}
|
||||
|
||||
def close() {
|
||||
@ -40,6 +58,8 @@ foreign class File {
|
||||
Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
foreign def descriptor
|
||||
|
||||
def isOpen { descriptor != -1 }
|
||||
|
||||
def size {
|
||||
@ -49,25 +69,48 @@ foreign class File {
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
def readBytes(count) {
|
||||
def readBytes(count) { readBytes(count, 0) }
|
||||
|
||||
def readBytes(count, offset) {
|
||||
if (!isOpen) Fiber.abort("File is not open.")
|
||||
if (!(count is Num)) Fiber.abort("Count must be an integer.")
|
||||
if (!count.isInteger) Fiber.abort("Count must be an integer.")
|
||||
if (count < 0) Fiber.abort("Count cannot be negative.")
|
||||
|
||||
readBytes_(count, Fiber.current)
|
||||
if (!(offset is Num)) Fiber.abort("Offset must be an integer.")
|
||||
if (!offset.isInteger) Fiber.abort("Offset must be an integer.")
|
||||
if (offset < 0) Fiber.abort("Offset cannot be negative.")
|
||||
|
||||
readBytes_(count, offset, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
foreign static def open_(path, fiber)
|
||||
foreign static def sizePath_(path, fiber)
|
||||
foreign static def statPath_(path, fiber)
|
||||
|
||||
foreign def close_(fiber)
|
||||
foreign def descriptor
|
||||
foreign def readBytes_(count, fiber)
|
||||
foreign def readBytes_(count, start, fiber)
|
||||
foreign def size_(fiber)
|
||||
}
|
||||
|
||||
class Stat {
|
||||
construct new_(fields) {
|
||||
_fields = fields
|
||||
}
|
||||
|
||||
def device { _fields[0] }
|
||||
def inode { _fields[1] }
|
||||
def mode { _fields[2] }
|
||||
def linkCount { _fields[3] }
|
||||
def user { _fields[4] }
|
||||
def group { _fields[5] }
|
||||
def specialDevice { _fields[6] }
|
||||
def size { _fields[7] }
|
||||
def blockSize { _fields[8] }
|
||||
def blockCount { _fields[9] }
|
||||
}
|
||||
|
||||
class Stdin {
|
||||
static def readLine() {
|
||||
if (__isClosed == true) {
|
||||
@ -90,6 +133,7 @@ class Stdin {
|
||||
readStop_()
|
||||
|
||||
if (__line != null) {
|
||||
// Emit the last line.
|
||||
var line = __line
|
||||
__line = null
|
||||
if (__waitingFiber != null) __waitingFiber.transfer(line)
|
||||
|
||||
@ -2,6 +2,17 @@
|
||||
static const char* ioModuleSource =
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"class Directory {\n"
|
||||
" static def list(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
"\n"
|
||||
" list_(path, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static def list_(path, fiber)\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"foreign class File {\n"
|
||||
" static def open(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
@ -35,6 +46,13 @@ static const char* ioModuleSource =
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static def stat(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
"\n"
|
||||
" statPath_(path, Fiber.current)\n"
|
||||
" return Stat.new_(Scheduler.runNextScheduled_())\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct new_(fd) {}\n"
|
||||
"\n"
|
||||
" def close() {\n"
|
||||
@ -42,6 +60,8 @@ static const char* ioModuleSource =
|
||||
" Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign def descriptor\n"
|
||||
"\n"
|
||||
" def isOpen { descriptor != -1 }\n"
|
||||
"\n"
|
||||
" def size {\n"
|
||||
@ -51,25 +71,48 @@ static const char* ioModuleSource =
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" def readBytes(count) {\n"
|
||||
" def readBytes(count) { readBytes(count, 0) }\n"
|
||||
"\n"
|
||||
" def readBytes(count, offset) {\n"
|
||||
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
|
||||
" if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\n"
|
||||
" if (!count.isInteger) Fiber.abort(\"Count must be an integer.\")\n"
|
||||
" if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n"
|
||||
"\n"
|
||||
" readBytes_(count, Fiber.current)\n"
|
||||
" if (!(offset is Num)) Fiber.abort(\"Offset must be an integer.\")\n"
|
||||
" if (!offset.isInteger) Fiber.abort(\"Offset must be an integer.\")\n"
|
||||
" if (offset < 0) Fiber.abort(\"Offset cannot be negative.\")\n"
|
||||
"\n"
|
||||
" readBytes_(count, offset, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static def open_(path, fiber)\n"
|
||||
" foreign static def sizePath_(path, fiber)\n"
|
||||
" foreign static def statPath_(path, fiber)\n"
|
||||
"\n"
|
||||
" foreign def close_(fiber)\n"
|
||||
" foreign def descriptor\n"
|
||||
" foreign def readBytes_(count, fiber)\n"
|
||||
" foreign def readBytes_(count, start, fiber)\n"
|
||||
" foreign def size_(fiber)\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Stat {\n"
|
||||
" construct new_(fields) {\n"
|
||||
" _fields = fields\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" def device { _fields[0] }\n"
|
||||
" def inode { _fields[1] }\n"
|
||||
" def mode { _fields[2] }\n"
|
||||
" def linkCount { _fields[3] }\n"
|
||||
" def user { _fields[4] }\n"
|
||||
" def group { _fields[5] }\n"
|
||||
" def specialDevice { _fields[6] }\n"
|
||||
" def size { _fields[7] }\n"
|
||||
" def blockSize { _fields[8] }\n"
|
||||
" def blockCount { _fields[9] }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Stdin {\n"
|
||||
" static def readLine() {\n"
|
||||
" if (__isClosed == true) {\n"
|
||||
@ -92,6 +135,7 @@ static const char* ioModuleSource =
|
||||
" readStop_()\n"
|
||||
"\n"
|
||||
" if (__line != null) {\n"
|
||||
" // Emit the last line.\n"
|
||||
" var line = __line\n"
|
||||
" __line = null\n"
|
||||
" if (__waitingFiber != null) __waitingFiber.transfer(line)\n"
|
||||
|
||||
@ -7,30 +7,19 @@
|
||||
#include "wren.h"
|
||||
#include "vm.h"
|
||||
|
||||
// A handle to the "Scheduler" class object. Used to call static methods on it.
|
||||
static WrenValue* schedulerClass;
|
||||
|
||||
// This method resumes a fiber that is suspended waiting on an asynchronous
|
||||
// operation. The first resumes it with zero arguments, and the second passes
|
||||
// one.
|
||||
static WrenValue* resume;
|
||||
static WrenValue* resumeWithArg;
|
||||
static WrenValue* resume1;
|
||||
static WrenValue* resume2;
|
||||
static WrenValue* resumeError;
|
||||
|
||||
void schedulerCaptureMethods(WrenVM* vm)
|
||||
static void resume(WrenValue* method)
|
||||
{
|
||||
resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)");
|
||||
resumeWithArg = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_,_)");
|
||||
resumeError = wrenGetMethod(vm, "scheduler", "Scheduler", "resumeError_(_,_)");
|
||||
}
|
||||
|
||||
static void callResume(WrenValue* resumeMethod, WrenValue* fiber,
|
||||
const char* argTypes, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, argTypes);
|
||||
WrenInterpretResult result = wrenCallVarArgs(getVM(), resumeMethod, NULL,
|
||||
argTypes, args);
|
||||
va_end(args);
|
||||
|
||||
wrenReleaseValue(getVM(), fiber);
|
||||
WrenInterpretResult result = wrenCall(getVM(), method);
|
||||
|
||||
// If a runtime error occurs in response to an async operation and nothing
|
||||
// catches the error in the fiber, then exit the CLI.
|
||||
@ -41,34 +30,50 @@ static void callResume(WrenValue* resumeMethod, WrenValue* fiber,
|
||||
}
|
||||
}
|
||||
|
||||
void schedulerResume(WrenValue* fiber)
|
||||
void schedulerCaptureMethods(WrenVM* vm)
|
||||
{
|
||||
callResume(resume, fiber, "v", fiber);
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "scheduler", "Scheduler", 0);
|
||||
schedulerClass = wrenGetSlotValue(vm, 0);
|
||||
|
||||
resume1 = wrenMakeCallHandle(vm, "resume_(_)");
|
||||
resume2 = wrenMakeCallHandle(vm, "resume_(_,_)");
|
||||
resumeError = wrenMakeCallHandle(vm, "resumeError_(_,_)");
|
||||
}
|
||||
|
||||
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length)
|
||||
void schedulerResume(WrenValue* fiber, bool hasArgument)
|
||||
{
|
||||
callResume(resumeWithArg, fiber, "va", fiber, bytes, length);
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 2 + (hasArgument ? 1 : 0));
|
||||
wrenSetSlotValue(vm, 0, schedulerClass);
|
||||
wrenSetSlotValue(vm, 1, fiber);
|
||||
wrenReleaseValue(vm, fiber);
|
||||
|
||||
// If we don't need to wait for an argument to be stored on the stack, resume
|
||||
// it now.
|
||||
if (!hasArgument) resume(resume1);
|
||||
}
|
||||
|
||||
void schedulerResumeDouble(WrenValue* fiber, double value)
|
||||
void schedulerFinishResume()
|
||||
{
|
||||
callResume(resumeWithArg, fiber, "vd", fiber, value);
|
||||
}
|
||||
|
||||
void schedulerResumeString(WrenValue* fiber, const char* text)
|
||||
{
|
||||
callResume(resumeWithArg, fiber, "vs", fiber, text);
|
||||
resume(resume2);
|
||||
}
|
||||
|
||||
void schedulerResumeError(WrenValue* fiber, const char* error)
|
||||
{
|
||||
callResume(resumeError, fiber, "vs", fiber, error);
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotString(getVM(), 2, error);
|
||||
resume(resumeError);
|
||||
}
|
||||
|
||||
void schedulerShutdown()
|
||||
{
|
||||
if (resume != NULL) wrenReleaseValue(getVM(), resume);
|
||||
if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg);
|
||||
if (resumeError != NULL) wrenReleaseValue(getVM(), resumeError);
|
||||
// If the module was never loaded, we don't have anything to release.
|
||||
if (schedulerClass == NULL) return;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenReleaseValue(vm, schedulerClass);
|
||||
wrenReleaseValue(vm, resume1);
|
||||
wrenReleaseValue(vm, resume2);
|
||||
wrenReleaseValue(vm, resumeError);
|
||||
}
|
||||
|
||||
@ -3,10 +3,16 @@
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void schedulerResume(WrenValue* fiber);
|
||||
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length);
|
||||
void schedulerResumeDouble(WrenValue* fiber, double value);
|
||||
void schedulerResumeString(WrenValue* fiber, const char* text);
|
||||
// Sets up the API stack to call one of the resume methods on Scheduler.
|
||||
//
|
||||
// If [hasArgument] is false, this just sets up the stack to have another
|
||||
// argument stored in slot 2 and returns. The module must store the argument
|
||||
// on the stack and then call [schedulerFinishResume] to complete the call.
|
||||
//
|
||||
// Otherwise, the call resumes immediately. Releases [fiber] when called.
|
||||
void schedulerResume(WrenValue* fiber, bool hasArgument);
|
||||
|
||||
void schedulerFinishResume();
|
||||
void schedulerResumeError(WrenValue* fiber, const char* error);
|
||||
|
||||
void schedulerShutdown();
|
||||
|
||||
@ -22,13 +22,13 @@ static void timerCallback(uv_timer_t* handle)
|
||||
uv_close((uv_handle_t*)handle, timerCloseCallback);
|
||||
|
||||
// Run the fiber that was sleeping.
|
||||
schedulerResume(fiber);
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
|
||||
void timerStartTimer(WrenVM* vm)
|
||||
{
|
||||
int milliseconds = (int)wrenGetArgumentDouble(vm, 1);
|
||||
WrenValue* fiber = wrenGetArgumentValue(vm, 2);
|
||||
int milliseconds = (int)wrenGetSlotDouble(vm, 1);
|
||||
WrenValue* fiber = wrenGetSlotValue(vm, 2);
|
||||
|
||||
// Store the fiber to resume when the timer completes.
|
||||
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));
|
||||
|
||||
@ -18,13 +18,11 @@ void metaCompile(WrenVM* vm)
|
||||
: AS_CLOSURE(callingFn)->fn->module;
|
||||
|
||||
// Compile it.
|
||||
ObjFn* fn = wrenCompile(vm, module, wrenGetArgumentString(vm, 1), false);
|
||||
if (fn == NULL) return;
|
||||
ObjFn* fn = wrenCompile(vm, module, wrenGetSlotString(vm, 1), false);
|
||||
|
||||
// Return the result. We can't use the public API for this since we have a
|
||||
// bare ObjFn.
|
||||
*vm->foreignStackStart = OBJ_VAL(fn);
|
||||
vm->foreignStackStart = NULL;
|
||||
vm->apiStack[0] = fn != NULL ? OBJ_VAL(fn) : NULL_VAL;
|
||||
}
|
||||
|
||||
static WrenForeignMethodFn bindMetaForeignMethods(WrenVM* vm,
|
||||
|
||||
@ -45,7 +45,7 @@ static void randomAllocate(WrenVM* vm)
|
||||
|
||||
static void randomSeed0(WrenVM* vm)
|
||||
{
|
||||
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
|
||||
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
srand((uint32_t)time(NULL));
|
||||
for (int i = 0; i < 16; i++)
|
||||
@ -56,9 +56,9 @@ static void randomSeed0(WrenVM* vm)
|
||||
|
||||
static void randomSeed1(WrenVM* vm)
|
||||
{
|
||||
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
|
||||
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
srand((uint32_t)wrenGetArgumentDouble(vm, 1));
|
||||
srand((uint32_t)wrenGetSlotDouble(vm, 1));
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
well->state[i] = rand();
|
||||
@ -67,17 +67,17 @@ static void randomSeed1(WrenVM* vm)
|
||||
|
||||
static void randomSeed16(WrenVM* vm)
|
||||
{
|
||||
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
|
||||
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
well->state[i] = (uint32_t)wrenGetArgumentDouble(vm, i + 1);
|
||||
well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void randomFloat(WrenVM* vm)
|
||||
{
|
||||
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
|
||||
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
// A double has 53 bits of precision in its mantissa, and we'd like to take
|
||||
// full advantage of that, so we need 53 bits of random source data.
|
||||
@ -92,14 +92,14 @@ static void randomFloat(WrenVM* vm)
|
||||
// from 0 to 1.0 (half-inclusive).
|
||||
result /= 9007199254740992.0;
|
||||
|
||||
wrenReturnDouble(vm, result);
|
||||
wrenSetSlotDouble(vm, 0, result);
|
||||
}
|
||||
|
||||
static void randomInt0(WrenVM* vm)
|
||||
{
|
||||
Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0);
|
||||
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
wrenReturnDouble(vm, (double)advanceState(well));
|
||||
wrenSetSlotDouble(vm, 0, (double)advanceState(well));
|
||||
}
|
||||
|
||||
// TODO: The way these are wired up is pretty verbose and tedious. Also, the
|
||||
|
||||
@ -1331,7 +1331,7 @@ static int resolveLocal(Compiler* compiler, const char* name, int length)
|
||||
|
||||
// Adds an upvalue to [compiler]'s function with the given properties. Does not
|
||||
// add one if an upvalue for that variable is already in the list. Returns the
|
||||
// index of the uvpalue.
|
||||
// index of the upvalue.
|
||||
static int addUpvalue(Compiler* compiler, bool isLocal, int index)
|
||||
{
|
||||
// Look for an existing one.
|
||||
|
||||
@ -150,6 +150,8 @@ DEF_PRIMITIVE(fiber_suspend)
|
||||
{
|
||||
// Switching to a null fiber tells the interpreter to stop and exit.
|
||||
vm->fiber = NULL;
|
||||
vm->apiStack = NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,9 @@ void wrenDebugPrintStackTrace(ObjFiber* fiber)
|
||||
CallFrame* frame = &fiber->frames[i];
|
||||
ObjFn* fn = wrenUpwrapClosure(frame->fn);
|
||||
|
||||
// Skip over stub functions for calling methods from the C API.
|
||||
if (fn->module == NULL) continue;
|
||||
|
||||
// The built-in core module has no name. We explicitly omit it from stack
|
||||
// traces since we don't want to highlight to a user the implementation
|
||||
// detail of what part of the core module is written in C and what is Wren.
|
||||
@ -50,7 +53,7 @@ static void dumpObject(Obj* obj)
|
||||
case OBJ_RANGE: printf("[range %p]", obj); break;
|
||||
case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break;
|
||||
case OBJ_UPVALUE: printf("[upvalue %p]", obj); break;
|
||||
default: printf("[unknown object]"); break;
|
||||
default: printf("[unknown object %d]", obj->type); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -157,7 +157,11 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
||||
// Allocate the arrays before the fiber in case it triggers a GC.
|
||||
CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES);
|
||||
|
||||
int stackCapacity = wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots);
|
||||
// Add one slot for the unused implicit receiver slot that the compiler
|
||||
// assumes all functions have.
|
||||
int stackCapacity = fn == NULL
|
||||
? 1
|
||||
: wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots + 1);
|
||||
Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity);
|
||||
|
||||
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
|
||||
@ -179,10 +183,52 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn)
|
||||
fiber->caller = NULL;
|
||||
fiber->error = NULL_VAL;
|
||||
fiber->callerIsTrying = false;
|
||||
fiber->numFrames = 0;
|
||||
|
||||
// Initialize the first call frame.
|
||||
fiber->numFrames = 0;
|
||||
wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
|
||||
if (fn != NULL) wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
|
||||
}
|
||||
|
||||
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed)
|
||||
{
|
||||
if (fiber->stackCapacity >= needed) return;
|
||||
|
||||
int capacity = wrenPowerOf2Ceil(needed);
|
||||
|
||||
Value* oldStack = fiber->stack;
|
||||
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
|
||||
sizeof(Value) * fiber->stackCapacity,
|
||||
sizeof(Value) * capacity);
|
||||
fiber->stackCapacity = capacity;
|
||||
|
||||
// If the reallocation moves the stack, then we need to shift every pointer
|
||||
// into the stack to point to its new location.
|
||||
if (fiber->stack != oldStack)
|
||||
{
|
||||
// Top of the stack.
|
||||
long offset = fiber->stack - oldStack;
|
||||
|
||||
if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop)
|
||||
{
|
||||
vm->apiStack += offset;
|
||||
}
|
||||
|
||||
// Stack pointer for each call frame.
|
||||
for (int i = 0; i < fiber->numFrames; i++)
|
||||
{
|
||||
fiber->frames[i].stackStart += offset;
|
||||
}
|
||||
|
||||
// Open upvalues.
|
||||
for (ObjUpvalue* upvalue = fiber->openUpvalues;
|
||||
upvalue != NULL;
|
||||
upvalue = upvalue->next)
|
||||
{
|
||||
upvalue->value += offset;
|
||||
}
|
||||
|
||||
fiber->stackTop += offset;
|
||||
}
|
||||
}
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
|
||||
@ -389,6 +435,8 @@ static uint32_t hashValue(Value value)
|
||||
case VAL_OBJ: return hashObject(AS_OBJ(value));
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1163,10 +1211,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
||||
wrenValueBufferClear(vm, &((ObjModule*)obj)->variables);
|
||||
break;
|
||||
|
||||
case OBJ_STRING:
|
||||
case OBJ_CLOSURE:
|
||||
case OBJ_INSTANCE:
|
||||
case OBJ_RANGE:
|
||||
case OBJ_STRING:
|
||||
case OBJ_UPVALUE:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -74,6 +74,7 @@
|
||||
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
|
||||
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
|
||||
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
|
||||
#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList
|
||||
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
|
||||
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
|
||||
|
||||
@ -662,6 +663,9 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
||||
frame->ip = wrenUpwrapClosure(frame->fn)->bytecode;
|
||||
}
|
||||
|
||||
// Ensures [fiber]'s stack has at least [needed] slots.
|
||||
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
||||
|
||||
// TODO: The argument list here is getting a bit gratuitous.
|
||||
|
||||
451
src/vm/wren_vm.c
451
src/vm/wren_vm.c
@ -65,7 +65,7 @@ WrenVM* wrenNewVM(WrenConfiguration* config)
|
||||
vm->modules = wrenNewMap(vm);
|
||||
|
||||
wrenInitializeCore(vm);
|
||||
|
||||
|
||||
// TODO: Lazy load these.
|
||||
#if WREN_OPT_META
|
||||
wrenLoadMetaModule(vm);
|
||||
@ -137,7 +137,7 @@ void wrenCollectGarbage(WrenVM* vm)
|
||||
wrenGrayObj(vm, vm->tempRoots[i]);
|
||||
}
|
||||
|
||||
// The current fiber.
|
||||
// The rooted fiber.
|
||||
wrenGrayObj(vm, (Obj*)vm->fiber);
|
||||
|
||||
// The value handles.
|
||||
@ -348,19 +348,14 @@ static void bindMethod(WrenVM* vm, int methodType, int symbol,
|
||||
static void callForeign(WrenVM* vm, ObjFiber* fiber,
|
||||
WrenForeignMethodFn foreign, int numArgs)
|
||||
{
|
||||
vm->foreignStackStart = fiber->stackTop - numArgs;
|
||||
vm->apiStack = fiber->stackTop - numArgs;
|
||||
|
||||
foreign(vm);
|
||||
|
||||
// Discard the stack slots for the arguments (but leave one for
|
||||
// the result).
|
||||
fiber->stackTop -= numArgs - 1;
|
||||
|
||||
// If nothing was returned, implicitly return null.
|
||||
if (vm->foreignStackStart != NULL)
|
||||
{
|
||||
*vm->foreignStackStart = NULL_VAL;
|
||||
vm->foreignStackStart = NULL;
|
||||
}
|
||||
// Discard the stack slots for the arguments and temporaries but leave one
|
||||
// for the result.
|
||||
fiber->stackTop = vm->apiStack + 1;
|
||||
vm->apiStack = NULL;
|
||||
}
|
||||
|
||||
// Handles the current fiber having aborted because of an error. Switches to
|
||||
@ -469,41 +464,8 @@ static inline void callFunction(
|
||||
// Grow the stack if needed.
|
||||
int stackSize = (int)(fiber->stackTop - fiber->stack);
|
||||
int needed = stackSize + wrenUpwrapClosure(function)->maxSlots;
|
||||
wrenEnsureStack(vm, fiber, needed);
|
||||
|
||||
if (fiber->stackCapacity < needed)
|
||||
{
|
||||
int capacity = wrenPowerOf2Ceil(needed);
|
||||
|
||||
Value* oldStack = fiber->stack;
|
||||
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
|
||||
sizeof(Value) * fiber->stackCapacity,
|
||||
sizeof(Value) * capacity);
|
||||
fiber->stackCapacity = capacity;
|
||||
|
||||
// If the reallocation moves the stack, then we need to shift every pointer
|
||||
// into the stack to point to its new location.
|
||||
if (fiber->stack != oldStack)
|
||||
{
|
||||
// Top of the stack.
|
||||
long offset = fiber->stack - oldStack;
|
||||
fiber->stackTop += offset;
|
||||
|
||||
// Stack pointer for each call frame.
|
||||
for (int i = 0; i < fiber->numFrames; i++)
|
||||
{
|
||||
fiber->frames[i].stackStart += offset;
|
||||
}
|
||||
|
||||
// Open upvalues.
|
||||
for (ObjUpvalue* upvalue = fiber->openUpvalues;
|
||||
upvalue != NULL;
|
||||
upvalue = upvalue->next)
|
||||
{
|
||||
upvalue->value += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
|
||||
}
|
||||
|
||||
@ -689,7 +651,7 @@ static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
|
||||
|
||||
if (methods.finalize != NULL)
|
||||
{
|
||||
method.fn.foreign = methods.finalize;
|
||||
method.fn.foreign = (WrenForeignMethodFn)methods.finalize;
|
||||
wrenBindMethod(vm, classObj, symbol, method);
|
||||
}
|
||||
}
|
||||
@ -742,7 +704,7 @@ static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
|
||||
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
|
||||
|
||||
// Pass the constructor arguments to the allocator as well.
|
||||
vm->foreignStackStart = stack;
|
||||
vm->apiStack = stack;
|
||||
|
||||
method->fn.foreign(vm);
|
||||
|
||||
@ -767,11 +729,8 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
|
||||
|
||||
ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign.");
|
||||
|
||||
// Pass the constructor arguments to the allocator as well.
|
||||
Value slot = OBJ_VAL(foreign);
|
||||
vm->foreignStackStart = &slot;
|
||||
|
||||
method->fn.foreign(vm);
|
||||
WrenFinalizerFn finalizer = (WrenFinalizerFn)method->fn.foreign;
|
||||
finalizer(foreign->data);
|
||||
}
|
||||
|
||||
// The main bytecode interpreter loop. This is where the magic happens. It is
|
||||
@ -1179,18 +1138,23 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
// If the fiber is complete, end it.
|
||||
if (fiber->numFrames == 0)
|
||||
{
|
||||
// See if there's another fiber to return to.
|
||||
ObjFiber* callingFiber = fiber->caller;
|
||||
// See if there's another fiber to return to. If not, we're done.
|
||||
if (fiber->caller == NULL)
|
||||
{
|
||||
// Store the final result value at the beginning of the stack so the
|
||||
// C API can get it.
|
||||
fiber->stack[0] = result;
|
||||
fiber->stackTop = fiber->stack + 1;
|
||||
return WREN_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ObjFiber* resumingFiber = fiber->caller;
|
||||
fiber->caller = NULL;
|
||||
fiber = resumingFiber;
|
||||
vm->fiber = resumingFiber;
|
||||
|
||||
fiber = callingFiber;
|
||||
vm->fiber = fiber;
|
||||
|
||||
// If not, we're done.
|
||||
if (fiber == NULL) return WREN_RESULT_SUCCESS;
|
||||
|
||||
// Store the result in the resuming fiber.
|
||||
*(fiber->stackTop - 1) = result;
|
||||
fiber->stackTop[-1] = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1202,7 +1166,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
// result).
|
||||
fiber->stackTop = frame->stackStart + 1;
|
||||
}
|
||||
|
||||
|
||||
LOAD_FRAME();
|
||||
DISPATCH();
|
||||
}
|
||||
@ -1330,11 +1294,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
#undef READ_SHORT
|
||||
}
|
||||
|
||||
// Creates an [ObjFn] that invokes a method with [signature] when called.
|
||||
static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
|
||||
WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature)
|
||||
{
|
||||
int signatureLength = (int)strlen(signature);
|
||||
|
||||
|
||||
// Count the number parameters the method expects.
|
||||
int numParams = 0;
|
||||
if (signature[signatureLength - 1] == ')')
|
||||
@ -1345,167 +1308,73 @@ static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
|
||||
if (*s == '_') numParams++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add the signatue to the method table.
|
||||
int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
|
||||
signature, signatureLength);
|
||||
|
||||
|
||||
// Create a little stub function that assumes the arguments are on the stack
|
||||
// and calls the method.
|
||||
uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5);
|
||||
bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams);
|
||||
bytecode[1] = (method >> 8) & 0xff;
|
||||
bytecode[2] = method & 0xff;
|
||||
bytecode[3] = CODE_RETURN;
|
||||
bytecode[4] = CODE_END;
|
||||
|
||||
|
||||
int* debugLines = ALLOCATE_ARRAY(vm, int, 5);
|
||||
memset(debugLines, 1, 5);
|
||||
|
||||
ObjFn* fn = wrenNewFunction(vm, NULL, NULL, 0, 0, numParams + 1, 0, bytecode,
|
||||
5, signature, signatureLength, debugLines);
|
||||
|
||||
return wrenNewFunction(vm, module, NULL, 0, 0, numParams + 1, 0, bytecode, 5,
|
||||
signature, signatureLength, debugLines);
|
||||
// Wrap the function in a handle.
|
||||
return wrenCaptureValue(vm, OBJ_VAL(fn));
|
||||
}
|
||||
|
||||
WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
|
||||
const char* signature)
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method)
|
||||
{
|
||||
Value moduleName = wrenStringFormat(vm, "$", module);
|
||||
wrenPushRoot(vm, AS_OBJ(moduleName));
|
||||
|
||||
ObjModule* moduleObj = getModule(vm, moduleName);
|
||||
// TODO: Handle module not being found.
|
||||
|
||||
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
|
||||
variable, strlen(variable));
|
||||
// TODO: Handle the variable not being found.
|
||||
|
||||
ObjFn* fn = makeCallStub(vm, moduleObj, signature);
|
||||
wrenPushRoot(vm, (Obj*)fn);
|
||||
|
||||
// Create a single fiber that we can reuse each time the method is invoked.
|
||||
ObjFiber* fiber = wrenNewFiber(vm, (Obj*)fn);
|
||||
wrenPushRoot(vm, (Obj*)fiber);
|
||||
|
||||
// Create a handle that keeps track of the function that calls the method.
|
||||
WrenValue* method = wrenCaptureValue(vm, OBJ_VAL(fiber));
|
||||
|
||||
// Store the receiver in the fiber's stack so we can use it later in the call.
|
||||
*fiber->stackTop++ = moduleObj->variables.data[variableSlot];
|
||||
|
||||
wrenPopRoot(vm); // fiber.
|
||||
wrenPopRoot(vm); // fn.
|
||||
wrenPopRoot(vm); // moduleName.
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, argTypes);
|
||||
WrenInterpretResult result = wrenCallVarArgs(vm, method, returnValue,
|
||||
argTypes, args);
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, va_list args)
|
||||
{
|
||||
// TODO: Validate that the number of arguments matches what the method
|
||||
// expects.
|
||||
|
||||
ASSERT(IS_FIBER(method->value), "Value must come from wrenGetMethod().");
|
||||
ObjFiber* fiber = AS_FIBER(method->value);
|
||||
|
||||
// Push the arguments.
|
||||
for (const char* argType = argTypes; *argType != '\0'; argType++)
|
||||
{
|
||||
Value value = NULL_VAL;
|
||||
switch (*argType)
|
||||
{
|
||||
case 'a':
|
||||
{
|
||||
const char* bytes = va_arg(args, const char*);
|
||||
int length = va_arg(args, int);
|
||||
value = wrenNewString(vm, bytes, (size_t)length);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'b': value = BOOL_VAL(va_arg(args, int)); break;
|
||||
case 'd': value = NUM_VAL(va_arg(args, double)); break;
|
||||
case 'i': value = NUM_VAL((double)va_arg(args, int)); break;
|
||||
case 'n': value = NULL_VAL; va_arg(args, void*); break;
|
||||
case 's':
|
||||
value = wrenStringFormat(vm, "$", va_arg(args, const char*));
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
{
|
||||
// Allow a NULL value pointer for Wren null.
|
||||
WrenValue* wrenValue = va_arg(args, WrenValue*);
|
||||
if (wrenValue != NULL) value = wrenValue->value;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ASSERT(false, "Unknown argument type.");
|
||||
break;
|
||||
}
|
||||
|
||||
*fiber->stackTop++ = value;
|
||||
}
|
||||
|
||||
Value receiver = fiber->stack[0];
|
||||
Obj* fn = fiber->frames[0].fn;
|
||||
wrenPushRoot(vm, (Obj*)fn);
|
||||
|
||||
WrenInterpretResult result = runInterpreter(vm, fiber);
|
||||
|
||||
if (result == WREN_RESULT_SUCCESS)
|
||||
{
|
||||
if (returnValue != NULL)
|
||||
{
|
||||
// Make sure the return value doesn't get collected while capturing it.
|
||||
fiber->stackTop++;
|
||||
*returnValue = wrenCaptureValue(vm, fiber->stack[0]);
|
||||
}
|
||||
|
||||
// Reset the fiber to get ready for the next call.
|
||||
wrenResetFiber(vm, fiber, fn);
|
||||
|
||||
// Push the receiver back on the stack.
|
||||
*fiber->stackTop++ = receiver;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (returnValue != NULL) *returnValue = NULL;
|
||||
}
|
||||
|
||||
wrenPopRoot(vm);
|
||||
|
||||
return result;
|
||||
ASSERT(method != NULL, "Method cannot be NULL.");
|
||||
ASSERT(IS_FN(method->value), "Method must be a method handle.");
|
||||
ASSERT(vm->fiber != NULL, "Must set up arguments for call first.");
|
||||
ASSERT(vm->apiStack != NULL, "Must set up arguments for call first.");
|
||||
ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method.");
|
||||
|
||||
ObjFn* fn = AS_FN(method->value);
|
||||
|
||||
ASSERT(vm->fiber->stackTop - vm->fiber->stack >= fn->arity,
|
||||
"Stack must have enough arguments for method.");
|
||||
|
||||
// Discard any extra temporary slots. We take for granted that the stub
|
||||
// function has exactly one slot for each arguments.
|
||||
vm->fiber->stackTop = &vm->fiber->stack[fn->maxSlots];
|
||||
|
||||
callFunction(vm, vm->fiber, (Obj*)fn, 0);
|
||||
return runInterpreter(vm, vm->fiber);
|
||||
}
|
||||
|
||||
WrenValue* wrenCaptureValue(WrenVM* vm, Value value)
|
||||
{
|
||||
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
|
||||
|
||||
// Make a handle for it.
|
||||
WrenValue* wrappedValue = ALLOCATE(vm, WrenValue);
|
||||
wrappedValue->value = value;
|
||||
|
||||
if (IS_OBJ(value)) wrenPopRoot(vm);
|
||||
|
||||
// Add it to the front of the linked list of handles.
|
||||
if (vm->valueHandles != NULL) vm->valueHandles->prev = wrappedValue;
|
||||
wrappedValue->prev = NULL;
|
||||
wrappedValue->next = vm->valueHandles;
|
||||
vm->valueHandles = wrappedValue;
|
||||
|
||||
|
||||
return wrappedValue;
|
||||
}
|
||||
|
||||
void wrenReleaseValue(WrenVM* vm, WrenValue* value)
|
||||
{
|
||||
ASSERT(value != NULL, "NULL value.");
|
||||
ASSERT(value != NULL, "Value cannot be NULL.");
|
||||
|
||||
// Update the VM's head pointer if we're releasing the first handle.
|
||||
if (vm->valueHandles == value) vm->valueHandles = value->next;
|
||||
@ -1524,14 +1393,14 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value)
|
||||
|
||||
void* wrenAllocateForeign(WrenVM* vm, size_t size)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
ASSERT(vm->apiStack != NULL, "Must be in foreign call.");
|
||||
|
||||
// TODO: Validate this. It can fail if the user calls this inside another
|
||||
// foreign method, or calls one of the return functions.
|
||||
ObjClass* classObj = AS_CLASS(vm->foreignStackStart[0]);
|
||||
ObjClass* classObj = AS_CLASS(vm->apiStack[0]);
|
||||
|
||||
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
|
||||
vm->foreignStackStart[0] = OBJ_VAL(foreign);
|
||||
vm->apiStack[0] = OBJ_VAL(foreign);
|
||||
|
||||
return (void*)foreign->data;
|
||||
}
|
||||
@ -1658,100 +1527,170 @@ void wrenPopRoot(WrenVM* vm)
|
||||
vm->numTempRoots--;
|
||||
}
|
||||
|
||||
int wrenGetArgumentCount(WrenVM* vm)
|
||||
int wrenGetSlotCount(WrenVM* vm)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
if (vm->apiStack == NULL) return 0;
|
||||
|
||||
// If no fiber is executing, we must be in a finalizer, in which case the
|
||||
// "stack" just has one object, the object being finalized.
|
||||
if (vm->fiber == NULL) return 1;
|
||||
|
||||
return (int)(vm->fiber->stackTop - vm->foreignStackStart);
|
||||
return (int)(vm->fiber->stackTop - vm->apiStack);
|
||||
}
|
||||
|
||||
static void validateForeignArgument(WrenVM* vm, int index)
|
||||
void wrenEnsureSlots(WrenVM* vm, int numSlots)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
ASSERT(index >= 0, "index cannot be negative.");
|
||||
ASSERT(index < wrenGetArgumentCount(vm), "Not that many arguments.");
|
||||
// If we don't have a fiber accessible, create one for the API to use.
|
||||
if (vm->apiStack == NULL)
|
||||
{
|
||||
vm->fiber = wrenNewFiber(vm, NULL);
|
||||
vm->apiStack = vm->fiber->stack;
|
||||
}
|
||||
|
||||
int currentSize = (int)(vm->fiber->stackTop - vm->apiStack);
|
||||
if (currentSize >= numSlots) return;
|
||||
|
||||
// Grow the stack if needed.
|
||||
int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots;
|
||||
wrenEnsureStack(vm, vm->fiber, needed);
|
||||
|
||||
vm->fiber->stackTop = vm->apiStack + numSlots;
|
||||
}
|
||||
|
||||
bool wrenGetArgumentBool(WrenVM* vm, int index)
|
||||
// Ensures that [slot] is a valid index into the API's stack of slots.
|
||||
static void validateApiSlot(WrenVM* vm, int slot)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
|
||||
if (!IS_BOOL(vm->foreignStackStart[index])) return false;
|
||||
|
||||
return AS_BOOL(vm->foreignStackStart[index]);
|
||||
ASSERT(slot >= 0, "Slot cannot be negative.");
|
||||
ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots.");
|
||||
}
|
||||
|
||||
double wrenGetArgumentDouble(WrenVM* vm, int index)
|
||||
bool wrenGetSlotBool(WrenVM* vm, int slot)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
validateApiSlot(vm, slot);
|
||||
ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool.");
|
||||
|
||||
if (!IS_NUM(vm->foreignStackStart[index])) return 0.0;
|
||||
|
||||
return AS_NUM(vm->foreignStackStart[index]);
|
||||
return AS_BOOL(vm->apiStack[slot]);
|
||||
}
|
||||
|
||||
void* wrenGetArgumentForeign(WrenVM* vm, int index)
|
||||
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
|
||||
if (!IS_FOREIGN(vm->foreignStackStart[index])) return NULL;
|
||||
|
||||
return AS_FOREIGN(vm->foreignStackStart[index])->data;
|
||||
validateApiSlot(vm, slot);
|
||||
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
|
||||
|
||||
ObjString* string = AS_STRING(vm->apiStack[slot]);
|
||||
*length = string->length;
|
||||
return string->value;
|
||||
}
|
||||
|
||||
const char* wrenGetArgumentString(WrenVM* vm, int index)
|
||||
double wrenGetSlotDouble(WrenVM* vm, int slot)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
validateApiSlot(vm, slot);
|
||||
ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number.");
|
||||
|
||||
if (!IS_STRING(vm->foreignStackStart[index])) return NULL;
|
||||
|
||||
return AS_CSTRING(vm->foreignStackStart[index]);
|
||||
return AS_NUM(vm->apiStack[slot]);
|
||||
}
|
||||
|
||||
WrenValue* wrenGetArgumentValue(WrenVM* vm, int index)
|
||||
void* wrenGetSlotForeign(WrenVM* vm, int slot)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
validateApiSlot(vm, slot);
|
||||
ASSERT(IS_FOREIGN(vm->apiStack[slot]),
|
||||
"Slot must hold a foreign instance.");
|
||||
|
||||
return wrenCaptureValue(vm, vm->foreignStackStart[index]);
|
||||
return AS_FOREIGN(vm->apiStack[slot])->data;
|
||||
}
|
||||
|
||||
void wrenReturnBool(WrenVM* vm, bool value)
|
||||
const char* wrenGetSlotString(WrenVM* vm, int slot)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
validateApiSlot(vm, slot);
|
||||
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
|
||||
|
||||
*vm->foreignStackStart = BOOL_VAL(value);
|
||||
vm->foreignStackStart = NULL;
|
||||
return AS_CSTRING(vm->apiStack[slot]);
|
||||
}
|
||||
|
||||
void wrenReturnDouble(WrenVM* vm, double value)
|
||||
WrenValue* wrenGetSlotValue(WrenVM* vm, int slot)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
|
||||
*vm->foreignStackStart = NUM_VAL(value);
|
||||
vm->foreignStackStart = NULL;
|
||||
validateApiSlot(vm, slot);
|
||||
return wrenCaptureValue(vm, vm->apiStack[slot]);
|
||||
}
|
||||
|
||||
void wrenReturnString(WrenVM* vm, const char* text, int length)
|
||||
// Stores [value] in [slot] in the foreign call stack.
|
||||
static void setSlot(WrenVM* vm, int slot, Value value)
|
||||
{
|
||||
validateApiSlot(vm, slot);
|
||||
vm->apiStack[slot] = value;
|
||||
}
|
||||
|
||||
void wrenSetSlotBool(WrenVM* vm, int slot, bool value)
|
||||
{
|
||||
setSlot(vm, slot, BOOL_VAL(value));
|
||||
}
|
||||
|
||||
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length)
|
||||
{
|
||||
ASSERT(bytes != NULL, "Byte array cannot be NULL.");
|
||||
setSlot(vm, slot, wrenNewString(vm, bytes, length));
|
||||
}
|
||||
|
||||
void wrenSetSlotDouble(WrenVM* vm, int slot, double value)
|
||||
{
|
||||
setSlot(vm, slot, NUM_VAL(value));
|
||||
}
|
||||
|
||||
void wrenSetSlotNewList(WrenVM* vm, int slot)
|
||||
{
|
||||
setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
|
||||
}
|
||||
|
||||
void wrenSetSlotNull(WrenVM* vm, int slot)
|
||||
{
|
||||
setSlot(vm, slot, NULL_VAL);
|
||||
}
|
||||
|
||||
void wrenSetSlotString(WrenVM* vm, int slot, const char* text)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
ASSERT(text != NULL, "String cannot be NULL.");
|
||||
|
||||
size_t size = length;
|
||||
if (length == -1) size = strlen(text);
|
||||
|
||||
*vm->foreignStackStart = wrenNewString(vm, text, size);
|
||||
vm->foreignStackStart = NULL;
|
||||
setSlot(vm, slot, wrenNewString(vm, text, strlen(text)));
|
||||
}
|
||||
|
||||
void wrenReturnValue(WrenVM* vm, WrenValue* value)
|
||||
void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
ASSERT(value != NULL, "Value cannot be NULL.");
|
||||
|
||||
*vm->foreignStackStart = value->value;
|
||||
vm->foreignStackStart = NULL;
|
||||
setSlot(vm, slot, value->value);
|
||||
}
|
||||
|
||||
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
|
||||
{
|
||||
validateApiSlot(vm, listSlot);
|
||||
validateApiSlot(vm, elementSlot);
|
||||
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list.");
|
||||
|
||||
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
|
||||
|
||||
// Negative indices count from the end.
|
||||
if (index < 0) index = list->elements.count + 1 + index;
|
||||
|
||||
ASSERT(index <= list->elements.count, "Index out of bounds.");
|
||||
|
||||
wrenListInsert(vm, list, vm->apiStack[elementSlot], index);
|
||||
}
|
||||
|
||||
// TODO: Maybe just have this always return a WrenValue* instead of having to
|
||||
// deal with slots?
|
||||
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
|
||||
int slot)
|
||||
{
|
||||
ASSERT(module != NULL, "Module cannot be NULL.");
|
||||
ASSERT(module != NULL, "Variable name cannot be NULL.");
|
||||
validateApiSlot(vm, slot);
|
||||
|
||||
Value moduleName = wrenStringFormat(vm, "$", module);
|
||||
wrenPushRoot(vm, AS_OBJ(moduleName));
|
||||
|
||||
ObjModule* moduleObj = getModule(vm, moduleName);
|
||||
ASSERT(moduleObj != NULL, "Could not find module.");
|
||||
|
||||
wrenPopRoot(vm); // moduleName.
|
||||
|
||||
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
|
||||
name, strlen(name));
|
||||
ASSERT(variableSlot != -1, "Could not find variable.");
|
||||
|
||||
setSlot(vm, slot, moduleObj->variables.data[variableSlot]);
|
||||
}
|
||||
|
||||
@ -84,11 +84,14 @@ struct WrenVM
|
||||
// NULL if there are no handles.
|
||||
WrenValue* valueHandles;
|
||||
|
||||
// Foreign function data:
|
||||
|
||||
// During a foreign function call, this will point to the first argument (the
|
||||
// receiver) of the call on the fiber's stack.
|
||||
Value* foreignStackStart;
|
||||
// Pointer to the bottom of the range of stack slots available for use from
|
||||
// the C API. During a foreign method, this will be in the stack of the fiber
|
||||
// that is executing a method.
|
||||
//
|
||||
// If not in a foreign method, this is initially NULL. If the user requests
|
||||
// slots by calling wrenEnsureSlots(), a stack is created and this is
|
||||
// initialized.
|
||||
Value* apiStack;
|
||||
|
||||
WrenConfiguration config;
|
||||
|
||||
|
||||
@ -1,21 +1,81 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
static void arguments(WrenVM* vm)
|
||||
{
|
||||
double result = 0;
|
||||
result += wrenGetArgumentDouble(vm, 1);
|
||||
result += wrenGetArgumentDouble(vm, 2);
|
||||
result += wrenGetArgumentDouble(vm, 3);
|
||||
result += wrenGetArgumentDouble(vm, 4);
|
||||
|
||||
result += wrenGetSlotDouble(vm, 1);
|
||||
result += wrenGetSlotDouble(vm, 2);
|
||||
result += wrenGetSlotDouble(vm, 3);
|
||||
result += wrenGetSlotDouble(vm, 4);
|
||||
|
||||
wrenSetSlotDouble(vm, 0, result);
|
||||
}
|
||||
|
||||
const char* testScript =
|
||||
"class Test {\n"
|
||||
" static method(a, b, c, d) { a + b + c + d }\n"
|
||||
"}\n";
|
||||
|
||||
static void call(WrenVM* vm)
|
||||
{
|
||||
int iterations = (int)wrenGetSlotDouble(vm, 1);
|
||||
|
||||
wrenReturnDouble(vm, result);
|
||||
// Since the VM is not re-entrant, we can't call from within this foreign
|
||||
// method. Instead, make a new VM to run the call test in.
|
||||
WrenConfiguration config;
|
||||
wrenInitConfiguration(&config);
|
||||
WrenVM* otherVM = wrenNewVM(&config);
|
||||
|
||||
wrenInterpret(otherVM, testScript);
|
||||
|
||||
WrenValue* method = wrenMakeCallHandle(otherVM, "method(_,_,_,_)");
|
||||
|
||||
wrenEnsureSlots(otherVM, 1);
|
||||
wrenGetVariable(otherVM, "main", "Test", 0);
|
||||
WrenValue* testClass = wrenGetSlotValue(otherVM, 0);
|
||||
|
||||
double startTime = (double)clock() / CLOCKS_PER_SEC;
|
||||
|
||||
double result = 0;
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
wrenEnsureSlots(otherVM, 5);
|
||||
wrenSetSlotValue(otherVM, 0, testClass);
|
||||
wrenSetSlotDouble(otherVM, 1, 1.0);
|
||||
wrenSetSlotDouble(otherVM, 2, 2.0);
|
||||
wrenSetSlotDouble(otherVM, 3, 3.0);
|
||||
wrenSetSlotDouble(otherVM, 4, 4.0);
|
||||
|
||||
wrenCall(otherVM, method);
|
||||
|
||||
result += wrenGetSlotDouble(otherVM, 0);
|
||||
}
|
||||
|
||||
double elapsed = (double)clock() / CLOCKS_PER_SEC - startTime;
|
||||
|
||||
wrenReleaseValue(otherVM, testClass);
|
||||
wrenReleaseValue(otherVM, method);
|
||||
wrenFreeVM(otherVM);
|
||||
|
||||
if (result == (1.0 + 2.0 + 3.0 + 4.0) * iterations)
|
||||
{
|
||||
wrenSetSlotDouble(vm, 0, elapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Got the wrong result.
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
WrenForeignMethodFn benchmarkBindMethod(const char* signature)
|
||||
{
|
||||
if (strcmp(signature, "static Benchmark.arguments(_,_,_,_)") == 0) return arguments;
|
||||
|
||||
if (strcmp(signature, "static Benchmark.call(_)") == 0) return call;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -5,33 +5,84 @@
|
||||
|
||||
void callRunTests(WrenVM* vm)
|
||||
{
|
||||
WrenValue* noParams = wrenGetMethod(vm, "main", "Call", "noParams");
|
||||
WrenValue* zero = wrenGetMethod(vm, "main", "Call", "zero()");
|
||||
WrenValue* one = wrenGetMethod(vm, "main", "Call", "one(_)");
|
||||
WrenValue* two = wrenGetMethod(vm, "main", "Call", "two(_,_)");
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "main", "Call", 0);
|
||||
WrenValue* callClass = wrenGetSlotValue(vm, 0);
|
||||
|
||||
WrenValue* noParams = wrenMakeCallHandle(vm, "noParams");
|
||||
WrenValue* zero = wrenMakeCallHandle(vm, "zero()");
|
||||
WrenValue* one = wrenMakeCallHandle(vm, "one(_)");
|
||||
WrenValue* two = wrenMakeCallHandle(vm, "two(_,_)");
|
||||
|
||||
// Different arity.
|
||||
wrenCall(vm, noParams, NULL, "");
|
||||
wrenCall(vm, zero, NULL, "");
|
||||
wrenCall(vm, one, NULL, "i", 1);
|
||||
wrenCall(vm, two, NULL, "ii", 1, 2);
|
||||
|
||||
WrenValue* getValue = wrenGetMethod(vm, "main", "Call", "getValue(_)");
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenCall(vm, noParams);
|
||||
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenCall(vm, zero);
|
||||
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotDouble(vm, 1, 1.0);
|
||||
wrenCall(vm, one);
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotDouble(vm, 1, 1.0);
|
||||
wrenSetSlotDouble(vm, 2, 2.0);
|
||||
wrenCall(vm, two);
|
||||
|
||||
// Returning a value.
|
||||
WrenValue* value = NULL;
|
||||
wrenCall(vm, getValue, &value, "v", NULL);
|
||||
WrenValue* getValue = wrenMakeCallHandle(vm, "getValue()");
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenCall(vm, getValue);
|
||||
WrenValue* value = wrenGetSlotValue(vm, 0);
|
||||
|
||||
// Different argument types.
|
||||
wrenCall(vm, two, NULL, "bb", true, false);
|
||||
wrenCall(vm, two, NULL, "dd", 1.2, 3.4);
|
||||
wrenCall(vm, two, NULL, "ii", 3, 4);
|
||||
wrenCall(vm, two, NULL, "ss", "string", "another");
|
||||
wrenCall(vm, two, NULL, "vv", NULL, value);
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotBool(vm, 1, true);
|
||||
wrenSetSlotBool(vm, 2, false);
|
||||
wrenCall(vm, two);
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotDouble(vm, 1, 1.2);
|
||||
wrenSetSlotDouble(vm, 2, 3.4);
|
||||
wrenCall(vm, two);
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotString(vm, 1, "string");
|
||||
wrenSetSlotString(vm, 2, "another");
|
||||
wrenCall(vm, two);
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotNull(vm, 1);
|
||||
wrenSetSlotValue(vm, 2, value);
|
||||
wrenCall(vm, two);
|
||||
|
||||
// Truncate a string, or allow null bytes.
|
||||
wrenCall(vm, two, NULL, "aa", "string", 3, "b\0y\0t\0e", 7);
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
wrenSetSlotBytes(vm, 1, "string", 3);
|
||||
wrenSetSlotBytes(vm, 2, "b\0y\0t\0e", 7);
|
||||
wrenCall(vm, two);
|
||||
|
||||
// Call ignores with extra temporary slots on stack.
|
||||
wrenEnsureSlots(vm, 10);
|
||||
wrenSetSlotValue(vm, 0, callClass);
|
||||
for (int i = 1; i < 10; i++)
|
||||
{
|
||||
wrenSetSlotDouble(vm, i, i * 0.1);
|
||||
}
|
||||
wrenCall(vm, one);
|
||||
|
||||
wrenReleaseValue(vm, callClass);
|
||||
wrenReleaseValue(vm, noParams);
|
||||
wrenReleaseValue(vm, zero);
|
||||
wrenReleaseValue(vm, one);
|
||||
|
||||
@ -20,13 +20,7 @@ class Call {
|
||||
System.print("two %(one) %(two)")
|
||||
}
|
||||
|
||||
static def getValue(value) {
|
||||
// Return a new value if we aren't given one.
|
||||
if (value == null) return ["a", "b"]
|
||||
|
||||
// Otherwise print it.
|
||||
System.print(value)
|
||||
}
|
||||
static def getValue() { ["a", "b"] }
|
||||
}
|
||||
|
||||
// expect: noParams
|
||||
@ -36,7 +30,7 @@ class Call {
|
||||
|
||||
// expect: two true false
|
||||
// expect: two 1.2 3.4
|
||||
// expect: two 3 4
|
||||
// expect: two string another
|
||||
// expect: two null [a, b]
|
||||
// expect: two str [98, 0, 121, 0, 116, 0, 101]
|
||||
// expect: one 0.1
|
||||
|
||||
@ -7,7 +7,7 @@ static int finalized = 0;
|
||||
|
||||
static void apiFinalized(WrenVM* vm)
|
||||
{
|
||||
wrenReturnDouble(vm, finalized);
|
||||
wrenSetSlotDouble(vm, 0, finalized);
|
||||
}
|
||||
|
||||
static void counterAllocate(WrenVM* vm)
|
||||
@ -18,25 +18,25 @@ static void counterAllocate(WrenVM* vm)
|
||||
|
||||
static void counterIncrement(WrenVM* vm)
|
||||
{
|
||||
double* value = (double*)wrenGetArgumentForeign(vm, 0);
|
||||
double increment = wrenGetArgumentDouble(vm, 1);
|
||||
double* value = (double*)wrenGetSlotForeign(vm, 0);
|
||||
double increment = wrenGetSlotDouble(vm, 1);
|
||||
|
||||
*value += increment;
|
||||
}
|
||||
|
||||
static void counterValue(WrenVM* vm)
|
||||
{
|
||||
double value = *(double*)wrenGetArgumentForeign(vm, 0);
|
||||
wrenReturnDouble(vm, value);
|
||||
double value = *(double*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotDouble(vm, 0, value);
|
||||
}
|
||||
|
||||
static void pointAllocate(WrenVM* vm)
|
||||
{
|
||||
double* coordinates = (double*)wrenAllocateForeign(vm, sizeof(double[3]));
|
||||
|
||||
// This gets called by both constructors, so sniff the argument count to see
|
||||
// This gets called by both constructors, so sniff the slot count to see
|
||||
// which one was invoked.
|
||||
if (wrenGetArgumentCount(vm) == 1)
|
||||
if (wrenGetSlotCount(vm) == 1)
|
||||
{
|
||||
coordinates[0] = 0.0;
|
||||
coordinates[1] = 0.0;
|
||||
@ -44,36 +44,41 @@ static void pointAllocate(WrenVM* vm)
|
||||
}
|
||||
else
|
||||
{
|
||||
coordinates[0] = wrenGetArgumentDouble(vm, 1);
|
||||
coordinates[1] = wrenGetArgumentDouble(vm, 2);
|
||||
coordinates[2] = wrenGetArgumentDouble(vm, 3);
|
||||
coordinates[0] = wrenGetSlotDouble(vm, 1);
|
||||
coordinates[1] = wrenGetSlotDouble(vm, 2);
|
||||
coordinates[2] = wrenGetSlotDouble(vm, 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void pointTranslate(WrenVM* vm)
|
||||
{
|
||||
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0);
|
||||
coordinates[0] += wrenGetArgumentDouble(vm, 1);
|
||||
coordinates[1] += wrenGetArgumentDouble(vm, 2);
|
||||
coordinates[2] += wrenGetArgumentDouble(vm, 3);
|
||||
double* coordinates = (double*)wrenGetSlotForeign(vm, 0);
|
||||
coordinates[0] += wrenGetSlotDouble(vm, 1);
|
||||
coordinates[1] += wrenGetSlotDouble(vm, 2);
|
||||
coordinates[2] += wrenGetSlotDouble(vm, 3);
|
||||
}
|
||||
|
||||
static void pointToString(WrenVM* vm)
|
||||
{
|
||||
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0);
|
||||
double* coordinates = (double*)wrenGetSlotForeign(vm, 0);
|
||||
char result[100];
|
||||
sprintf(result, "(%g, %g, %g)",
|
||||
coordinates[0], coordinates[1], coordinates[2]);
|
||||
wrenReturnString(vm, result, (int)strlen(result));
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
}
|
||||
|
||||
static void resourceAllocate(WrenVM* vm)
|
||||
{
|
||||
wrenAllocateForeign(vm, 0);
|
||||
int* value = (int*)wrenAllocateForeign(vm, sizeof(int));
|
||||
*value = 123;
|
||||
}
|
||||
|
||||
static void resourceFinalize(WrenVM* vm)
|
||||
static void resourceFinalize(void* data)
|
||||
{
|
||||
// Make sure we get the right data back.
|
||||
int* value = (int*)data;
|
||||
if (*value != 123) exit(1);
|
||||
|
||||
finalized++;
|
||||
}
|
||||
|
||||
|
||||
44
test/api/get_variable.c
Normal file
44
test/api/get_variable.c
Normal 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
3
test/api/get_variable.h
Normal file
@ -0,0 +1,3 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn getVariableBindMethod(const char* signature);
|
||||
24
test/api/get_variable.wren
Normal file
24
test/api/get_variable.wren
Normal 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
|
||||
3
test/api/get_variable_module.wren
Normal file
3
test/api/get_variable_module.wren
Normal file
@ -0,0 +1,3 @@
|
||||
// nontest
|
||||
|
||||
var Variable = "value"
|
||||
46
test/api/lists.c
Normal file
46
test/api/lists.c
Normal 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
3
test/api/lists.h
Normal file
@ -0,0 +1,3 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn listsBindMethod(const char* signature);
|
||||
10
test/api/lists.wren
Normal file
10
test/api/lists.wren
Normal 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]
|
||||
@ -6,8 +6,10 @@
|
||||
|
||||
#include "benchmark.h"
|
||||
#include "call.h"
|
||||
#include "get_variable.h"
|
||||
#include "foreign_class.h"
|
||||
#include "returns.h"
|
||||
#include "lists.h"
|
||||
#include "slots.h"
|
||||
#include "value.h"
|
||||
|
||||
// The name of the currently executing API test.
|
||||
@ -32,11 +34,17 @@ static WrenForeignMethodFn bindForeignMethod(
|
||||
|
||||
method = benchmarkBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
|
||||
method = getVariableBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = foreignClassBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = returnsBindMethod(fullName);
|
||||
method = listsBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = slotsBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = valueBindMethod(fullName);
|
||||
@ -60,7 +68,7 @@ static WrenForeignClassMethods bindForeignClass(
|
||||
}
|
||||
|
||||
static void afterLoad(WrenVM* vm) {
|
||||
if (strstr(testName, "call.wren") != NULL) callRunTests(vm);
|
||||
if (strstr(testName, "/call.wren") != NULL) callRunTests(vm);
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn returnsBindMethod(const char* signature);
|
||||
@ -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
146
test/api/slots.c
Normal 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
3
test/api/slots.h
Normal file
@ -0,0 +1,3 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn slotsBindMethod(const char* signature);
|
||||
24
test/api/slots.wren
Normal file
24
test/api/slots.wren
Normal 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)
|
||||
@ -6,12 +6,12 @@ static WrenValue* value;
|
||||
|
||||
static void setValue(WrenVM* vm)
|
||||
{
|
||||
value = wrenGetArgumentValue(vm, 1);
|
||||
value = wrenGetSlotValue(vm, 1);
|
||||
}
|
||||
|
||||
static void getValue(WrenVM* vm)
|
||||
{
|
||||
wrenReturnValue(vm, value);
|
||||
wrenSetSlotValue(vm, 0, value);
|
||||
wrenReleaseValue(vm, value);
|
||||
}
|
||||
|
||||
|
||||
9
test/benchmark/api_call.wren
Normal file
9
test/benchmark/api_call.wren
Normal 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)")
|
||||
1
test/io/directory/dir/a.txt
Normal file
1
test/io/directory/dir/a.txt
Normal file
@ -0,0 +1 @@
|
||||
this is a text file
|
||||
1
test/io/directory/dir/b.txt
Normal file
1
test/io/directory/dir/b.txt
Normal file
@ -0,0 +1 @@
|
||||
this is a text file
|
||||
1
test/io/directory/dir/c.txt
Normal file
1
test/io/directory/dir/c.txt
Normal file
@ -0,0 +1 @@
|
||||
this is a text file
|
||||
8
test/io/directory/list.wren
Normal file
8
test/io/directory/list.wren
Normal 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]
|
||||
3
test/io/directory/list_file.wren
Normal file
3
test/io/directory/list_file.wren
Normal 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
|
||||
3
test/io/directory/list_nonexistent.wren
Normal file
3
test/io/directory/list_nonexistent.wren
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for Directory
|
||||
|
||||
Directory.list("nonexistent") // expect runtime error: no such file or directory
|
||||
3
test/io/directory/list_wrong_arg_type.wren
Normal file
3
test/io/directory/list_wrong_arg_type.wren
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for Directory
|
||||
|
||||
Directory.list(123) // expect runtime error: Path must be a string.
|
||||
10
test/io/file/finalize.wren
Normal file
10
test/io/file/finalize.wren
Normal 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
|
||||
@ -10,4 +10,10 @@ System.print(file.readBytes(7)) // expect: this is
|
||||
// Allows zero.
|
||||
System.print(file.readBytes(0).bytes.count) // expect: 0
|
||||
|
||||
// A longer number reads the whole file.
|
||||
System.print(file.readBytes(100)) // expect: this is a text file
|
||||
|
||||
// Reading past the end truncates the buffer.
|
||||
System.print(file.readBytes(100).bytes.count) // expect: 19
|
||||
|
||||
file.close()
|
||||
|
||||
20
test/io/file/read_bytes_from.wren
Normal file
20
test/io/file/read_bytes_from.wren
Normal 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()
|
||||
6
test/io/file/read_bytes_from_after_close.wren
Normal file
6
test/io/file/read_bytes_from_after_close.wren
Normal 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.
|
||||
4
test/io/file/read_bytes_from_count_negative.wren
Normal file
4
test/io/file/read_bytes_from_count_negative.wren
Normal 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.
|
||||
4
test/io/file/read_bytes_from_count_not_integer.wren
Normal file
4
test/io/file/read_bytes_from_count_not_integer.wren
Normal 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.
|
||||
4
test/io/file/read_bytes_from_count_not_num.wren
Normal file
4
test/io/file/read_bytes_from_count_not_num.wren
Normal 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.
|
||||
4
test/io/file/read_bytes_from_offset_negative.wren
Normal file
4
test/io/file/read_bytes_from_offset_negative.wren
Normal 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.
|
||||
4
test/io/file/read_bytes_from_offset_not_integer.wren
Normal file
4
test/io/file/read_bytes_from_offset_not_integer.wren
Normal 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.
|
||||
4
test/io/file/read_bytes_from_offset_not_num.wren
Normal file
4
test/io/file/read_bytes_from_offset_not_num.wren
Normal 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.
|
||||
@ -1,13 +1,6 @@
|
||||
import "io" for File
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
System.print(File.size("test/io/file/size.wren")) // expect: 270
|
||||
|
||||
// Runs asynchronously.
|
||||
Scheduler.add {
|
||||
System.print("async")
|
||||
}
|
||||
|
||||
System.print(File.size("test/io/file/size.wren"))
|
||||
// expect: async
|
||||
// expect: 270
|
||||
var file = File.open("test/io/file/file.txt")
|
||||
System.print(file.size) // expect: 19
|
||||
file.close()
|
||||
|
||||
13
test/io/file/size_static.wren
Normal file
13
test/io/file/size_static.wren
Normal 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
|
||||
16
test/io/file/stat_static.wren
Normal file
16
test/io/file/stat_static.wren
Normal 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
|
||||
16
test/io/file/stat_static_directory.wren
Normal file
16
test/io/file/stat_static_directory.wren
Normal 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
|
||||
3
test/io/file/stat_static_nonexistent.wren
Normal file
3
test/io/file/stat_static_nonexistent.wren
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for File
|
||||
|
||||
File.stat("nonexistent") // expect runtime error: no such file or directory
|
||||
@ -51,6 +51,8 @@ def BENCHMARK(name, pattern):
|
||||
regex = re.compile(pattern + "\n" + r"elapsed: (\d+\.\d+)", re.MULTILINE)
|
||||
BENCHMARKS.append([name, regex, None])
|
||||
|
||||
BENCHMARK("api_call", "true")
|
||||
|
||||
BENCHMARK("api_foreign_method", "100000000")
|
||||
|
||||
BENCHMARK("binary_trees", """stretch tree of depth 13 check: -1
|
||||
|
||||
@ -182,6 +182,7 @@ class Test:
|
||||
|
||||
# Make sure the stack trace has the right line. Skip over any lines that
|
||||
# come from builtin libraries.
|
||||
match = False
|
||||
stack_lines = error_lines[line + 1:]
|
||||
match = None
|
||||
for stack_line in stack_lines:
|
||||
|
||||
@ -206,12 +206,15 @@ $(LIBUV): $(LIBUV_DIR)/build/gyp/gyp util/libuv.py
|
||||
|
||||
# Wren modules that get compiled into the binary as C strings.
|
||||
src/optional/wren_opt_%.wren.inc: src/optional/wren_opt_%.wren util/wren_to_c_string.py
|
||||
@ printf "%10s %-30s %s\n" str $<
|
||||
@ ./util/wren_to_c_string.py $@ $<
|
||||
|
||||
src/vm/wren_%.wren.inc: src/vm/wren_%.wren util/wren_to_c_string.py
|
||||
@ printf "%10s %-30s %s\n" str $<
|
||||
@ ./util/wren_to_c_string.py $@ $<
|
||||
|
||||
src/module/%.wren.inc: src/module/%.wren util/wren_to_c_string.py
|
||||
@ printf "%10s %-30s %s\n" str $<
|
||||
@ ./util/wren_to_c_string.py $@ $<
|
||||
|
||||
.PHONY: all cli test vm
|
||||
|
||||
@ -50,7 +50,5 @@ def main():
|
||||
with open(args.output, "w") as f:
|
||||
f.write(c_source)
|
||||
|
||||
print(" str " + args.input)
|
||||
|
||||
|
||||
main()
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
29205C9E1AB4E6430073018D /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; };
|
||||
29205C9F1AB4E6430073018D /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; };
|
||||
293D46961BB43F9900200083 /* call.c in Sources */ = {isa = PBXBuildFile; fileRef = 293D46941BB43F9900200083 /* call.c */; };
|
||||
2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */ = {isa = PBXBuildFile; fileRef = 2949AA8B1C2F14F000B106BA /* get_variable.c */; };
|
||||
29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; };
|
||||
29512C821B91F901008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; };
|
||||
29729F311BA70A620099CA20 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29729F2E1BA70A620099CA20 /* io.c */; };
|
||||
@ -26,6 +27,7 @@
|
||||
29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; };
|
||||
2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; };
|
||||
29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.c */; };
|
||||
29932D541C210F8D00099DEE /* lists.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D521C210F8D00099DEE /* lists.c */; };
|
||||
29A427341BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; };
|
||||
29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; };
|
||||
29A427361BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */; };
|
||||
@ -47,7 +49,7 @@
|
||||
29DC14A91BBA302F008A8274 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; };
|
||||
29DC14AA1BBA3032008A8274 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A61B7E3993000CE58C /* main.c */; };
|
||||
29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A81B7E39A8000CE58C /* foreign_class.c */; };
|
||||
29DC14AC1BBA303D008A8274 /* returns.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AA1B7E39A8000CE58C /* returns.c */; };
|
||||
29DC14AC1BBA303D008A8274 /* slots.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AA1B7E39A8000CE58C /* slots.c */; };
|
||||
29DC14AD1BBA3040008A8274 /* value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AC1B7E39A8000CE58C /* value.c */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -97,6 +99,8 @@
|
||||
29205CA81AB4E65E0073018D /* wren_vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_vm.h; path = ../../src/vm/wren_vm.h; sourceTree = "<group>"; };
|
||||
293D46941BB43F9900200083 /* call.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = call.c; path = ../../test/api/call.c; sourceTree = "<group>"; };
|
||||
293D46951BB43F9900200083 /* call.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = call.h; path = ../../test/api/call.h; sourceTree = "<group>"; };
|
||||
2949AA8B1C2F14F000B106BA /* get_variable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = get_variable.c; path = ../../test/api/get_variable.c; sourceTree = "<group>"; };
|
||||
2949AA8C1C2F14F000B106BA /* get_variable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = get_variable.h; path = ../../test/api/get_variable.h; sourceTree = "<group>"; };
|
||||
29512C7F1B91F86E008C10E6 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
29512C801B91F8EB008C10E6 /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../build/libuv.a; sourceTree = "<group>"; };
|
||||
296371B31AC713D000079FDA /* wren_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = "<group>"; };
|
||||
@ -106,6 +110,8 @@
|
||||
2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = "<group>"; };
|
||||
29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = "<group>"; };
|
||||
29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = "<group>"; };
|
||||
29932D521C210F8D00099DEE /* lists.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = "<group>"; };
|
||||
29932D531C210F8D00099DEE /* lists.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = "<group>"; };
|
||||
29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_opt_meta.c; path = ../../src/optional/wren_opt_meta.c; sourceTree = "<group>"; };
|
||||
29A4272F1BDBE435001E6E22 /* wren_opt_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = "<group>"; };
|
||||
29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = wren_opt_meta.wren.inc; path = ../../src/optional/wren_opt_meta.wren.inc; sourceTree = "<group>"; };
|
||||
@ -118,8 +124,8 @@
|
||||
29D009A61B7E3993000CE58C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = "<group>"; };
|
||||
29D009A81B7E39A8000CE58C /* foreign_class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = foreign_class.c; path = ../../test/api/foreign_class.c; sourceTree = "<group>"; };
|
||||
29D009A91B7E39A8000CE58C /* foreign_class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = foreign_class.h; path = ../../test/api/foreign_class.h; sourceTree = "<group>"; };
|
||||
29D009AA1B7E39A8000CE58C /* returns.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = returns.c; path = ../../test/api/returns.c; sourceTree = "<group>"; };
|
||||
29D009AB1B7E39A8000CE58C /* returns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = returns.h; path = ../../test/api/returns.h; sourceTree = "<group>"; };
|
||||
29D009AA1B7E39A8000CE58C /* slots.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = slots.c; path = ../../test/api/slots.c; sourceTree = "<group>"; };
|
||||
29D009AB1B7E39A8000CE58C /* slots.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = slots.h; path = ../../test/api/slots.h; sourceTree = "<group>"; };
|
||||
29D009AC1B7E39A8000CE58C /* value.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = value.c; path = ../../test/api/value.c; sourceTree = "<group>"; };
|
||||
29D009AD1B7E39A8000CE58C /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = value.h; path = ../../test/api/value.h; sourceTree = "<group>"; };
|
||||
29F384111BD19706002F84E0 /* io.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/module/io.h; sourceTree = "<group>"; };
|
||||
@ -249,8 +255,12 @@
|
||||
293D46951BB43F9900200083 /* call.h */,
|
||||
29D009A81B7E39A8000CE58C /* foreign_class.c */,
|
||||
29D009A91B7E39A8000CE58C /* foreign_class.h */,
|
||||
29D009AA1B7E39A8000CE58C /* returns.c */,
|
||||
29D009AB1B7E39A8000CE58C /* returns.h */,
|
||||
2949AA8B1C2F14F000B106BA /* get_variable.c */,
|
||||
2949AA8C1C2F14F000B106BA /* get_variable.h */,
|
||||
29932D521C210F8D00099DEE /* lists.c */,
|
||||
29932D531C210F8D00099DEE /* lists.h */,
|
||||
29D009AA1B7E39A8000CE58C /* slots.c */,
|
||||
29D009AB1B7E39A8000CE58C /* slots.h */,
|
||||
29D009AC1B7E39A8000CE58C /* value.c */,
|
||||
29D009AD1B7E39A8000CE58C /* value.h */,
|
||||
);
|
||||
@ -358,7 +368,9 @@
|
||||
files = (
|
||||
29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */,
|
||||
29729F321BA70A620099CA20 /* io.c in Sources */,
|
||||
29932D541C210F8D00099DEE /* lists.c in Sources */,
|
||||
291647C81BA5EC5E006142EE /* modules.c in Sources */,
|
||||
2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */,
|
||||
29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */,
|
||||
29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */,
|
||||
29932D511C20D8C900099DEE /* benchmark.c in Sources */,
|
||||
@ -376,7 +388,7 @@
|
||||
293D46961BB43F9900200083 /* call.c in Sources */,
|
||||
29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */,
|
||||
29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */,
|
||||
29DC14AC1BBA303D008A8274 /* returns.c in Sources */,
|
||||
29DC14AC1BBA303D008A8274 /* slots.c in Sources */,
|
||||
29DC14AD1BBA3040008A8274 /* value.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
Reference in New Issue
Block a user