initial commit

master
Fabien Freling 2019-09-27 18:36:36 +02:00
commit b6c60365ab
67 changed files with 17447 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

9
CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.15)
project(scripted-engine)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_subdirectory(src)

5
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,5 @@
add_subdirectory(engine)
add_subdirectory(logic)
add_executable(sc-eng main.cpp)
target_link_libraries(sc-eng engine logic)

View File

@ -0,0 +1,6 @@
set(MODULE engine)
add_library(${MODULE}
engine.cpp engine.hpp)
target_include_directories(${MODULE} INTERFACE ${CMAKE_CURRENT_LIST_DIR})

8
src/engine/engine.cpp Normal file
View File

@ -0,0 +1,8 @@
#include "engine.hpp"
Engine::Engine() {
}
int Engine::get_info() {
return 42;
}

8
src/engine/engine.hpp Normal file
View File

@ -0,0 +1,8 @@
#pragma once
class Engine {
public:
Engine();
int get_info();
};

8
src/logic/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
add_subdirectory(wren)
set(MODULE logic)
add_library(${MODULE}
logic.cpp logic.hpp)
target_link_libraries(${MODULE} wren)
target_link_libraries(${MODULE} engine)

74
src/logic/logic.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "logic.hpp"
#include "engine.hpp"
#include "wren/vm/wren_vm.h"
#include <cassert>
#include <functional>
#include <iostream>
void writeOutput(WrenVM *vm, const char *text) {
std::cout << "wren output:" << text << "\n";
}
void errorOutput(WrenVM *vm, WrenErrorType type, const char *module, int line, const char *message) {
std::cerr << "wren error [module \"" << module << "\", line " << line << "]: " << message << "\n";
}
Logic::Logic(Engine *engine) {
WrenConfiguration wrenConfig;
wrenInitConfiguration(&wrenConfig);
// Setup user-defined data such as pointer to "global" objects.
_bundleData.engine = engine;
wrenConfig.userData = &_bundleData;
wrenConfig.bindForeignMethodFn = &Logic::bindForeignMethod;
wrenConfig.writeFn = &writeOutput;
wrenConfig.errorFn = &errorOutput;
_wrenVm = wrenNewVM(&wrenConfig);
WrenInterpretResult result = wrenInterpret(_wrenVm, "scripted-engine", "System.print(\"I am running in a VM!\")");
assert(result == WREN_RESULT_SUCCESS);
}
Logic::~Logic() {
wrenFreeVM(_wrenVm);
}
void get_info(WrenVM *vm) {
// Retrieve "global" objects defined in userData
BundleData *bundleData = reinterpret_cast<BundleData *>(vm->config.userData);
Engine *engine = bundleData->engine;
assert(engine);
wrenEnsureSlots(vm, 1);
wrenSetSlotDouble(vm, 0, engine->get_info());
}
WrenForeignMethodFn
Logic::bindForeignMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature) {
if (strcmp(module, "scripted-engine") == 0) {
if (strcmp(className, "Engine") == 0) {
if (isStatic && strcmp(signature, "get_info()") == 0) {
return get_info;
}
// Other foreign methods on Math...
}
// Other classes in main...
}
// Other modules...
return nullptr;
}
void Logic::interpret() {
assert(_wrenVm);
WrenInterpretResult result = wrenInterpret(_wrenVm, "scripted-engine", "class Engine {\
foreign static get_info())\
}\
Engine.get_info()");
assert(result == WREN_RESULT_SUCCESS);
}
void Logic::add_item() {
}

25
src/logic/logic.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "wren/include/wren.hpp"
class Engine;
struct BundleData {
Engine *engine = nullptr;
};
class Logic {
public:
Logic(Engine *engine);
~Logic();
static WrenForeignMethodFn
bindForeignMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature);
void interpret();
void add_item();
private:
BundleData _bundleData;
WrenVM *_wrenVm = nullptr;
};

View File

@ -0,0 +1,13 @@
set(MODULE wren)
file(GLOB OPT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/optional/*.c)
file(GLOB VM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/vm/*.c)
add_library(${MODULE} ${VM_FILES} ${OPT_FILES})
target_include_directories(${MODULE}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/optional
${CMAKE_CURRENT_SOURCE_DIR}/vm
)

24
src/logic/wren/README.md Normal file
View File

@ -0,0 +1,24 @@
This contains the Wren source code. It is organized like so:
* `optional`: the Wren and C source code for the optional modules. These are
built in to the VM and can be used even when you embed the VM in your own
application. But they are also optional and can be compiled out by setting
defines.
* `cli`: the source code for the command line interface. This is a custom
executable that embeds the VM in itself. Code here handles reading
command-line, running the REPL, loading modules from disc, etc.
* `include`: the public header directory for the VM. If you are embedding the
VM in your own application, you will add this to your include path.
* `module`: the source code for the built-in modules that come with the CLI.
These modules are written in a mixture of C and Wren and generally use
[libuv][] to implement their underlying functionality.
* `vm`: the source code for the Wren VM itself. Unlike code in `cli` and
`module`, this has no dependencies on libuv. If you are embedding the VM in
your own application from source, you will compile the files here into your
app.
[libuv]: http://libuv.org/

40
src/logic/wren/cli/main.c Normal file
View File

@ -0,0 +1,40 @@
#include <stdio.h>
#include <string.h>
#include "os.h"
#include "vm.h"
#include "wren.h"
int main(int argc, const char* argv[])
{
if (argc == 2 && strcmp(argv[1], "--help") == 0)
{
printf("Usage: wren [file] [arguments...]\n");
printf(" --help Show command line usage\n");
return 0;
}
if (argc == 2 && strcmp(argv[1], "--version") == 0)
{
printf("wren %s\n", WREN_VERSION_STRING);
return 0;
}
osSetArguments(argc, argv);
WrenInterpretResult result;
if (argc == 1)
{
result = runRepl();
}
else
{
result = runFile(argv[1]);
}
// Exit with an error code if the script failed.
if (result == WREN_RESULT_COMPILE_ERROR) return 65; // EX_DATAERR.
if (result == WREN_RESULT_RUNTIME_ERROR) return 70; // EX_SOFTWARE.
return getExitCode();
}

View File

@ -0,0 +1,278 @@
#include <stdlib.h>
#include <string.h>
#include "modules.h"
#include "io.wren.inc"
#include "os.wren.inc"
#include "repl.wren.inc"
#include "scheduler.wren.inc"
#include "timer.wren.inc"
extern void directoryList(WrenVM* vm);
extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(void* data);
extern void fileDelete(WrenVM* vm);
extern void fileOpen(WrenVM* vm);
extern void fileSizePath(WrenVM* vm);
extern void fileClose(WrenVM* vm);
extern void fileDescriptor(WrenVM* vm);
extern void fileReadBytes(WrenVM* vm);
extern void fileRealPath(WrenVM* vm);
extern void fileSize(WrenVM* vm);
extern void fileStat(WrenVM* vm);
extern void fileWriteBytes(WrenVM* vm);
extern void platformIsPosix(WrenVM* vm);
extern void platformName(WrenVM* vm);
extern void processAllArguments(WrenVM* vm);
extern void statPath(WrenVM* vm);
extern void statBlockCount(WrenVM* vm);
extern void statBlockSize(WrenVM* vm);
extern void statDevice(WrenVM* vm);
extern void statGroup(WrenVM* vm);
extern void statInode(WrenVM* vm);
extern void statLinkCount(WrenVM* vm);
extern void statMode(WrenVM* vm);
extern void statSize(WrenVM* vm);
extern void statSpecialDevice(WrenVM* vm);
extern void statUser(WrenVM* vm);
extern void statIsDirectory(WrenVM* vm);
extern void statIsFile(WrenVM* vm);
extern void stdinIsRaw(WrenVM* vm);
extern void stdinIsRawSet(WrenVM* vm);
extern void stdinIsTerminal(WrenVM* vm);
extern void stdinReadStart(WrenVM* vm);
extern void stdinReadStop(WrenVM* vm);
extern void stdoutFlush(WrenVM* vm);
extern void schedulerCaptureMethods(WrenVM* vm);
extern void timerStartTimer(WrenVM* vm);
// The maximum number of foreign methods a single class defines. Ideally, we
// would use variable-length arrays for each class in the table below, but
// C++98 doesn't have any easy syntax for nested global static data, so we
// just use worst-case fixed-size arrays instead.
//
// 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 14
// 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 6
// Describes one foreign method in a class.
typedef struct
{
bool isStatic;
const char* signature;
WrenForeignMethodFn method;
} MethodRegistry;
// Describes one class in a built-in module.
typedef struct
{
const char* name;
MethodRegistry methods[MAX_METHODS_PER_CLASS];
} ClassRegistry;
// Describes one built-in module.
typedef struct
{
// The name of the module.
const char* name;
// Pointer to the string containing the source code of the module. We use a
// pointer here because the string variable itself is not a constant
// expression so can't be used in the initializer below.
const char **source;
ClassRegistry classes[MAX_CLASSES_PER_MODULE];
} ModuleRegistry;
// To locate foreign classes and modules, we build a big directory for them in
// static data. The nested collection initializer syntax gets pretty noisy, so
// define a couple of macros to make it easier.
#define SENTINEL_METHOD { false, NULL, NULL }
#define SENTINEL_CLASS { NULL, { SENTINEL_METHOD } }
#define SENTINEL_MODULE {NULL, NULL, { SENTINEL_CLASS } }
#define MODULE(name) { #name, &name##ModuleSource, {
#define END_MODULE SENTINEL_CLASS } },
#define CLASS(name) { #name, {
#define END_CLASS SENTINEL_METHOD } },
#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)
FINALIZER(fileFinalize)
STATIC_METHOD("delete_(_,_)", fileDelete)
STATIC_METHOD("open_(_,_,_)", fileOpen)
STATIC_METHOD("realPath_(_,_)", fileRealPath)
STATIC_METHOD("sizePath_(_,_)", fileSizePath)
METHOD("close_(_)", fileClose)
METHOD("descriptor", fileDescriptor)
METHOD("readBytes_(_,_,_)", fileReadBytes)
METHOD("size_(_)", fileSize)
METHOD("stat_(_)", fileStat)
METHOD("writeBytes_(_,_,_)", fileWriteBytes)
END_CLASS
CLASS(Stat)
STATIC_METHOD("path_(_,_)", statPath)
METHOD("blockCount", statBlockCount)
METHOD("blockSize", statBlockSize)
METHOD("device", statDevice)
METHOD("group", statGroup)
METHOD("inode", statInode)
METHOD("linkCount", statLinkCount)
METHOD("mode", statMode)
METHOD("size", statSize)
METHOD("specialDevice", statSpecialDevice)
METHOD("user", statUser)
METHOD("isDirectory", statIsDirectory)
METHOD("isFile", statIsFile)
END_CLASS
CLASS(Stdin)
STATIC_METHOD("isRaw", stdinIsRaw)
STATIC_METHOD("isRaw=(_)", stdinIsRawSet)
STATIC_METHOD("isTerminal", stdinIsTerminal)
STATIC_METHOD("readStart_()", stdinReadStart)
STATIC_METHOD("readStop_()", stdinReadStop)
END_CLASS
CLASS(Stdout)
STATIC_METHOD("flush()", stdoutFlush)
END_CLASS
END_MODULE
MODULE(os)
CLASS(Platform)
STATIC_METHOD("isPosix", platformIsPosix)
STATIC_METHOD("name", platformName)
END_CLASS
CLASS(Process)
STATIC_METHOD("allArguments", processAllArguments)
END_CLASS
END_MODULE
MODULE(repl)
END_MODULE
MODULE(scheduler)
CLASS(Scheduler)
STATIC_METHOD("captureMethods_()", schedulerCaptureMethods)
END_CLASS
END_MODULE
MODULE(timer)
CLASS(Timer)
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
END_CLASS
END_MODULE
SENTINEL_MODULE
};
#undef SENTINEL_METHOD
#undef SENTINEL_CLASS
#undef SENTINEL_MODULE
#undef MODULE
#undef END_MODULE
#undef CLASS
#undef END_CLASS
#undef METHOD
#undef STATIC_METHOD
#undef FINALIZER
// Looks for a built-in module with [name].
//
// Returns the BuildInModule for it or NULL if not found.
static ModuleRegistry* findModule(const char* name)
{
for (int i = 0; modules[i].name != NULL; i++)
{
if (strcmp(name, modules[i].name) == 0) return &modules[i];
}
return NULL;
}
// Looks for a class with [name] in [module].
static ClassRegistry* findClass(ModuleRegistry* module, const char* name)
{
for (int i = 0; module->classes[i].name != NULL; i++)
{
if (strcmp(name, module->classes[i].name) == 0) return &module->classes[i];
}
return NULL;
}
// Looks for a method with [signature] in [clas].
static WrenForeignMethodFn findMethod(ClassRegistry* clas,
bool isStatic, const char* signature)
{
for (int i = 0; clas->methods[i].signature != NULL; i++)
{
MethodRegistry* method = &clas->methods[i];
if (isStatic == method->isStatic &&
strcmp(signature, method->signature) == 0)
{
return method->method;
}
}
return NULL;
}
char* readBuiltInModule(const char* name)
{
ModuleRegistry* module = findModule(name);
if (module == NULL) return NULL;
size_t length = strlen(*module->source);
char* copy = (char*)malloc(length + 1);
memcpy(copy, *module->source, length + 1);
return copy;
}
WrenForeignMethodFn bindBuiltInForeignMethod(
WrenVM* vm, const char* moduleName, const char* className, bool isStatic,
const char* signature)
{
// TODO: Assert instead of return NULL?
ModuleRegistry* module = findModule(moduleName);
if (module == NULL) return NULL;
ClassRegistry* clas = findClass(module, className);
if (clas == NULL) return NULL;
return findMethod(clas, isStatic, signature);
}
WrenForeignClassMethods bindBuiltInForeignClass(
WrenVM* vm, const char* moduleName, const char* className)
{
WrenForeignClassMethods methods = { NULL, NULL };
ModuleRegistry* module = findModule(moduleName);
if (module == NULL) return methods;
ClassRegistry* clas = findClass(module, className);
if (clas == NULL) return methods;
methods.allocate = findMethod(clas, true, "<allocate>");
methods.finalize = (WrenFinalizerFn)findMethod(clas, true, "<finalize>");
return methods;
}

View File

@ -0,0 +1,23 @@
#ifndef modules_h
#define modules_h
// This wires up all of the foreign classes and methods defined by the built-in
// modules bundled with the CLI.
#include "wren.h"
// Returns the source for built-in module [name].
char* readBuiltInModule(const char* module);
// Looks up a foreign method in a built-in module.
//
// Returns `NULL` if [moduleName] is not a built-in module.
WrenForeignMethodFn bindBuiltInForeignMethod(
WrenVM* vm, const char* moduleName, const char* className, bool isStatic,
const char* signature);
// Binds foreign classes declared in a built-in modules.
WrenForeignClassMethods bindBuiltInForeignClass(
WrenVM* vm, const char* moduleName, const char* className);
#endif

312
src/logic/wren/cli/path.c Normal file
View File

@ -0,0 +1,312 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "path.h"
// The maximum number of components in a path. We can't normalize a path that
// contains more than this number of parts. The number here assumes a max path
// length of 4096, which is common on Linux, and then assumes each component is
// at least two characters, "/", and a single-letter directory name.
#define MAX_COMPONENTS 2048
typedef struct {
const char* start;
const char* end;
} Slice;
static void ensureCapacity(Path* path, size_t capacity)
{
// Capacity always needs to be one greater than the actual length to have
// room for the null byte, which is stored in the buffer, but not counted in
// the length. A zero-character path still needs a one-character array to
// store the '\0'.
capacity++;
if (path->capacity >= capacity) return;
// Grow by doubling in size.
size_t newCapacity = 16;
while (newCapacity < capacity) newCapacity *= 2;
path->chars = (char*)realloc(path->chars, newCapacity);
path->capacity = newCapacity;
}
static void appendSlice(Path* path, Slice slice)
{
size_t length = slice.end - slice.start;
ensureCapacity(path, path->length + length);
memcpy(path->chars + path->length, slice.start, length);
path->length += length;
path->chars[path->length] = '\0';
}
static bool isSeparator(char c)
{
// Slash is a separator on POSIX and Windows.
if (c == '/') return true;
// Backslash is only a separator on Windows.
#ifdef _WIN32
if (c == '\\') return true;
#endif
return false;
}
#ifdef _WIN32
static bool isDriveLetter(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
#endif
// Gets the length of the prefix of [path] that defines its absolute root.
//
// Returns 1 the leading "/". On Windows, also handles drive letters ("C:" or
// "C:\").
//
// If the path is not absolute, returns 0.
static size_t absolutePrefixLength(const char* path)
{
#ifdef _WIN32
// Drive letter.
if (isDriveLetter(path[0]) && path[1] == ':')
{
if (isSeparator(path[2]))
{
// Fully absolute path.
return 3;
} else {
// "Half-absolute" path like "C:", which is relative to the current
// working directory on drive. It's absolute for our purposes.
return 2;
}
}
// TODO: UNC paths.
#endif
// POSIX-style absolute path or absolute path in the current drive on Windows.
if (isSeparator(path[0])) return 1;
// Not absolute.
return 0;
}
PathType pathType(const char* path)
{
if (absolutePrefixLength(path) > 0) return PATH_TYPE_ABSOLUTE;
// See if it must be relative.
if ((path[0] == '.' && isSeparator(path[1])) ||
(path[0] == '.' && path[1] == '.' && isSeparator(path[2])))
{
return PATH_TYPE_RELATIVE;
}
// Otherwise, we don't know.
return PATH_TYPE_SIMPLE;
}
Path* pathNew(const char* string)
{
Path* path = (Path*)malloc(sizeof(Path));
path->chars = (char*)malloc(1);
path->chars[0] = '\0';
path->length = 0;
path->capacity = 0;
pathAppendString(path, string);
return path;
}
void pathFree(Path* path)
{
if (path->chars) free(path->chars);
free(path);
}
void pathDirName(Path* path)
{
// Find the last path separator.
for (size_t i = path->length - 1; i < path->length; i--)
{
if (isSeparator(path->chars[i]))
{
path->length = i;
path->chars[i] = '\0';
return;
}
}
// If we got here, there was no separator so it must be a single component.
path->length = 0;
path->chars[0] = '\0';
}
void pathRemoveExtension(Path* path)
{
for (size_t i = path->length - 1; i < path->length; i--)
{
// If we hit a path separator before finding the extension, then the last
// component doesn't have one.
if (isSeparator(path->chars[i])) return;
if (path->chars[i] == '.')
{
path->length = i;
path->chars[path->length] = '\0';
}
}
}
void pathJoin(Path* path, const char* string)
{
if (path->length > 0 && !isSeparator(path->chars[path->length - 1]))
{
pathAppendChar(path, '/');
}
pathAppendString(path, string);
}
void pathAppendChar(Path* path, char c)
{
ensureCapacity(path, path->length + 1);
path->chars[path->length++] = c;
path->chars[path->length] = '\0';
}
void pathAppendString(Path* path, const char* string)
{
Slice slice;
slice.start = string;
slice.end = string + strlen(string);
appendSlice(path, slice);
}
void pathNormalize(Path* path)
{
// Split the path into components.
Slice components[MAX_COMPONENTS];
int numComponents = 0;
char* start = path->chars;
char* end = path->chars;
// Split into parts and handle "." and "..".
int leadingDoubles = 0;
for (;;)
{
if (*end == '\0' || isSeparator(*end))
{
// Add the current component.
if (start != end)
{
size_t length = end - start;
if (length == 1 && start[0] == '.')
{
// Skip "." components.
}
else if (length == 2 && start[0] == '.' && start[1] == '.')
{
// Walk out of directories on "..".
if (numComponents > 0)
{
// Discard the previous component.
numComponents--;
}
else
{
// Can't back out any further, so preserve the "..".
leadingDoubles++;
}
}
else
{
if (numComponents >= MAX_COMPONENTS)
{
fprintf(stderr, "Path cannot have more than %d path components.\n",
MAX_COMPONENTS);
exit(1);
}
components[numComponents].start = start;
components[numComponents].end = end;
numComponents++;
}
}
// Skip over separators.
while (*end != '\0' && isSeparator(*end)) end++;
start = end;
if (*end == '\0') break;
}
end++;
}
// Preserve the path type. We don't want to turn, say, "./foo" into "foo"
// because that changes the semantics of how that path is handled when used
// as an import string.
bool needsSeparator = false;
Path* result = pathNew("");
size_t prefixLength = absolutePrefixLength(path->chars);
if (prefixLength > 0)
{
// It's an absolute path, so preserve the absolute prefix.
Slice slice;
slice.start = path->chars;
slice.end = path->chars + prefixLength;
appendSlice(result, slice);
}
else if (leadingDoubles > 0)
{
// Add any leading "..".
for (int i = 0; i < leadingDoubles; i++)
{
if (needsSeparator) pathAppendChar(result, '/');
pathAppendString(result, "..");
needsSeparator = true;
}
}
else if (path->chars[0] == '.' && isSeparator(path->chars[1]))
{
// Preserve a leading "./", since we use that to distinguish relative from
// logical imports.
pathAppendChar(result, '.');
needsSeparator = true;
}
for (int i = 0; i < numComponents; i++)
{
if (needsSeparator) pathAppendChar(result, '/');
appendSlice(result, components[i]);
needsSeparator = true;
}
if (result->length == 0) pathAppendChar(result, '.');
// Copy back into the original path.
free(path->chars);
path->capacity = result->capacity;
path->chars = result->chars;
path->length = result->length;
free(result);
}
char* pathToString(Path* path)
{
char* string = (char*)malloc(path->length + 1);
memcpy(string, path->chars, path->length);
string[path->length] = '\0';
return string;
}

65
src/logic/wren/cli/path.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef path_h
#define path_h
// Path manipulation functions.
typedef struct
{
// Dynamically allocated array of characters.
char* chars;
// The number of characters currently in use in [chars], not including the
// null terminator.
size_t length;
// Size of the allocated [chars] buffer.
size_t capacity;
} Path;
// Categorizes what form a path is.
typedef enum
{
// An absolute path, starting with "/" on POSIX systems, a drive letter on
// Windows, etc.
PATH_TYPE_ABSOLUTE,
// An explicitly relative path, starting with "./" or "../".
PATH_TYPE_RELATIVE,
// A path that has no leading prefix, like "foo/bar".
PATH_TYPE_SIMPLE,
} PathType;
PathType pathType(const char* path);
// Creates a new empty path.
Path* pathNew(const char* string);
// Releases the method associated with [path].
void pathFree(Path* path);
// Strips off the last component of the path name.
void pathDirName(Path* path);
// Strips off the file extension from the last component of the path.
void pathRemoveExtension(Path* path);
// Appends [string] to [path].
void pathJoin(Path* path, const char* string);
// Appends [c] to the path, growing the buffer if needed.
void pathAppendChar(Path* path, char c);
// Appends [string] to the path, growing the buffer if needed.
void pathAppendString(Path* path, const char* string);
// Simplifies the path string as much as possible.
//
// Applies and removes any "." or ".." components, collapses redundant "/"
// characters, and normalizes all path separators to "/".
void pathNormalize(Path* path);
// Allocates a new string exactly the right length and copies this path to it.
char* pathToString(Path* path);
#endif

32
src/logic/wren/cli/stat.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef stat_h
#define stat_h
// Utilities to smooth over working with stat() in a cross-platform way.
// Windows doesn't define all of the Unix permission and mode flags by default,
// so map them ourselves.
#if defined(WIN32) || defined(WIN64)
#include <sys\stat.h>
// Map to Windows permission flags.
#ifndef S_IRUSR
#define S_IRUSR _S_IREAD
#endif
#ifndef S_IWUSR
#define S_IWUSR _S_IWRITE
#endif
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
// Not supported on Windows.
#define O_SYNC 0
#endif
#endif

405
src/logic/wren/cli/vm.c Normal file
View File

@ -0,0 +1,405 @@
#include <stdio.h>
#include <string.h>
#include "io.h"
#include "modules.h"
#include "path.h"
#include "scheduler.h"
#include "stat.h"
#include "vm.h"
// The single VM instance that the CLI uses.
static WrenVM* vm;
static WrenBindForeignMethodFn bindMethodFn = NULL;
static WrenBindForeignClassFn bindClassFn = NULL;
static WrenForeignMethodFn afterLoadFn = NULL;
static uv_loop_t* loop;
// TODO: This isn't currently used, but probably will be when package imports
// are supported. If not then, then delete this.
static char* rootDirectory = NULL;
static Path* wrenModulesDirectory = NULL;
// The exit code to use unless some other error overrides it.
int defaultExitCode = 0;
// Reads the contents of the file at [path] and returns it as a heap allocated
// string.
//
// Returns `NULL` if the path could not be found. Exits if it was found but
// could not be read.
static char* readFile(const char* path)
{
FILE* file = fopen(path, "rb");
if (file == NULL) return NULL;
// Find out how big the file is.
fseek(file, 0L, SEEK_END);
size_t fileSize = ftell(file);
rewind(file);
// Allocate a buffer for it.
char* buffer = (char*)malloc(fileSize + 1);
if (buffer == NULL)
{
fprintf(stderr, "Could not read file \"%s\".\n", path);
exit(74);
}
// Read the entire file.
size_t bytesRead = fread(buffer, 1, fileSize, file);
if (bytesRead < fileSize)
{
fprintf(stderr, "Could not read file \"%s\".\n", path);
exit(74);
}
// Terminate the string.
buffer[bytesRead] = '\0';
fclose(file);
return buffer;
}
static bool isDirectory(Path* path)
{
uv_fs_t request;
uv_fs_stat(loop, &request, path->chars, NULL);
// TODO: Check request.result value?
bool result = request.result == 0 && S_ISDIR(request.statbuf.st_mode);
uv_fs_req_cleanup(&request);
return result;
}
static Path* realPath(Path* path)
{
uv_fs_t request;
uv_fs_realpath(loop, &request, path->chars, NULL);
Path* result = pathNew((char*)request.ptr);
uv_fs_req_cleanup(&request);
return result;
}
// Starting at [rootDirectory], walks up containing directories looking for a
// nearby "wren_modules" directory. If found, stores it in
// [wrenModulesDirectory].
//
// If [wrenModulesDirectory] has already been found, does nothing.
static void findModulesDirectory()
{
if (wrenModulesDirectory != NULL) return;
Path* searchDirectory = pathNew(rootDirectory);
Path* lastPath = realPath(searchDirectory);
// Keep walking up directories as long as we find them.
for (;;)
{
Path* modulesDirectory = pathNew(searchDirectory->chars);
pathJoin(modulesDirectory, "wren_modules");
if (isDirectory(modulesDirectory))
{
pathNormalize(modulesDirectory);
wrenModulesDirectory = modulesDirectory;
pathFree(lastPath);
break;
}
pathFree(modulesDirectory);
// Walk up directories until we hit the root. We can tell that because
// adding ".." yields the same real path.
pathJoin(searchDirectory, "..");
Path* thisPath = realPath(searchDirectory);
if (strcmp(lastPath->chars, thisPath->chars) == 0)
{
pathFree(thisPath);
break;
}
pathFree(lastPath);
lastPath = thisPath;
}
pathFree(searchDirectory);
}
// Applies the CLI's import resolution policy. The rules are:
//
// * If [module] starts with "./" or "../", it is a relative import, relative
// to [importer]. The resolved path is [name] concatenated onto the directory
// containing [importer] and then normalized.
//
// For example, importing "./a/./b/../c" from "./d/e/f" gives you "./d/e/a/c".
static const char* resolveModule(WrenVM* vm, const char* importer,
const char* module)
{
// Logical import strings are used as-is and need no resolution.
if (pathType(module) == PATH_TYPE_SIMPLE) return module;
// Get the directory containing the importing module.
Path* path = pathNew(importer);
pathDirName(path);
// Add the relative import path.
pathJoin(path, module);
pathNormalize(path);
char* resolved = pathToString(path);
pathFree(path);
return resolved;
}
// Attempts to read the source for [module] relative to the current root
// directory.
//
// Returns it if found, or NULL if the module could not be found. Exits if the
// module was found but could not be read.
static char* readModule(WrenVM* vm, const char* module)
{
Path* filePath;
if (pathType(module) == PATH_TYPE_SIMPLE)
{
// If there is no "wren_modules" directory, then the only logical imports
// we can handle are built-in ones. Let the VM try to handle it.
findModulesDirectory();
if (wrenModulesDirectory == NULL) return readBuiltInModule(module);
// TODO: Should we explicitly check for the existence of the module's base
// directory inside "wren_modules" here?
// Look up the module in "wren_modules".
filePath = pathNew(wrenModulesDirectory->chars);
pathJoin(filePath, module);
// If the module is a single bare name, treat it as a module with the same
// name inside the package. So "foo" means "foo/foo".
if (strchr(module, '/') == NULL) pathJoin(filePath, module);
}
else
{
// The module path is already a file path.
filePath = pathNew(module);
}
// Add a ".wren" file extension.
pathAppendString(filePath, ".wren");
char* source = readFile(filePath->chars);
pathFree(filePath);
// If we didn't find it, it may be a module built into the CLI or VM, so keep
// going.
if (source != NULL) return source;
// Otherwise, see if it's a built-in module.
return readBuiltInModule(module);
}
// Binds foreign methods declared in either built in modules, or the injected
// API test modules.
static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
const char* className, bool isStatic, const char* signature)
{
WrenForeignMethodFn method = bindBuiltInForeignMethod(vm, module, className,
isStatic, signature);
if (method != NULL) return method;
if (bindMethodFn != NULL)
{
return bindMethodFn(vm, module, className, isStatic, signature);
}
return NULL;
}
// Binds foreign classes declared in either built in modules, or the injected
// API test modules.
static WrenForeignClassMethods bindForeignClass(
WrenVM* vm, const char* module, const char* className)
{
WrenForeignClassMethods methods = bindBuiltInForeignClass(vm, module,
className);
if (methods.allocate != NULL) return methods;
if (bindClassFn != NULL)
{
return bindClassFn(vm, module, className);
}
return methods;
}
static void write(WrenVM* vm, const char* text)
{
printf("%s", text);
}
static void reportError(WrenVM* vm, WrenErrorType type,
const char* module, int line, const char* message)
{
switch (type)
{
case WREN_ERROR_COMPILE:
fprintf(stderr, "[%s line %d] %s\n", module, line, message);
break;
case WREN_ERROR_RUNTIME:
fprintf(stderr, "%s\n", message);
break;
case WREN_ERROR_STACK_TRACE:
fprintf(stderr, "[%s line %d] in %s\n", module, line, message);
break;
}
}
static void initVM()
{
WrenConfiguration config;
wrenInitConfiguration(&config);
config.bindForeignMethodFn = bindForeignMethod;
config.bindForeignClassFn = bindForeignClass;
config.resolveModuleFn = resolveModule;
config.loadModuleFn = readModule;
config.writeFn = write;
config.errorFn = reportError;
// Since we're running in a standalone process, be generous with memory.
config.initialHeapSize = 1024 * 1024 * 100;
vm = wrenNewVM(&config);
// Initialize the event loop.
loop = (uv_loop_t*)malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
}
static void freeVM()
{
ioShutdown();
schedulerShutdown();
uv_loop_close(loop);
free(loop);
wrenFreeVM(vm);
uv_tty_reset_mode();
if (wrenModulesDirectory != NULL) pathFree(wrenModulesDirectory);
}
WrenInterpretResult runFile(const char* path)
{
char* source = readFile(path);
if (source == NULL)
{
fprintf(stderr, "Could not find file \"%s\".\n", path);
exit(66);
}
// If it looks like a relative path, make it explicitly relative so that we
// can distinguish it from logical paths.
// TODO: It might be nice to be able to run scripts from within a surrounding
// "wren_modules" directory by passing in a simple path like "foo/bar". In
// that case, here, we could check to see whether the give path exists inside
// "wren_modules" or as a relative path and choose to add "./" or not based
// on that.
Path* module = pathNew(path);
if (pathType(module->chars) == PATH_TYPE_SIMPLE)
{
Path* relative = pathNew(".");
pathJoin(relative, path);
pathFree(module);
module = relative;
}
pathRemoveExtension(module);
// Use the directory where the file is as the root to resolve imports
// relative to.
Path* directory = pathNew(module->chars);
pathDirName(directory);
rootDirectory = pathToString(directory);
pathFree(directory);
initVM();
WrenInterpretResult result = wrenInterpret(vm, module->chars, source);
if (afterLoadFn != NULL) afterLoadFn(vm);
if (result == WREN_RESULT_SUCCESS)
{
uv_run(loop, UV_RUN_DEFAULT);
}
freeVM();
free(source);
free(rootDirectory);
pathFree(module);
return result;
}
WrenInterpretResult runRepl()
{
// This cast is safe since we don't try to free the string later.
rootDirectory = (char*)".";
initVM();
printf("\\\\/\"-\n");
printf(" \\_/ wren v%s\n", WREN_VERSION_STRING);
WrenInterpretResult result = wrenInterpret(vm, "<repl>", "import \"repl\"\n");
if (result == WREN_RESULT_SUCCESS)
{
uv_run(loop, UV_RUN_DEFAULT);
}
freeVM();
return result;
}
WrenVM* getVM()
{
return vm;
}
uv_loop_t* getLoop()
{
return loop;
}
int getExitCode()
{
return defaultExitCode;
}
void setExitCode(int exitCode)
{
defaultExitCode = exitCode;
}
void setTestCallbacks(WrenBindForeignMethodFn bindMethod,
WrenBindForeignClassFn bindClass,
WrenForeignMethodFn afterLoad)
{
bindMethodFn = bindMethod;
bindClassFn = bindClass;
afterLoadFn = afterLoad;
}

33
src/logic/wren/cli/vm.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef vm_h
#define vm_h
#include "uv.h"
#include "wren.h"
// Executes the Wren script at [path] in a new VM.
WrenInterpretResult runFile(const char* path);
// Runs the Wren interactive REPL.
WrenInterpretResult runRepl();
// Gets the currently running VM.
WrenVM* getVM();
// Gets the event loop the VM is using.
uv_loop_t* getLoop();
// Get the exit code the CLI should exit with when done.
int getExitCode();
// Set the exit code the CLI should exit with when done.
void setExitCode(int exitCode);
// Adds additional callbacks to use when binding foreign members from Wren.
//
// Used by the API test executable to let it wire up its own foreign functions.
// This must be called before calling [createVM()].
void setTestCallbacks(WrenBindForeignMethodFn bindMethod,
WrenBindForeignClassFn bindClass,
void (*afterLoad)(WrenVM* vm));
#endif

View File

@ -0,0 +1,488 @@
#ifndef wren_h
#define wren_h
#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>
// The Wren semantic version number components.
#define WREN_VERSION_MAJOR 0
#define WREN_VERSION_MINOR 1
#define WREN_VERSION_PATCH 0
// A human-friendly string representation of the version.
#define WREN_VERSION_STRING "0.1.0"
// A monotonically increasing numeric representation of the version number. Use
// this if you want to do range checks over versions.
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
WREN_VERSION_MINOR * 1000 + \
WREN_VERSION_PATCH)
// A single virtual machine for executing Wren code.
//
// Wren has no global state, so all state stored by a running interpreter lives
// here.
typedef struct WrenVM WrenVM;
// A handle to a Wren object.
//
// This lets code outside of the VM hold a persistent reference to an object.
// After a handle is acquired, and until it is released, this ensures the
// garbage collector will not reclaim the object it references.
typedef struct WrenHandle WrenHandle;
// A generic allocation function that handles all explicit memory management
// used by Wren. It's used like so:
//
// - To allocate new memory, [memory] is NULL and [newSize] is the desired
// size. It should return the allocated memory or NULL on failure.
//
// - To attempt to grow an existing allocation, [memory] is the memory, and
// [newSize] is the desired size. It should return [memory] if it was able to
// grow it in place, or a new pointer if it had to move it.
//
// - To shrink memory, [memory] and [newSize] are the same as above but it will
// always return [memory].
//
// - To free memory, [memory] will be the memory to free and [newSize] will be
// zero. It should return NULL.
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);
// Gives the host a chance to canonicalize the imported module name,
// potentially taking into account the (previously resolved) name of the module
// that contains the import. Typically, this is used to implement relative
// imports.
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
const char* importer, const char* name);
// Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
// Returns a pointer to a foreign method on [className] in [module] with
// [signature].
typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
const char* module, const char* className, bool isStatic,
const char* signature);
// Displays a string of text to the user.
typedef void (*WrenWriteFn)(WrenVM* vm, const char* text);
typedef enum
{
// A syntax or resolution error detected at compile time.
WREN_ERROR_COMPILE,
// The error message for a runtime error.
WREN_ERROR_RUNTIME,
// One entry of a runtime error's stack trace.
WREN_ERROR_STACK_TRACE
} WrenErrorType;
// Reports an error to the user.
//
// An error detected during compile time is reported by calling this once with
// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line]
// where the error occurs, and the compiler's error [message].
//
// A runtime error is reported by calling this once with [type]
// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's
// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are
// made for each line in the stack trace. Each of those has the resolved
// [module] and [line] where the method or function is defined and [message] is
// the name of the method or function.
typedef void (*WrenErrorFn)(
WrenVM* vm, WrenErrorType type, const char* module, int line,
const char* message);
typedef struct
{
// The callback invoked when the foreign object is created.
//
// This must be provided. Inside the body of this, it must call
// [wrenSetSlotNewForeign()] exactly once.
WrenForeignMethodFn allocate;
// The callback invoked when the garbage collector is about to collect a
// foreign object's memory.
//
// This may be `NULL` if the foreign class does not need to finalize.
WrenFinalizerFn finalize;
} WrenForeignClassMethods;
// Returns a pair of pointers to the foreign methods used to allocate and
// finalize the data for instances of [className] in resolved [module].
typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(
WrenVM* vm, const char* module, const char* className);
typedef struct
{
// The callback Wren will use to allocate, reallocate, and deallocate memory.
//
// If `NULL`, defaults to a built-in function that uses `realloc` and `free`.
WrenReallocateFn reallocateFn;
// The callback Wren uses to resolve a module name.
//
// Some host applications may wish to support "relative" imports, where the
// meaning of an import string depends on the module that contains it. To
// support that without baking any policy into Wren itself, the VM gives the
// host a chance to resolve an import string.
//
// Before an import is loaded, it calls this, passing in the name of the
// module that contains the import and the import string. The host app can
// look at both of those and produce a new "canonical" string that uniquely
// identifies the module. This string is then used as the name of the module
// going forward. It is what is passed to [loadModuleFn], how duplicate
// imports of the same module are detected, and how the module is reported in
// stack traces.
//
// If you leave this function NULL, then the original import string is
// treated as the resolved string.
//
// If an import cannot be resolved by the embedder, it should return NULL and
// Wren will report that as a runtime error.
//
// Wren will take ownership of the string you return and free it for you, so
// it should be allocated using the same allocation function you provide
// above.
WrenResolveModuleFn resolveModuleFn;
// The callback Wren uses to load a module.
//
// Since Wren does not talk directly to the file system, it relies on the
// embedder to physically locate and read the source code for a module. The
// first time an import appears, Wren will call this and pass in the name of
// the module being imported. The VM should return the soure code for that
// module. Memory for the source should be allocated using [reallocateFn] and
// Wren will take ownership over it.
//
// This will only be called once for any given module name. Wren caches the
// result internally so subsequent imports of the same module will use the
// previous source and not call this.
//
// If a module with the given name could not be found by the embedder, it
// should return NULL and Wren will report that as a runtime error.
WrenLoadModuleFn loadModuleFn;
// The callback Wren uses to find a foreign method and bind it to a class.
//
// When a foreign method is declared in a class, this will be called with the
// foreign method's module, class, and signature when the class body is
// executed. It should return a pointer to the foreign function that will be
// bound to that method.
//
// If the foreign function could not be found, this should return NULL and
// Wren will report it as runtime error.
WrenBindForeignMethodFn bindForeignMethodFn;
// The callback Wren uses to find a foreign class and get its foreign methods.
//
// When a foreign class is declared, this will be called with the class's
// module and name when the class body is executed. It should return the
// foreign functions uses to allocate and (optionally) finalize the bytes
// stored in the foreign object when an instance is created.
WrenBindForeignClassFn bindForeignClassFn;
// The callback Wren uses to display text when `System.print()` or the other
// related functions are called.
//
// If this is `NULL`, Wren discards any printed text.
WrenWriteFn writeFn;
// The callback Wren uses to report errors.
//
// When an error occurs, this will be called with the module name, line
// number, and an error message. If this is `NULL`, Wren doesn't report any
// errors.
WrenErrorFn errorFn;
// The number of bytes Wren will allocate before triggering the first garbage
// collection.
//
// If zero, defaults to 10MB.
size_t initialHeapSize;
// After a collection occurs, the threshold for the next collection is
// determined based on the number of bytes remaining in use. This allows Wren
// to shrink its memory usage automatically after reclaiming a large amount
// of memory.
//
// This can be used to ensure that the heap does not get too small, which can
// in turn lead to a large number of collections afterwards as the heap grows
// back to a usable size.
//
// If zero, defaults to 1MB.
size_t minHeapSize;
// Wren will resize the heap automatically as the number of bytes
// remaining in use after a collection changes. This number determines the
// amount of additional memory Wren will use after a collection, as a
// percentage of the current heap size.
//
// For example, say that this is 50. After a garbage collection, when there
// are 400 bytes of memory still in use, the next collection will be triggered
// after a total of 600 bytes are allocated (including the 400 already in
// use.)
//
// Setting this to a smaller number wastes less memory, but triggers more
// frequent garbage collections.
//
// If zero, defaults to 50.
int heapGrowthPercent;
// User-defined data associated with the VM.
void* userData;
} WrenConfiguration;
typedef enum
{
WREN_RESULT_SUCCESS,
WREN_RESULT_COMPILE_ERROR,
WREN_RESULT_RUNTIME_ERROR
} WrenInterpretResult;
// The type of an object stored in a slot.
//
// This is not necessarily the object's *class*, but instead its low level
// representation type.
typedef enum
{
WREN_TYPE_BOOL,
WREN_TYPE_NUM,
WREN_TYPE_FOREIGN,
WREN_TYPE_LIST,
WREN_TYPE_NULL,
WREN_TYPE_STRING,
// The object is of a type that isn't accessible by the C API.
WREN_TYPE_UNKNOWN
} WrenType;
// Initializes [configuration] with all of its default values.
//
// Call this before setting the particular fields you care about.
void wrenInitConfiguration(WrenConfiguration* configuration);
// Creates a new Wren virtual machine using the given [configuration]. Wren
// will copy the configuration data, so the argument passed to this can be
// freed after calling this. If [configuration] is `NULL`, uses a default
// configuration.
WrenVM* wrenNewVM(WrenConfiguration* configuration);
// Disposes of all resources is use by [vm], which was previously created by a
// call to [wrenNewVM].
void wrenFreeVM(WrenVM* vm);
// Immediately run the garbage collector to free unused memory.
void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm] in the
// context of resolved [module].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source);
// 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 you are done with this handle, it must be released using
// [wrenReleaseHandle].
WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// Calls [method], using the receiver and arguments previously set up on the
// stack.
//
// [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.
//
// After this returns, you can access the return value from slot 0 on the stack.
WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
// Releases the reference stored in [handle]. After calling this, [handle] can
// no longer be used.
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
// 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 wrenGetSlotHandle(), 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.
// Returns the number of slots available to the current foreign method.
int wrenGetSlotCount(WrenVM* vm);
// Ensures that the foreign method stack has at least [numSlots] available for
// use, growing the stack if needed.
//
// Does not shrink the stack if it has more than enough slots.
//
// It is an error to call this from a finalizer.
void wrenEnsureSlots(WrenVM* vm, int numSlots);
// Gets the type of the object in [slot].
WrenType wrenGetSlotType(WrenVM* vm, int slot);
// 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 byte array 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.
//
// 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);
// 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 [wrenReleaseHandle()].
WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);
// Stores the boolean [value] in [slot].
void wrenSetSlotBool(WrenVM* vm, int slot, bool 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);
// Stores the numeric [value] in [slot].
void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// Creates a new instance of the foreign class stored in [classSlot] with [size]
// bytes of raw storage and places the resulting object in [slot].
//
// This does not invoke the foreign class's constructor on the new instance. If
// you need that to happen, call the constructor from Wren, which will then
// call the allocator foreign method. In there, call this to create the object
// and then the constructor will be invoked when the allocator returns.
//
// Returns a pointer to the foreign object's data.
void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);
// 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].
//
// 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 [handle] in [slot].
//
// This does not release the handle for the value.
void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
// Returns the number of elements in the list stored in [slot].
int wrenGetListCount(WrenVM* vm, int slot);
// Reads element [index] from the list in [listSlot] and stores it in
// [elementSlot].
void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// 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 resolved [module] and stores
// it in [slot].
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot);
// Sets the current fiber to be aborted, and uses the value in [slot] as the
// runtime error object.
void wrenAbortFiber(WrenVM* vm, int slot);
// Returns the user data associated with the WrenVM.
void* wrenGetUserData(WrenVM* vm);
// Sets user data associated with the WrenVM.
void wrenSetUserData(WrenVM* vm, void* userData);
#endif

View File

@ -0,0 +1,11 @@
#ifndef wren_hpp
#define wren_hpp
// This is a convenience header for users that want to compile Wren as C and
// link to it from a C++ application.
extern "C" {
#include "wren.h"
}
#endif

600
src/logic/wren/module/io.c Normal file
View File

@ -0,0 +1,600 @@
#include <stdlib.h>
#include <string.h>
#include "uv.h"
#include "scheduler.h"
#include "stat.h"
#include "vm.h"
#include "wren.h"
#include <stdio.h>
#include <fcntl.h>
typedef struct sFileRequestData
{
WrenHandle* fiber;
uv_buf_t buffer;
} FileRequestData;
static const int stdinDescriptor = 0;
// Handle to the Stat class object.
static WrenHandle* statClass = NULL;
// Handle to the Stdin class object.
static WrenHandle* stdinClass = NULL;
// Handle to an onData_() method call. Called when libuv provides data on stdin.
static WrenHandle* stdinOnData = NULL;
// The stream used to read from stdin. Initialized on the first read.
static uv_stream_t* stdinStream = NULL;
// True if stdin has been set to raw mode.
static bool isStdinRaw = false;
// Frees all resources related to stdin.
static void shutdownStdin()
{
if (stdinStream != NULL)
{
uv_tty_reset_mode();
uv_close((uv_handle_t*)stdinStream, NULL);
free(stdinStream);
stdinStream = NULL;
}
if (stdinClass != NULL)
{
wrenReleaseHandle(getVM(), stdinClass);
stdinClass = NULL;
}
if (stdinOnData != NULL)
{
wrenReleaseHandle(getVM(), stdinOnData);
stdinOnData = NULL;
}
}
void ioShutdown()
{
shutdownStdin();
if (statClass != NULL)
{
wrenReleaseHandle(getVM(), statClass);
statClass = NULL;
}
}
// If [request] failed with an error, sends the runtime error to the VM and
// frees the request.
//
// Returns true if an error was reported.
static bool handleRequestError(uv_fs_t* request)
{
if (request->result >= 0) return false;
FileRequestData* data = (FileRequestData*)request->data;
WrenHandle* fiber = (WrenHandle*)data->fiber;
int error = (int)request->result;
free(data);
uv_fs_req_cleanup(request);
free(request);
schedulerResumeError(fiber, uv_strerror(error));
return true;
}
// Allocates a new request that resumes [fiber] when it completes.
uv_fs_t* createRequest(WrenHandle* 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;
}
// Releases resources used by [request].
//
// Returns the fiber that should be resumed after [request] completes.
WrenHandle* freeRequest(uv_fs_t* request)
{
FileRequestData* data = (FileRequestData*)request->data;
WrenHandle* fiber = data->fiber;
free(data);
uv_fs_req_cleanup(request);
free(request);
return fiber;
}
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(wrenGetSlotHandle(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*)wrenSetSlotNewForeign(vm, 0, 0, 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 fileDeleteCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
schedulerResume(freeRequest(request), false);
}
void fileDelete(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2));
// TODO: Check return.
uv_fs_unlink(getLoop(), request, path, fileDeleteCallback);
}
static void fileOpenCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
double fd = (double)request->result;
schedulerResume(freeRequest(request), true);
wrenSetSlotDouble(getVM(), 2, fd);
schedulerFinishResume();
}
// The UNIX file flags have specified names but not values. So we define our
// own values in FileFlags and remap them to the host OS's values here.
static int mapFileFlags(int flags)
{
int result = 0;
// Note: These must be kept in sync with FileFlags in io.wren.
if (flags & 0x01) result |= O_RDONLY;
if (flags & 0x02) result |= O_WRONLY;
if (flags & 0x04) result |= O_RDWR;
if (flags & 0x08) result |= O_SYNC;
if (flags & 0x10) result |= O_CREAT;
if (flags & 0x20) result |= O_TRUNC;
if (flags & 0x40) result |= O_EXCL;
return result;
}
void fileOpen(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
int flags = (int)wrenGetSlotDouble(vm, 2);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 3));
// TODO: Allow controlling access.
uv_fs_open(getLoop(), request, path, mapFileFlags(flags), S_IRUSR | S_IWUSR,
fileOpenCallback);
}
// Called by libuv when the stat call for size completes.
static void fileSizeCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
double size = (double)request->statbuf.st_size;
schedulerResume(freeRequest(request), true);
wrenSetSlotDouble(getVM(), 2, size);
schedulerFinishResume();
}
void fileSizePath(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2));
uv_fs_stat(getLoop(), request, path, fileSizeCallback);
}
static void fileCloseCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
schedulerResume(freeRequest(request), false);
}
void fileClose(WrenVM* vm)
{
int* foreign = (int*)wrenGetSlotForeign(vm, 0);
int fd = *foreign;
// If it's already closed, we're done.
if (fd == -1)
{
wrenSetSlotBool(vm, 0, true);
return;
}
// Mark it closed immediately.
*foreign = -1;
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 1));
uv_fs_close(getLoop(), request, fd, fileCloseCallback);
wrenSetSlotBool(vm, 0, false);
}
void fileDescriptor(WrenVM* vm)
{
int* foreign = (int*)wrenGetSlotForeign(vm, 0);
int fd = *foreign;
wrenSetSlotDouble(vm, 0, fd);
}
static void fileReadBytesCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
FileRequestData* data = (FileRequestData*)request->data;
uv_buf_t buffer = data->buffer;
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.
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(wrenGetSlotHandle(vm, 3));
int fd = *(int*)wrenGetSlotForeign(vm, 0);
// TODO: Assert fd != -1.
FileRequestData* data = (FileRequestData*)request->data;
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);
uv_fs_read(getLoop(), request, fd, &data->buffer, 1, offset,
fileReadBytesCallback);
}
static void realPathCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
wrenEnsureSlots(getVM(), 3);
wrenSetSlotString(getVM(), 2, (char*)request->ptr);
schedulerResume(freeRequest(request), true);
schedulerFinishResume();
}
void fileRealPath(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2));
uv_fs_realpath(getLoop(), request, path, realPathCallback);
}
// Called by libuv when the stat call completes.
static void statCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
// Get a handle to the Stat class. We'll hang on to this so we don't have to
// look it up by name every time.
if (statClass == NULL)
{
wrenGetVariable(vm, "io", "Stat", 0);
statClass = wrenGetSlotHandle(vm, 0);
}
// Create a foreign Stat object to store the stat struct.
wrenSetSlotHandle(vm, 2, statClass);
wrenSetSlotNewForeign(vm, 2, 2, sizeof(uv_stat_t));
// Copy the stat data.
uv_stat_t* data = (uv_stat_t*)wrenGetSlotForeign(vm, 2);
*data = request->statbuf;
schedulerResume(freeRequest(request), true);
schedulerFinishResume();
}
void fileStat(WrenVM* vm)
{
int fd = *(int*)wrenGetSlotForeign(vm, 0);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 1));
uv_fs_fstat(getLoop(), request, fd, statCallback);
}
void fileSize(WrenVM* vm)
{
int fd = *(int*)wrenGetSlotForeign(vm, 0);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 1));
uv_fs_fstat(getLoop(), request, fd, fileSizeCallback);
}
static void fileWriteBytesCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
FileRequestData* data = (FileRequestData*)request->data;
free(data->buffer.base);
schedulerResume(freeRequest(request), false);
}
void fileWriteBytes(WrenVM* vm)
{
int fd = *(int*)wrenGetSlotForeign(vm, 0);
int length;
const char* bytes = wrenGetSlotBytes(vm, 1, &length);
size_t offset = (size_t)wrenGetSlotDouble(vm, 2);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 3));
FileRequestData* data = (FileRequestData*)request->data;
data->buffer.len = length;
// TODO: Instead of copying, just create a WrenHandle for the byte string and
// hold on to it in the request until the write is done.
// TODO: Handle allocation failure.
data->buffer.base = (char*)malloc(length);
memcpy(data->buffer.base, bytes, length);
uv_fs_write(getLoop(), request, fd, &data->buffer, 1, offset,
fileWriteBytesCallback);
}
void statPath(WrenVM* vm)
{
const char* path = wrenGetSlotString(vm, 1);
uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2));
uv_fs_stat(getLoop(), request, path, statCallback);
}
void statBlockCount(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_blocks);
}
void statBlockSize(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_blksize);
}
void statDevice(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_dev);
}
void statGroup(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_gid);
}
void statInode(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_ino);
}
void statLinkCount(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_nlink);
}
void statMode(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_mode);
}
void statSize(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_size);
}
void statSpecialDevice(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_rdev);
}
void statUser(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_uid);
}
void statIsDirectory(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, S_ISDIR(stat->st_mode));
}
void statIsFile(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, S_ISREG(stat->st_mode));
}
// Sets up the stdin stream if not already initialized.
static void initStdin()
{
if (stdinStream == NULL)
{
if (uv_guess_handle(stdinDescriptor) == UV_TTY)
{
// stdin is connected to a terminal.
uv_tty_t* handle = (uv_tty_t*)malloc(sizeof(uv_tty_t));
uv_tty_init(getLoop(), handle, stdinDescriptor, true);
stdinStream = (uv_stream_t*)handle;
}
else
{
// stdin is a pipe or a file.
uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t));
uv_pipe_init(getLoop(), handle, false);
uv_pipe_open(handle, stdinDescriptor);
stdinStream = (uv_stream_t*)handle;
}
}
}
void stdinIsRaw(WrenVM* vm)
{
wrenSetSlotBool(vm, 0, isStdinRaw);
}
void stdinIsRawSet(WrenVM* vm)
{
initStdin();
isStdinRaw = wrenGetSlotBool(vm, 1);
if (uv_guess_handle(stdinDescriptor) == UV_TTY)
{
uv_tty_t* handle = (uv_tty_t*)stdinStream;
uv_tty_set_mode(handle, isStdinRaw ? UV_TTY_MODE_RAW : UV_TTY_MODE_NORMAL);
}
else
{
// Can't set raw mode when not talking to a TTY.
// TODO: Make this a runtime error?
}
}
void stdinIsTerminal(WrenVM* vm)
{
initStdin();
wrenSetSlotBool(vm, 0, uv_guess_handle(stdinDescriptor) == UV_TTY);
}
void stdoutFlush(WrenVM* vm)
{
fflush(stdout);
wrenSetSlotNull(vm, 0);
}
static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf)
{
// TODO: Handle allocation failure.
buf->base = (char*)malloc(suggestedSize);
buf->len = suggestedSize;
}
static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead,
const uv_buf_t* buffer)
{
WrenVM* vm = getVM();
if (stdinClass == NULL)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "io", "Stdin", 0);
stdinClass = wrenGetSlotHandle(vm, 0);
}
if (stdinOnData == NULL)
{
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
}
// If stdin was closed, send null to let io.wren know.
if (numRead == UV_EOF)
{
wrenEnsureSlots(vm, 2);
wrenSetSlotHandle(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.
wrenEnsureSlots(vm, 2);
wrenSetSlotHandle(vm, 0, stdinClass);
wrenSetSlotBytes(vm, 1, buffer->base, numRead);
wrenCall(vm, stdinOnData);
// TODO: Likewise, freeing this after we resume is lame.
free(buffer->base);
}
void stdinReadStart(WrenVM* vm)
{
initStdin();
uv_read_start(stdinStream, allocCallback, stdinReadCallback);
// TODO: Check return.
}
void stdinReadStop(WrenVM* vm)
{
uv_read_stop(stdinStream);
}

View File

@ -0,0 +1,11 @@
#ifndef io_h
#define io_h
#include "wren.h"
// Frees up any pending resources in use by the IO module.
//
// In particular, this closes down the stdin stream.
void ioShutdown();
#endif

View File

@ -0,0 +1,303 @@
import "scheduler" for Scheduler
class Directory {
// TODO: Copied from File. Figure out good way to share this.
static ensurePath_(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
}
static exists(path) {
ensurePath_(path)
var stat
Fiber.new {
stat = Stat.path(path)
}.try()
// If we can't stat it, there's nothing there.
if (stat == null) return false
return stat.isDirectory
}
static list(path) {
ensurePath_(path)
list_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
foreign static list_(path, fiber)
}
foreign class File {
static create(path) {
return openWithFlags(path,
FileFlags.writeOnly |
FileFlags.create |
FileFlags.truncate)
}
static create(path, fn) {
return openWithFlags(path,
FileFlags.writeOnly |
FileFlags.create |
FileFlags.truncate, fn)
}
static delete(path) {
ensurePath_(path)
delete_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
static exists(path) {
ensurePath_(path)
var stat
Fiber.new {
stat = Stat.path(path)
}.try()
// If we can't stat it, there's nothing there.
if (stat == null) return false
return stat.isFile
}
static open(path) { openWithFlags(path, FileFlags.readOnly) }
static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }
// TODO: Add named parameters and then call this "open(_,flags:_)"?
// TODO: Test.
static openWithFlags(path, flags) {
ensurePath_(path)
ensureInt_(flags, "Flags")
open_(path, flags, Fiber.current)
var fd = Scheduler.runNextScheduled_()
return new_(fd)
}
static openWithFlags(path, flags, fn) {
var file = openWithFlags(path, flags)
var fiber = Fiber.new { fn.call(file) }
// Poor man's finally. Can we make this more elegant?
var result = fiber.try()
file.close()
// TODO: Want something like rethrow since now the callstack ends here. :(
if (fiber.error != null) Fiber.abort(fiber.error)
return result
}
static read(path) {
return File.open(path) {|file| file.readBytes(file.size) }
}
// TODO: This works for directories too, so putting it on File is kind of
// lame. Consider reorganizing these classes some.
static realPath(path) {
ensurePath_(path)
realPath_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
static size(path) {
ensurePath_(path)
sizePath_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
construct new_(fd) {}
close() {
if (close_(Fiber.current)) return
Scheduler.runNextScheduled_()
}
foreign descriptor
isOpen { descriptor != -1 }
size {
ensureOpen_()
size_(Fiber.current)
return Scheduler.runNextScheduled_()
}
stat {
ensureOpen_()
stat_(Fiber.current)
return Scheduler.runNextScheduled_()
}
readBytes(count) { readBytes(count, 0) }
readBytes(count, offset) {
ensureOpen_()
File.ensureInt_(count, "Count")
File.ensureInt_(offset, "Offset")
readBytes_(count, offset, Fiber.current)
return Scheduler.runNextScheduled_()
}
writeBytes(bytes) { writeBytes(bytes, size) }
writeBytes(bytes, offset) {
ensureOpen_()
if (!(bytes is String)) Fiber.abort("Bytes must be a string.")
File.ensureInt_(offset, "Offset")
writeBytes_(bytes, offset, Fiber.current)
return Scheduler.runNextScheduled_()
}
ensureOpen_() {
if (!isOpen) Fiber.abort("File is not open.")
}
static ensurePath_(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
}
static ensureInt_(value, name) {
if (!(value is Num)) Fiber.abort("%(name) must be an integer.")
if (!value.isInteger) Fiber.abort("%(name) must be an integer.")
if (value < 0) Fiber.abort("%(name) cannot be negative.")
}
foreign static delete_(path, fiber)
foreign static open_(path, flags, fiber)
foreign static realPath_(path, fiber)
foreign static sizePath_(path, fiber)
foreign close_(fiber)
foreign readBytes_(count, offset, fiber)
foreign size_(fiber)
foreign stat_(fiber)
foreign writeBytes_(bytes, offset, fiber)
}
class FileFlags {
// Note: These must be kept in sync with mapFileFlags() in io.c.
static readOnly { 0x01 }
static writeOnly { 0x02 }
static readWrite { 0x04 }
static sync { 0x08 }
static create { 0x10 }
static truncate { 0x20 }
static exclusive { 0x40 }
}
foreign class Stat {
static path(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
path_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
foreign static path_(path, fiber)
foreign blockCount
foreign blockSize
foreign device
foreign group
foreign inode
foreign linkCount
foreign mode
foreign size
foreign specialDevice
foreign user
foreign isFile
foreign isDirectory
// TODO: Other mode checks.
}
class Stdin {
foreign static isRaw
foreign static isRaw=(value)
foreign static isTerminal
static readByte() {
return read_ {
// Peel off the first byte.
var byte = __buffered.bytes[0]
__buffered = __buffered[1..-1]
return byte
}
}
static readLine() {
return read_ {
// TODO: Handle Windows line separators.
var lineSeparator = __buffered.indexOf("\n")
if (lineSeparator == -1) return null
// Split the line at the separator.
var line = __buffered[0...lineSeparator]
__buffered = __buffered[lineSeparator + 1..-1]
return line
}
}
static read_(handleData) {
// See if we're already buffered enough to immediately produce a result.
if (__buffered != null && !__buffered.isEmpty) {
var result = handleData.call()
if (result != null) return result
}
if (__isClosed == true) Fiber.abort("Stdin was closed.")
// Otherwise, we need to wait for input to come in.
__handleData = handleData
// TODO: Error if other fiber is already waiting.
readStart_()
__waitingFiber = Fiber.current
var result = Scheduler.runNextScheduled_()
readStop_()
return result
}
static onData_(data) {
// If data is null, it means stdin just closed.
if (data == null) {
__isClosed = true
readStop_()
if (__buffered != null) {
// TODO: Is this correct for readByte()?
// Emit the last remaining bytes.
var result = __buffered
__buffered = null
__waitingFiber.transfer(result)
} else {
__waitingFiber.transferError("Stdin was closed.")
}
}
// Append to the buffer.
if (__buffered == null) {
__buffered = data
} else {
// TODO: Instead of concatenating strings each time, it's probably faster
// to keep a list of buffers and flatten lazily.
__buffered = __buffered + data
}
// Ask the data handler if we have a complete result now.
var result = __handleData.call()
if (result != null) __waitingFiber.transfer(result)
}
foreign static readStart_()
foreign static readStop_()
}
class Stdout {
foreign static flush()
}

View File

@ -0,0 +1,305 @@
// Generated automatically from src/module/io.wren. Do not edit.
static const char* ioModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Directory {\n"
" // TODO: Copied from File. Figure out good way to share this.\n"
" static ensurePath_(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
" }\n"
"\n"
" static exists(path) {\n"
" ensurePath_(path)\n"
" var stat\n"
" Fiber.new {\n"
" stat = Stat.path(path)\n"
" }.try()\n"
"\n"
" // If we can't stat it, there's nothing there.\n"
" if (stat == null) return false\n"
" return stat.isDirectory\n"
" }\n"
"\n"
" static list(path) {\n"
" ensurePath_(path)\n"
" list_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static list_(path, fiber)\n"
"}\n"
"\n"
"foreign class File {\n"
" static create(path) {\n"
" return openWithFlags(path,\n"
" FileFlags.writeOnly |\n"
" FileFlags.create |\n"
" FileFlags.truncate)\n"
" }\n"
"\n"
" static create(path, fn) {\n"
" return openWithFlags(path,\n"
" FileFlags.writeOnly |\n"
" FileFlags.create |\n"
" FileFlags.truncate, fn)\n"
" }\n"
"\n"
" static delete(path) {\n"
" ensurePath_(path)\n"
" delete_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" static exists(path) {\n"
" ensurePath_(path)\n"
" var stat\n"
" Fiber.new {\n"
" stat = Stat.path(path)\n"
" }.try()\n"
"\n"
" // If we can't stat it, there's nothing there.\n"
" if (stat == null) return false\n"
" return stat.isFile\n"
" }\n"
"\n"
" static open(path) { openWithFlags(path, FileFlags.readOnly) }\n"
"\n"
" static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }\n"
"\n"
" // TODO: Add named parameters and then call this \"open(_,flags:_)\"?\n"
" // TODO: Test.\n"
" static openWithFlags(path, flags) {\n"
" ensurePath_(path)\n"
" ensureInt_(flags, \"Flags\")\n"
" open_(path, flags, Fiber.current)\n"
" var fd = Scheduler.runNextScheduled_()\n"
" return new_(fd)\n"
" }\n"
"\n"
" static openWithFlags(path, flags, fn) {\n"
" var file = openWithFlags(path, flags)\n"
" var fiber = Fiber.new { fn.call(file) }\n"
"\n"
" // Poor man's finally. Can we make this more elegant?\n"
" var result = fiber.try()\n"
" file.close()\n"
"\n"
" // TODO: Want something like rethrow since now the callstack ends here. :(\n"
" if (fiber.error != null) Fiber.abort(fiber.error)\n"
" return result\n"
" }\n"
"\n"
" static read(path) {\n"
" return File.open(path) {|file| file.readBytes(file.size) }\n"
" }\n"
"\n"
" // TODO: This works for directories too, so putting it on File is kind of\n"
" // lame. Consider reorganizing these classes some.\n"
" static realPath(path) {\n"
" ensurePath_(path)\n"
" realPath_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" static size(path) {\n"
" ensurePath_(path)\n"
" sizePath_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" construct new_(fd) {}\n"
"\n"
" close() {\n"
" if (close_(Fiber.current)) return\n"
" Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign descriptor\n"
"\n"
" isOpen { descriptor != -1 }\n"
"\n"
" size {\n"
" ensureOpen_()\n"
" size_(Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" stat {\n"
" ensureOpen_()\n"
" stat_(Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" readBytes(count) { readBytes(count, 0) }\n"
"\n"
" readBytes(count, offset) {\n"
" ensureOpen_()\n"
" File.ensureInt_(count, \"Count\")\n"
" File.ensureInt_(offset, \"Offset\")\n"
"\n"
" readBytes_(count, offset, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" writeBytes(bytes) { writeBytes(bytes, size) }\n"
"\n"
" writeBytes(bytes, offset) {\n"
" ensureOpen_()\n"
" if (!(bytes is String)) Fiber.abort(\"Bytes must be a string.\")\n"
" File.ensureInt_(offset, \"Offset\")\n"
"\n"
" writeBytes_(bytes, offset, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" ensureOpen_() {\n"
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
" }\n"
"\n"
" static ensurePath_(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
" }\n"
"\n"
" static ensureInt_(value, name) {\n"
" if (!(value is Num)) Fiber.abort(\"%(name) must be an integer.\")\n"
" if (!value.isInteger) Fiber.abort(\"%(name) must be an integer.\")\n"
" if (value < 0) Fiber.abort(\"%(name) cannot be negative.\")\n"
" }\n"
"\n"
" foreign static delete_(path, fiber)\n"
" foreign static open_(path, flags, fiber)\n"
" foreign static realPath_(path, fiber)\n"
" foreign static sizePath_(path, fiber)\n"
"\n"
" foreign close_(fiber)\n"
" foreign readBytes_(count, offset, fiber)\n"
" foreign size_(fiber)\n"
" foreign stat_(fiber)\n"
" foreign writeBytes_(bytes, offset, fiber)\n"
"}\n"
"\n"
"class FileFlags {\n"
" // Note: These must be kept in sync with mapFileFlags() in io.c.\n"
"\n"
" static readOnly { 0x01 }\n"
" static writeOnly { 0x02 }\n"
" static readWrite { 0x04 }\n"
" static sync { 0x08 }\n"
" static create { 0x10 }\n"
" static truncate { 0x20 }\n"
" static exclusive { 0x40 }\n"
"}\n"
"\n"
"foreign class Stat {\n"
" static path(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" path_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static path_(path, fiber)\n"
"\n"
" foreign blockCount\n"
" foreign blockSize\n"
" foreign device\n"
" foreign group\n"
" foreign inode\n"
" foreign linkCount\n"
" foreign mode\n"
" foreign size\n"
" foreign specialDevice\n"
" foreign user\n"
"\n"
" foreign isFile\n"
" foreign isDirectory\n"
" // TODO: Other mode checks.\n"
"}\n"
"\n"
"class Stdin {\n"
" foreign static isRaw\n"
" foreign static isRaw=(value)\n"
" foreign static isTerminal\n"
"\n"
" static readByte() {\n"
" return read_ {\n"
" // Peel off the first byte.\n"
" var byte = __buffered.bytes[0]\n"
" __buffered = __buffered[1..-1]\n"
" return byte\n"
" }\n"
" }\n"
"\n"
" static readLine() {\n"
" return read_ {\n"
" // TODO: Handle Windows line separators.\n"
" var lineSeparator = __buffered.indexOf(\"\n\")\n"
" if (lineSeparator == -1) return null\n"
"\n"
" // Split the line at the separator.\n"
" var line = __buffered[0...lineSeparator]\n"
" __buffered = __buffered[lineSeparator + 1..-1]\n"
" return line\n"
" }\n"
" }\n"
"\n"
" static read_(handleData) {\n"
" // See if we're already buffered enough to immediately produce a result.\n"
" if (__buffered != null && !__buffered.isEmpty) {\n"
" var result = handleData.call()\n"
" if (result != null) return result\n"
" }\n"
"\n"
" if (__isClosed == true) Fiber.abort(\"Stdin was closed.\")\n"
"\n"
" // Otherwise, we need to wait for input to come in.\n"
" __handleData = handleData\n"
"\n"
" // TODO: Error if other fiber is already waiting.\n"
" readStart_()\n"
"\n"
" __waitingFiber = Fiber.current\n"
" var result = Scheduler.runNextScheduled_()\n"
"\n"
" readStop_()\n"
" return result\n"
" }\n"
"\n"
" static onData_(data) {\n"
" // If data is null, it means stdin just closed.\n"
" if (data == null) {\n"
" __isClosed = true\n"
" readStop_()\n"
"\n"
" if (__buffered != null) {\n"
" // TODO: Is this correct for readByte()?\n"
" // Emit the last remaining bytes.\n"
" var result = __buffered\n"
" __buffered = null\n"
" __waitingFiber.transfer(result)\n"
" } else {\n"
" __waitingFiber.transferError(\"Stdin was closed.\")\n"
" }\n"
" }\n"
"\n"
" // Append to the buffer.\n"
" if (__buffered == null) {\n"
" __buffered = data\n"
" } else {\n"
" // TODO: Instead of concatenating strings each time, it's probably faster\n"
" // to keep a list of buffers and flatten lazily.\n"
" __buffered = __buffered + data\n"
" }\n"
"\n"
" // Ask the data handler if we have a complete result now.\n"
" var result = __handleData.call()\n"
" if (result != null) __waitingFiber.transfer(result)\n"
" }\n"
"\n"
" foreign static readStart_()\n"
" foreign static readStop_()\n"
"}\n"
"\n"
"class Stdout {\n"
" foreign static flush()\n"
"}\n";

View File

@ -0,0 +1,71 @@
#include "os.h"
#include "wren.h"
#if __APPLE__
#include "TargetConditionals.h"
#endif
int numArgs;
const char** args;
void osSetArguments(int argc, const char* argv[])
{
numArgs = argc;
args = argv;
}
void platformName(WrenVM* vm)
{
wrenEnsureSlots(vm, 1);
#ifdef _WIN32
wrenSetSlotString(vm, 0, "Windows");
#elif __APPLE__
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
wrenSetSlotString(vm, 0, "iOS");
#elif TARGET_OS_MAC
wrenSetSlotString(vm, 0, "OS X");
#else
wrenSetSlotString(vm, 0, "Unknown");
#endif
#elif __linux__
wrenSetSlotString(vm, 0, "Linux");
#elif __unix__
wrenSetSlotString(vm, 0, "Unix");
#elif defined(_POSIX_VERSION)
wrenSetSlotString(vm, 0, "POSIX");
#else
wrenSetSlotString(vm, 0, "Unknown");
#endif
}
void platformIsPosix(WrenVM* vm)
{
wrenEnsureSlots(vm, 1);
#ifdef _WIN32
wrenSetSlotBool(vm, 0, false);
#elif __APPLE__
wrenSetSlotBool(vm, 0, true);
#elif __linux__
wrenSetSlotBool(vm, 0, true);
#elif __unix__
wrenSetSlotBool(vm, 0, true);
#elif defined(_POSIX_VERSION)
wrenSetSlotBool(vm, 0, true);
#else
wrenSetSlotString(vm, 0, false);
#endif
}
void processAllArguments(WrenVM* vm)
{
wrenEnsureSlots(vm, 2);
wrenSetSlotNewList(vm, 0);
for (int i = 0; i < numArgs; i++)
{
wrenSetSlotString(vm, 1, args[i]);
wrenInsertInList(vm, 0, -1, 1);
}
}

View File

@ -0,0 +1,9 @@
#ifndef process_h
#define process_h
#include "wren.h"
// Stores the command line arguments passed to the CLI.
void osSetArguments(int argc, const char* argv[]);
#endif

View File

@ -0,0 +1,13 @@
class Platform {
foreign static isPosix
foreign static name
static isWindows { name == "Windows" }
}
class Process {
// TODO: This will need to be smarter when wren supports CLI options.
static arguments { allArguments[2..-1] }
foreign static allArguments
}

View File

@ -0,0 +1,15 @@
// Generated automatically from src/module/os.wren. Do not edit.
static const char* osModuleSource =
"class Platform {\n"
" foreign static isPosix\n"
" foreign static name\n"
"\n"
" static isWindows { name == \"Windows\" }\n"
"}\n"
"\n"
"class Process {\n"
" // TODO: This will need to be smarter when wren supports CLI options.\n"
" static arguments { allArguments[2..-1] }\n"
"\n"
" foreign static allArguments\n"
"}\n";

View File

@ -0,0 +1,3 @@
#include "repl.h"
#include "wren.h"

View File

@ -0,0 +1,6 @@
#ifndef repl_h
#define repl_h
#include "wren.h"
#endif

View File

@ -0,0 +1,954 @@
import "meta" for Meta
import "io" for Stdin, Stdout
import "os" for Platform
/// Abstract base class for the REPL. Manages the input line and history, but
/// does not render.
class Repl {
construct new() {
_cursor = 0
_line = ""
_history = []
_historyIndex = 0
}
cursor { _cursor }
cursor=(value) { _cursor = value }
line { _line }
line=(value) { _line = value }
run() {
Stdin.isRaw = true
refreshLine(false)
while (true) {
var byte = Stdin.readByte()
if (handleChar(byte)) break
refreshLine(true)
}
}
handleChar(byte) {
if (byte == Chars.ctrlC) {
System.print()
return true
} else if (byte == Chars.ctrlD) {
// If the line is empty, Ctrl_D exits.
if (_line.isEmpty) {
System.print()
return true
}
// Otherwise, it deletes the character after the cursor.
deleteRight()
} else if (byte == Chars.tab) {
var completion = getCompletion()
if (completion != null) {
_line = _line + completion
_cursor = _line.count
}
} else if (byte == Chars.ctrlU) {
// Clear the line.
_line = ""
_cursor = 0
} else if (byte == Chars.ctrlN) {
nextHistory()
} else if (byte == Chars.ctrlP) {
previousHistory()
} else if (byte == Chars.escape) {
var escapeType = Stdin.readByte()
var value = Stdin.readByte()
if (escapeType == Chars.leftBracket) {
// ESC [ sequence.
handleEscapeBracket(value)
} else {
// TODO: Handle ESC 0 sequences.
}
} else if (byte == Chars.carriageReturn) {
executeInput()
} else if (byte == Chars.delete) {
deleteLeft()
} else if (byte >= Chars.space && byte <= Chars.tilde) {
insertChar(byte)
} else if (byte == Chars.ctrlW) { // Handle Ctrl+w
// Delete trailing spaces
while (_cursor != 0 && _line[_cursor - 1] == " ") {
deleteLeft()
}
// Delete until the next space
while (_cursor != 0 && _line[_cursor - 1] != " ") {
deleteLeft()
}
} else {
// TODO: Other shortcuts?
System.print("Unhandled key-code [dec]: %(byte)")
}
return false
}
/// Inserts the character with [byte] value at the current cursor position.
insertChar(byte) {
var char = String.fromCodePoint(byte)
_line = _line[0..._cursor] + char + _line[_cursor..-1]
_cursor = _cursor + 1
}
/// Deletes the character before the cursor, if any.
deleteLeft() {
if (_cursor == 0) return
// Delete the character before the cursor.
_line = _line[0...(_cursor - 1)] + _line[_cursor..-1]
_cursor = _cursor - 1
}
/// Deletes the character after the cursor, if any.
deleteRight() {
if (_cursor == _line.count) return
// Delete the character after the cursor.
_line = _line[0..._cursor] + _line[(_cursor + 1)..-1]
}
handleEscapeBracket(byte) {
if (byte == EscapeBracket.up) {
previousHistory()
} else if (byte == EscapeBracket.down) {
nextHistory()
} else if (byte == EscapeBracket.delete) {
deleteRight()
// Consume extra 126 character generated by delete
Stdin.readByte()
} else if (byte == EscapeBracket.end) {
_cursor = _line.count
} else if (byte == EscapeBracket.home) {
_cursor = 0
}
}
previousHistory() {
if (_historyIndex == 0) return
_historyIndex = _historyIndex - 1
_line = _history[_historyIndex]
_cursor = _line.count
}
nextHistory() {
if (_historyIndex >= _history.count) return
_historyIndex = _historyIndex + 1
if (_historyIndex < _history.count) {
_line = _history[_historyIndex]
_cursor = _line.count
} else {
_line = ""
_cursor = 0
}
}
executeInput() {
// Remove the completion hint.
refreshLine(false)
// Add it to the history (if the line is interesting).
if (_line != "" && (_history.isEmpty || _history[-1] != _line)) {
_history.add(_line)
_historyIndex = _history.count
}
// Reset the current line.
var input = _line
_line = ""
_cursor = 0
System.print()
// Guess if it looks like a statement or expression. If it looks like an
// expression, we try to print the result.
var token = lexFirst(input)
// No code, so do nothing.
if (token == null) return
var isStatement =
token.type == Token.breakKeyword ||
token.type == Token.classKeyword ||
token.type == Token.forKeyword ||
token.type == Token.foreignKeyword ||
token.type == Token.ifKeyword ||
token.type == Token.importKeyword ||
token.type == Token.returnKeyword ||
token.type == Token.varKeyword ||
token.type == Token.whileKeyword
var closure
if (isStatement) {
closure = Meta.compile(input)
} else {
closure = Meta.compileExpression(input)
}
// Stop if there was a compile error.
if (closure == null) return
var fiber = Fiber.new(closure)
var result = fiber.try()
if (fiber.error != null) {
// TODO: Include callstack.
showRuntimeError("Runtime error: %(fiber.error)")
return
}
if (!isStatement) {
showResult(result)
}
}
lex(line, includeWhitespace) {
var lexer = Lexer.new(line)
var tokens = []
while (true) {
var token = lexer.readToken()
if (token.type == Token.eof) break
if (includeWhitespace ||
(token.type != Token.comment && token.type != Token.whitespace)) {
tokens.add(token)
}
}
return tokens
}
lexFirst(line) {
var lexer = Lexer.new(line)
while (true) {
var token = lexer.readToken()
if (token.type == Token.eof) return null
if (token.type != Token.comment && token.type != Token.whitespace) {
return token
}
}
}
/// Gets the best possible auto-completion for the current line, or null if
/// there is none. The completion is the remaining string to append to the
/// line, not the entire completed line.
getCompletion() {
if (_line.isEmpty) return null
// Only complete if the cursor is at the end.
if (_cursor != _line.count) return null
for (name in Meta.getModuleVariables("repl")) {
// TODO: Also allow completion if the line ends with an identifier but
// has other stuff before it.
if (name.startsWith(_line)) {
return name[_line.count..-1]
}
}
}
}
/// A reduced functionality REPL that doesn't use ANSI escape sequences.
class SimpleRepl is Repl {
construct new() {
super()
_erase = ""
}
refreshLine(showCompletion) {
// A carriage return just moves the cursor to the beginning of the line.
// We have to erase it manually. Since we can't use ANSI escapes, and we
// don't know how wide the terminal is, erase the longest line we've seen
// so far.
if (line.count > _erase.count) _erase = " " * line.count
System.write("\r %(_erase)")
// Show the prompt at the beginning of the line.
System.write("\r> ")
// Write the line.
System.write(line)
Stdout.flush()
}
showResult(value) {
// TODO: Syntax color based on type? It might be nice to distinguish
// between string results versus stringified results. Otherwise, the
// user can't tell the difference between `true` and "true".
System.print(value)
}
showRuntimeError(message) {
System.print(message)
}
}
class AnsiRepl is Repl {
construct new() {
super()
}
handleChar(byte) {
if (byte == Chars.ctrlA) {
cursor = 0
} else if (byte == Chars.ctrlB) {
cursorLeft()
} else if (byte == Chars.ctrlE) {
cursor = line.count
} else if (byte == Chars.ctrlF) {
cursorRight()
} else if (byte == Chars.ctrlK) {
// Delete everything after the cursor.
line = line[0...cursor]
} else if (byte == Chars.ctrlL) {
// Clear the screen.
System.write("\x1b[2J")
// Move cursor to top left.
System.write("\x1b[H")
} else {
// TODO: Ctrl-T to swap chars.
// TODO: ESC H and F to move to beginning and end of line. (Both ESC
// [ and ESC 0 sequences?)
// TODO: Ctrl-W delete previous word.
return super.handleChar(byte)
}
return false
}
handleEscapeBracket(byte) {
if (byte == EscapeBracket.left) {
cursorLeft()
} else if (byte == EscapeBracket.right) {
cursorRight()
}
super.handleEscapeBracket(byte)
}
/// Move the cursor left one character.
cursorLeft() {
if (cursor > 0) cursor = cursor - 1
}
/// Move the cursor right one character.
cursorRight() {
// TODO: Take into account multi-byte characters?
if (cursor < line.count) cursor = cursor + 1
}
refreshLine(showCompletion) {
// Erase the whole line.
System.write("\x1b[2K")
// Show the prompt at the beginning of the line.
System.write(Color.gray)
System.write("\r> ")
System.write(Color.none)
// Syntax highlight the line.
for (token in lex(line, true)) {
if (token.type == Token.eof) break
System.write(TOKEN_COLORS[token.type])
System.write(token.text)
System.write(Color.none)
}
if (showCompletion) {
var completion = getCompletion()
if (completion != null) {
System.write("%(Color.gray)%(completion)%(Color.none)")
}
}
// Position the cursor.
System.write("\r\x1b[%(2 + cursor)C")
Stdout.flush()
}
showResult(value) {
// TODO: Syntax color based on type? It might be nice to distinguish
// between string results versus stringified results. Otherwise, the
// user can't tell the difference between `true` and "true".
System.print("%(Color.brightWhite)%(value)%(Color.none)")
}
showRuntimeError(message) {
System.print("%(Color.red)%(message)%(Color.none)")
// TODO: Print entire stack.
}
}
/// ANSI color escape sequences.
class Color {
static none { "\x1b[0m" }
static black { "\x1b[30m" }
static red { "\x1b[31m" }
static green { "\x1b[32m" }
static yellow { "\x1b[33m" }
static blue { "\x1b[34m" }
static magenta { "\x1b[35m" }
static cyan { "\x1b[36m" }
static white { "\x1b[37m" }
static gray { "\x1b[30;1m" }
static pink { "\x1b[31;1m" }
static brightWhite { "\x1b[37;1m" }
}
/// Utilities for working with characters.
class Chars {
static ctrlA { 0x01 }
static ctrlB { 0x02 }
static ctrlC { 0x03 }
static ctrlD { 0x04 }
static ctrlE { 0x05 }
static ctrlF { 0x06 }
static tab { 0x09 }
static lineFeed { 0x0a }
static ctrlK { 0x0b }
static ctrlL { 0x0c }
static carriageReturn { 0x0d }
static ctrlN { 0x0e }
static ctrlP { 0x10 }
static ctrlU { 0x15 }
static ctrlW { 0x17 }
static escape { 0x1b }
static space { 0x20 }
static bang { 0x21 }
static quote { 0x22 }
static percent { 0x25 }
static amp { 0x26 }
static leftParen { 0x28 }
static rightParen { 0x29 }
static star { 0x2a }
static plus { 0x2b }
static comma { 0x2c }
static minus { 0x2d }
static dot { 0x2e }
static slash { 0x2f }
static zero { 0x30 }
static nine { 0x39 }
static colon { 0x3a }
static less { 0x3c }
static equal { 0x3d }
static greater { 0x3e }
static question { 0x3f }
static upperA { 0x41 }
static upperF { 0x46 }
static upperZ { 0x5a }
static leftBracket { 0x5b }
static backslash { 0x5c }
static rightBracket { 0x5d }
static caret { 0x5e }
static underscore { 0x5f }
static lowerA { 0x61 }
static lowerF { 0x66 }
static lowerX { 0x78 }
static lowerZ { 0x7a }
static leftBrace { 0x7b }
static pipe { 0x7c }
static rightBrace { 0x7d }
static tilde { 0x7e }
static delete { 0x7f }
static isAlpha(c) {
return c >= lowerA && c <= lowerZ ||
c >= upperA && c <= upperZ ||
c == underscore
}
static isDigit(c) { c >= zero && c <= nine }
static isAlphaNumeric(c) { isAlpha(c) || isDigit(c) }
static isHexDigit(c) {
return c >= zero && c <= nine ||
c >= lowerA && c <= lowerF ||
c >= upperA && c <= upperF
}
static isLowerAlpha(c) { c >= lowerA && c <= lowerZ }
static isWhitespace(c) { c == space || c == tab || c == carriageReturn }
}
class EscapeBracket {
static delete { 0x33 }
static up { 0x41 }
static down { 0x42 }
static right { 0x43 }
static left { 0x44 }
static end { 0x46 }
static home { 0x48 }
}
class Token {
// Punctuators.
static leftParen { "leftParen" }
static rightParen { "rightParen" }
static leftBracket { "leftBracket" }
static rightBracket { "rightBracket" }
static leftBrace { "leftBrace" }
static rightBrace { "rightBrace" }
static colon { "colon" }
static dot { "dot" }
static dotDot { "dotDot" }
static dotDotDot { "dotDotDot" }
static comma { "comma" }
static star { "star" }
static slash { "slash" }
static percent { "percent" }
static plus { "plus" }
static minus { "minus" }
static pipe { "pipe" }
static pipePipe { "pipePipe" }
static caret { "caret" }
static amp { "amp" }
static ampAmp { "ampAmp" }
static question { "question" }
static bang { "bang" }
static tilde { "tilde" }
static equal { "equal" }
static less { "less" }
static lessEqual { "lessEqual" }
static lessLess { "lessLess" }
static greater { "greater" }
static greaterEqual { "greaterEqual" }
static greaterGreater { "greaterGreater" }
static equalEqual { "equalEqual" }
static bangEqual { "bangEqual" }
// Keywords.
static breakKeyword { "break" }
static classKeyword { "class" }
static constructKeyword { "construct" }
static elseKeyword { "else" }
static falseKeyword { "false" }
static forKeyword { "for" }
static foreignKeyword { "foreign" }
static ifKeyword { "if" }
static importKeyword { "import" }
static inKeyword { "in" }
static isKeyword { "is" }
static nullKeyword { "null" }
static returnKeyword { "return" }
static staticKeyword { "static" }
static superKeyword { "super" }
static thisKeyword { "this" }
static trueKeyword { "true" }
static varKeyword { "var" }
static whileKeyword { "while" }
static field { "field" }
static name { "name" }
static number { "number" }
static string { "string" }
static interpolation { "interpolation" }
static comment { "comment" }
static whitespace { "whitespace" }
static line { "line" }
static error { "error" }
static eof { "eof" }
construct new(source, type, start, length) {
_source = source
_type = type
_start = start
_length = length
}
type { _type }
text { _source[_start...(_start + _length)] }
start { _start }
length { _length }
toString { text }
}
var KEYWORDS = {
"break": Token.breakKeyword,
"class": Token.classKeyword,
"construct": Token.constructKeyword,
"else": Token.elseKeyword,
"false": Token.falseKeyword,
"for": Token.forKeyword,
"foreign": Token.foreignKeyword,
"if": Token.ifKeyword,
"import": Token.importKeyword,
"in": Token.inKeyword,
"is": Token.isKeyword,
"null": Token.nullKeyword,
"return": Token.returnKeyword,
"static": Token.staticKeyword,
"super": Token.superKeyword,
"this": Token.thisKeyword,
"true": Token.trueKeyword,
"var": Token.varKeyword,
"while": Token.whileKeyword
}
var TOKEN_COLORS = {
Token.leftParen: Color.gray,
Token.rightParen: Color.gray,
Token.leftBracket: Color.gray,
Token.rightBracket: Color.gray,
Token.leftBrace: Color.gray,
Token.rightBrace: Color.gray,
Token.colon: Color.gray,
Token.dot: Color.gray,
Token.dotDot: Color.none,
Token.dotDotDot: Color.none,
Token.comma: Color.gray,
Token.star: Color.none,
Token.slash: Color.none,
Token.percent: Color.none,
Token.plus: Color.none,
Token.minus: Color.none,
Token.pipe: Color.none,
Token.pipePipe: Color.none,
Token.caret: Color.none,
Token.amp: Color.none,
Token.ampAmp: Color.none,
Token.question: Color.none,
Token.bang: Color.none,
Token.tilde: Color.none,
Token.equal: Color.none,
Token.less: Color.none,
Token.lessEqual: Color.none,
Token.lessLess: Color.none,
Token.greater: Color.none,
Token.greaterEqual: Color.none,
Token.greaterGreater: Color.none,
Token.equalEqual: Color.none,
Token.bangEqual: Color.none,
// Keywords.
Token.breakKeyword: Color.cyan,
Token.classKeyword: Color.cyan,
Token.constructKeyword: Color.cyan,
Token.elseKeyword: Color.cyan,
Token.falseKeyword: Color.cyan,
Token.forKeyword: Color.cyan,
Token.foreignKeyword: Color.cyan,
Token.ifKeyword: Color.cyan,
Token.importKeyword: Color.cyan,
Token.inKeyword: Color.cyan,
Token.isKeyword: Color.cyan,
Token.nullKeyword: Color.cyan,
Token.returnKeyword: Color.cyan,
Token.staticKeyword: Color.cyan,
Token.superKeyword: Color.cyan,
Token.thisKeyword: Color.cyan,
Token.trueKeyword: Color.cyan,
Token.varKeyword: Color.cyan,
Token.whileKeyword: Color.cyan,
Token.field: Color.none,
Token.name: Color.none,
Token.number: Color.magenta,
Token.string: Color.yellow,
Token.interpolation: Color.yellow,
Token.comment: Color.gray,
Token.whitespace: Color.none,
Token.line: Color.none,
Token.error: Color.red,
Token.eof: Color.none,
}
// Data table for tokens that are tokenized using maximal munch.
//
// The key is the character that starts the token or tokens. After that is a
// list of token types and characters. As long as the next character is matched,
// the type will update to the type after that character.
var PUNCTUATORS = {
Chars.leftParen: [Token.leftParen],
Chars.rightParen: [Token.rightParen],
Chars.leftBracket: [Token.leftBracket],
Chars.rightBracket: [Token.rightBracket],
Chars.leftBrace: [Token.leftBrace],
Chars.rightBrace: [Token.rightBrace],
Chars.colon: [Token.colon],
Chars.comma: [Token.comma],
Chars.star: [Token.star],
Chars.percent: [Token.percent],
Chars.plus: [Token.plus],
Chars.minus: [Token.minus],
Chars.tilde: [Token.tilde],
Chars.caret: [Token.caret],
Chars.question: [Token.question],
Chars.lineFeed: [Token.line],
Chars.pipe: [Token.pipe, Chars.pipe, Token.pipePipe],
Chars.amp: [Token.amp, Chars.amp, Token.ampAmp],
Chars.bang: [Token.bang, Chars.equal, Token.bangEqual],
Chars.equal: [Token.equal, Chars.equal, Token.equalEqual],
Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot]
}
/// Tokenizes a string of input. This lexer differs from most in that it
/// silently ignores errors from incomplete input, like a string literal with
/// no closing quote. That's because this is intended to be run on a line of
/// input while the user is still typing it.
class Lexer {
construct new(source) {
_source = source
// Due to the magic of UTF-8, we can safely treat Wren source as a series
// of bytes, since the only code points that are meaningful to Wren fit in
// ASCII. The only place where non-ASCII code points can occur is inside
// string literals and comments and the lexer safely treats those as opaque
// bytes.
_bytes = source.bytes
_start = 0
_current = 0
// The stack of ongoing interpolated strings. Each element in the list is
// a single level of interpolation nesting. The value of the element is the
// number of unbalanced "(" still remaining to be closed.
_interpolations = []
}
readToken() {
if (_current >= _bytes.count) return makeToken(Token.eof)
_start = _current
var c = _bytes[_current]
advance()
if (!_interpolations.isEmpty) {
if (c == Chars.leftParen) {
_interpolations[-1] = _interpolations[-1] + 1
} else if (c == Chars.rightParen) {
_interpolations[-1] = _interpolations[-1] - 1
// The last ")" in an interpolated expression ends the expression and
// resumes the string.
if (_interpolations[-1] == 0) {
// This is the final ")", so the interpolation expression has ended.
// This ")" now begins the next section of the template string.
_interpolations.removeAt(-1)
return readString()
}
}
}
if (PUNCTUATORS.containsKey(c)) {
var punctuator = PUNCTUATORS[c]
var type = punctuator[0]
var i = 1
while (i < punctuator.count) {
if (!match(punctuator[i])) break
type = punctuator[i + 1]
i = i + 2
}
return makeToken(type)
}
// Handle "<", "<<", and "<=".
if (c == Chars.less) {
if (match(Chars.less)) return makeToken(Token.lessLess)
if (match(Chars.equal)) return makeToken(Token.lessEqual)
return makeToken(Token.less)
}
// Handle ">", ">>", and ">=".
if (c == Chars.greater) {
if (match(Chars.greater)) return makeToken(Token.greaterGreater)
if (match(Chars.equal)) return makeToken(Token.greaterEqual)
return makeToken(Token.greater)
}
// Handle "/", "//", and "/*".
if (c == Chars.slash) {
if (match(Chars.slash)) return readLineComment()
if (match(Chars.star)) return readBlockComment()
return makeToken(Token.slash)
}
if (c == Chars.underscore) return readField()
if (c == Chars.quote) return readString()
if (c == Chars.zero && peek() == Chars.lowerX) return readHexNumber()
if (Chars.isWhitespace(c)) return readWhitespace()
if (Chars.isDigit(c)) return readNumber()
if (Chars.isAlpha(c)) return readName()
return makeToken(Token.error)
}
// Reads a line comment until the end of the line is reached.
readLineComment() {
// A line comment stops at the newline since newlines are significant.
while (peek() != Chars.lineFeed && !isAtEnd) {
advance()
}
return makeToken(Token.comment)
}
readBlockComment() {
// Block comments can nest.
var nesting = 1
while (nesting > 0) {
// TODO: Report error.
if (isAtEnd) break
if (peek() == Chars.slash && peek(1) == Chars.star) {
advance()
advance()
nesting = nesting + 1
} else if (peek() == Chars.star && peek(1) == Chars.slash) {
advance()
advance()
nesting = nesting - 1
if (nesting == 0) break
} else {
advance()
}
}
return makeToken(Token.comment)
}
// Reads a static or instance field.
readField() {
var type = Token.field
// Read the rest of the name.
while (match {|c| Chars.isAlphaNumeric(c) }) {}
return makeToken(type)
}
// Reads a string literal.
readString() {
var type = Token.string
while (!isAtEnd) {
var c = _bytes[_current]
advance()
if (c == Chars.backslash) {
// TODO: Process specific escapes and validate them.
if (!isAtEnd) advance()
} else if (c == Chars.percent) {
// Consume the '('.
if (!isAtEnd) advance()
// TODO: Handle missing '('.
_interpolations.add(1)
type = Token.interpolation
break
} else if (c == Chars.quote) {
break
}
}
return makeToken(type)
}
// Reads a number literal.
readHexNumber() {
// Skip past the `x`.
advance()
// Read the rest of the number.
while (match {|c| Chars.isHexDigit(c) }) {}
return makeToken(Token.number)
}
// Reads a series of whitespace characters.
readWhitespace() {
// Read the rest of the whitespace.
while (match {|c| Chars.isWhitespace(c) }) {}
return makeToken(Token.whitespace)
}
// Reads a number literal.
readNumber() {
// Read the rest of the number.
while (match {|c| Chars.isDigit(c) }) {}
// TODO: Floating point, scientific.
return makeToken(Token.number)
}
// Reads an identifier or keyword token.
readName() {
// Read the rest of the name.
while (match {|c| Chars.isAlphaNumeric(c) }) {}
var text = _source[_start..._current]
var type = Token.name
if (KEYWORDS.containsKey(text)) {
type = KEYWORDS[text]
}
return Token.new(_source, type, _start, _current - _start)
}
// Returns `true` if we have scanned all characters.
isAtEnd { _current >= _bytes.count }
// Advances past the current character.
advance() {
_current = _current + 1
}
// Returns the byte value of the current character.
peek() { peek(0) }
// Returns the byte value of the character [n] bytes past the current
// character.
peek(n) {
if (_current + n >= _bytes.count) return -1
return _bytes[_current + n]
}
// Consumes the current character if it matches [condition], which can be a
// numeric code point value or a function that takes a code point and returns
// `true` if the code point matches.
match(condition) {
if (isAtEnd) return false
var c = _bytes[_current]
if (condition is Fn) {
if (!condition.call(c)) return false
} else if (c != condition) {
return false
}
advance()
return true
}
// Creates a token of [type] from the current character range.
makeToken(type) { Token.new(_source, type, _start, _current - _start) }
}
// Fire up the REPL. We use ANSI when talking to a POSIX TTY.
if (Platform.isPosix && Stdin.isTerminal) {
AnsiRepl.new().run()
} else {
// ANSI escape sequences probably aren't supported, so degrade.
SimpleRepl.new().run()
}

View File

@ -0,0 +1,956 @@
// Generated automatically from src/module/repl.wren. Do not edit.
static const char* replModuleSource =
"import \"meta\" for Meta\n"
"import \"io\" for Stdin, Stdout\n"
"import \"os\" for Platform\n"
"\n"
"/// Abstract base class for the REPL. Manages the input line and history, but\n"
"/// does not render.\n"
"class Repl {\n"
" construct new() {\n"
" _cursor = 0\n"
" _line = \"\"\n"
"\n"
" _history = []\n"
" _historyIndex = 0\n"
" }\n"
"\n"
" cursor { _cursor }\n"
" cursor=(value) { _cursor = value }\n"
" line { _line }\n"
" line=(value) { _line = value }\n"
"\n"
" run() {\n"
" Stdin.isRaw = true\n"
" refreshLine(false)\n"
"\n"
" while (true) {\n"
" var byte = Stdin.readByte()\n"
" if (handleChar(byte)) break\n"
" refreshLine(true)\n"
" }\n"
" }\n"
"\n"
" handleChar(byte) {\n"
" if (byte == Chars.ctrlC) {\n"
" System.print()\n"
" return true\n"
" } else if (byte == Chars.ctrlD) {\n"
" // If the line is empty, Ctrl_D exits.\n"
" if (_line.isEmpty) {\n"
" System.print()\n"
" return true\n"
" }\n"
"\n"
" // Otherwise, it deletes the character after the cursor.\n"
" deleteRight()\n"
" } else if (byte == Chars.tab) {\n"
" var completion = getCompletion()\n"
" if (completion != null) {\n"
" _line = _line + completion\n"
" _cursor = _line.count\n"
" }\n"
" } else if (byte == Chars.ctrlU) {\n"
" // Clear the line.\n"
" _line = \"\"\n"
" _cursor = 0\n"
" } else if (byte == Chars.ctrlN) {\n"
" nextHistory()\n"
" } else if (byte == Chars.ctrlP) {\n"
" previousHistory()\n"
" } else if (byte == Chars.escape) {\n"
" var escapeType = Stdin.readByte()\n"
" var value = Stdin.readByte()\n"
" if (escapeType == Chars.leftBracket) {\n"
" // ESC [ sequence.\n"
" handleEscapeBracket(value)\n"
" } else {\n"
" // TODO: Handle ESC 0 sequences.\n"
" }\n"
" } else if (byte == Chars.carriageReturn) {\n"
" executeInput()\n"
" } else if (byte == Chars.delete) {\n"
" deleteLeft()\n"
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
" insertChar(byte)\n"
" } else if (byte == Chars.ctrlW) { // Handle Ctrl+w\n"
" // Delete trailing spaces\n"
" while (_cursor != 0 && _line[_cursor - 1] == \" \") {\n"
" deleteLeft()\n"
" }\n"
" // Delete until the next space\n"
" while (_cursor != 0 && _line[_cursor - 1] != \" \") {\n"
" deleteLeft()\n"
" }\n"
" } else {\n"
" // TODO: Other shortcuts?\n"
" System.print(\"Unhandled key-code [dec]: %(byte)\")\n"
" }\n"
"\n"
" return false\n"
" }\n"
"\n"
" /// Inserts the character with [byte] value at the current cursor position.\n"
" insertChar(byte) {\n"
" var char = String.fromCodePoint(byte)\n"
" _line = _line[0..._cursor] + char + _line[_cursor..-1]\n"
" _cursor = _cursor + 1\n"
" }\n"
"\n"
" /// Deletes the character before the cursor, if any.\n"
" deleteLeft() {\n"
" if (_cursor == 0) return\n"
"\n"
" // Delete the character before the cursor.\n"
" _line = _line[0...(_cursor - 1)] + _line[_cursor..-1]\n"
" _cursor = _cursor - 1\n"
" }\n"
"\n"
" /// Deletes the character after the cursor, if any.\n"
" deleteRight() {\n"
" if (_cursor == _line.count) return\n"
"\n"
" // Delete the character after the cursor.\n"
" _line = _line[0..._cursor] + _line[(_cursor + 1)..-1]\n"
" }\n"
"\n"
" handleEscapeBracket(byte) {\n"
" if (byte == EscapeBracket.up) {\n"
" previousHistory()\n"
" } else if (byte == EscapeBracket.down) {\n"
" nextHistory()\n"
" } else if (byte == EscapeBracket.delete) {\n"
" deleteRight()\n"
" // Consume extra 126 character generated by delete\n"
" Stdin.readByte()\n"
" } else if (byte == EscapeBracket.end) {\n"
" _cursor = _line.count\n"
" } else if (byte == EscapeBracket.home) {\n"
" _cursor = 0\n"
" }\n"
" }\n"
"\n"
" previousHistory() {\n"
" if (_historyIndex == 0) return\n"
"\n"
" _historyIndex = _historyIndex - 1\n"
" _line = _history[_historyIndex]\n"
" _cursor = _line.count\n"
" }\n"
"\n"
" nextHistory() {\n"
" if (_historyIndex >= _history.count) return\n"
"\n"
" _historyIndex = _historyIndex + 1\n"
" if (_historyIndex < _history.count) {\n"
" _line = _history[_historyIndex]\n"
" _cursor = _line.count\n"
" } else {\n"
" _line = \"\"\n"
" _cursor = 0\n"
" }\n"
" }\n"
"\n"
" executeInput() {\n"
" // Remove the completion hint.\n"
" refreshLine(false)\n"
"\n"
" // Add it to the history (if the line is interesting).\n"
" if (_line != \"\" && (_history.isEmpty || _history[-1] != _line)) {\n"
" _history.add(_line)\n"
" _historyIndex = _history.count\n"
" }\n"
"\n"
" // Reset the current line.\n"
" var input = _line\n"
" _line = \"\"\n"
" _cursor = 0\n"
"\n"
" System.print()\n"
"\n"
" // Guess if it looks like a statement or expression. If it looks like an\n"
" // expression, we try to print the result.\n"
" var token = lexFirst(input)\n"
"\n"
" // No code, so do nothing.\n"
" if (token == null) return\n"
"\n"
" var isStatement =\n"
" token.type == Token.breakKeyword ||\n"
" token.type == Token.classKeyword ||\n"
" token.type == Token.forKeyword ||\n"
" token.type == Token.foreignKeyword ||\n"
" token.type == Token.ifKeyword ||\n"
" token.type == Token.importKeyword ||\n"
" token.type == Token.returnKeyword ||\n"
" token.type == Token.varKeyword ||\n"
" token.type == Token.whileKeyword\n"
"\n"
" var closure\n"
" if (isStatement) {\n"
" closure = Meta.compile(input)\n"
" } else {\n"
" closure = Meta.compileExpression(input)\n"
" }\n"
"\n"
" // Stop if there was a compile error.\n"
" if (closure == null) return\n"
"\n"
" var fiber = Fiber.new(closure)\n"
"\n"
" var result = fiber.try()\n"
" if (fiber.error != null) {\n"
" // TODO: Include callstack.\n"
" showRuntimeError(\"Runtime error: %(fiber.error)\")\n"
" return\n"
" }\n"
"\n"
" if (!isStatement) {\n"
" showResult(result)\n"
" }\n"
" }\n"
"\n"
" lex(line, includeWhitespace) {\n"
" var lexer = Lexer.new(line)\n"
" var tokens = []\n"
" while (true) {\n"
" var token = lexer.readToken()\n"
" if (token.type == Token.eof) break\n"
"\n"
" if (includeWhitespace ||\n"
" (token.type != Token.comment && token.type != Token.whitespace)) {\n"
" tokens.add(token)\n"
" }\n"
" }\n"
"\n"
" return tokens\n"
" }\n"
"\n"
" lexFirst(line) {\n"
" var lexer = Lexer.new(line)\n"
" while (true) {\n"
" var token = lexer.readToken()\n"
" if (token.type == Token.eof) return null\n"
"\n"
" if (token.type != Token.comment && token.type != Token.whitespace) {\n"
" return token\n"
" }\n"
" }\n"
" }\n"
"\n"
" /// Gets the best possible auto-completion for the current line, or null if\n"
" /// there is none. The completion is the remaining string to append to the\n"
" /// line, not the entire completed line.\n"
" getCompletion() {\n"
" if (_line.isEmpty) return null\n"
"\n"
" // Only complete if the cursor is at the end.\n"
" if (_cursor != _line.count) return null\n"
"\n"
" for (name in Meta.getModuleVariables(\"repl\")) {\n"
" // TODO: Also allow completion if the line ends with an identifier but\n"
" // has other stuff before it.\n"
" if (name.startsWith(_line)) {\n"
" return name[_line.count..-1]\n"
" }\n"
" }\n"
" }\n"
"}\n"
"\n"
"/// A reduced functionality REPL that doesn't use ANSI escape sequences.\n"
"class SimpleRepl is Repl {\n"
" construct new() {\n"
" super()\n"
" _erase = \"\"\n"
" }\n"
"\n"
" refreshLine(showCompletion) {\n"
" // A carriage return just moves the cursor to the beginning of the line.\n"
" // We have to erase it manually. Since we can't use ANSI escapes, and we\n"
" // don't know how wide the terminal is, erase the longest line we've seen\n"
" // so far.\n"
" if (line.count > _erase.count) _erase = \" \" * line.count\n"
" System.write(\"\r %(_erase)\")\n"
"\n"
" // Show the prompt at the beginning of the line.\n"
" System.write(\"\r> \")\n"
"\n"
" // Write the line.\n"
" System.write(line)\n"
" Stdout.flush()\n"
" }\n"
"\n"
" showResult(value) {\n"
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
" // between string results versus stringified results. Otherwise, the\n"
" // user can't tell the difference between `true` and \"true\".\n"
" System.print(value)\n"
" }\n"
"\n"
" showRuntimeError(message) {\n"
" System.print(message)\n"
" }\n"
"}\n"
"\n"
"class AnsiRepl is Repl {\n"
" construct new() {\n"
" super()\n"
" }\n"
"\n"
" handleChar(byte) {\n"
" if (byte == Chars.ctrlA) {\n"
" cursor = 0\n"
" } else if (byte == Chars.ctrlB) {\n"
" cursorLeft()\n"
" } else if (byte == Chars.ctrlE) {\n"
" cursor = line.count\n"
" } else if (byte == Chars.ctrlF) {\n"
" cursorRight()\n"
" } else if (byte == Chars.ctrlK) {\n"
" // Delete everything after the cursor.\n"
" line = line[0...cursor]\n"
" } else if (byte == Chars.ctrlL) {\n"
" // Clear the screen.\n"
" System.write(\"\x1b[2J\")\n"
" // Move cursor to top left.\n"
" System.write(\"\x1b[H\")\n"
" } else {\n"
" // TODO: Ctrl-T to swap chars.\n"
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
" // [ and ESC 0 sequences?)\n"
" // TODO: Ctrl-W delete previous word.\n"
" return super.handleChar(byte)\n"
" }\n"
"\n"
" return false\n"
" }\n"
"\n"
" handleEscapeBracket(byte) {\n"
" if (byte == EscapeBracket.left) {\n"
" cursorLeft()\n"
" } else if (byte == EscapeBracket.right) {\n"
" cursorRight()\n"
" }\n"
"\n"
" super.handleEscapeBracket(byte)\n"
" }\n"
"\n"
" /// Move the cursor left one character.\n"
" cursorLeft() {\n"
" if (cursor > 0) cursor = cursor - 1\n"
" }\n"
"\n"
" /// Move the cursor right one character.\n"
" cursorRight() {\n"
" // TODO: Take into account multi-byte characters?\n"
" if (cursor < line.count) cursor = cursor + 1\n"
" }\n"
"\n"
" refreshLine(showCompletion) {\n"
" // Erase the whole line.\n"
" System.write(\"\x1b[2K\")\n"
"\n"
" // Show the prompt at the beginning of the line.\n"
" System.write(Color.gray)\n"
" System.write(\"\r> \")\n"
" System.write(Color.none)\n"
"\n"
" // Syntax highlight the line.\n"
" for (token in lex(line, true)) {\n"
" if (token.type == Token.eof) break\n"
"\n"
" System.write(TOKEN_COLORS[token.type])\n"
" System.write(token.text)\n"
" System.write(Color.none)\n"
" }\n"
"\n"
" if (showCompletion) {\n"
" var completion = getCompletion()\n"
" if (completion != null) {\n"
" System.write(\"%(Color.gray)%(completion)%(Color.none)\")\n"
" }\n"
" }\n"
"\n"
" // Position the cursor.\n"
" System.write(\"\r\x1b[%(2 + cursor)C\")\n"
" Stdout.flush()\n"
" }\n"
"\n"
" showResult(value) {\n"
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
" // between string results versus stringified results. Otherwise, the\n"
" // user can't tell the difference between `true` and \"true\".\n"
" System.print(\"%(Color.brightWhite)%(value)%(Color.none)\")\n"
" }\n"
"\n"
" showRuntimeError(message) {\n"
" System.print(\"%(Color.red)%(message)%(Color.none)\")\n"
" // TODO: Print entire stack.\n"
" }\n"
"}\n"
"\n"
"/// ANSI color escape sequences.\n"
"class Color {\n"
" static none { \"\x1b[0m\" }\n"
" static black { \"\x1b[30m\" }\n"
" static red { \"\x1b[31m\" }\n"
" static green { \"\x1b[32m\" }\n"
" static yellow { \"\x1b[33m\" }\n"
" static blue { \"\x1b[34m\" }\n"
" static magenta { \"\x1b[35m\" }\n"
" static cyan { \"\x1b[36m\" }\n"
" static white { \"\x1b[37m\" }\n"
"\n"
" static gray { \"\x1b[30;1m\" }\n"
" static pink { \"\x1b[31;1m\" }\n"
" static brightWhite { \"\x1b[37;1m\" }\n"
"}\n"
"\n"
"/// Utilities for working with characters.\n"
"class Chars {\n"
" static ctrlA { 0x01 }\n"
" static ctrlB { 0x02 }\n"
" static ctrlC { 0x03 }\n"
" static ctrlD { 0x04 }\n"
" static ctrlE { 0x05 }\n"
" static ctrlF { 0x06 }\n"
" static tab { 0x09 }\n"
" static lineFeed { 0x0a }\n"
" static ctrlK { 0x0b }\n"
" static ctrlL { 0x0c }\n"
" static carriageReturn { 0x0d }\n"
" static ctrlN { 0x0e }\n"
" static ctrlP { 0x10 }\n"
" static ctrlU { 0x15 }\n"
" static ctrlW { 0x17 }\n"
" static escape { 0x1b }\n"
" static space { 0x20 }\n"
" static bang { 0x21 }\n"
" static quote { 0x22 }\n"
" static percent { 0x25 }\n"
" static amp { 0x26 }\n"
" static leftParen { 0x28 }\n"
" static rightParen { 0x29 }\n"
" static star { 0x2a }\n"
" static plus { 0x2b }\n"
" static comma { 0x2c }\n"
" static minus { 0x2d }\n"
" static dot { 0x2e }\n"
" static slash { 0x2f }\n"
"\n"
" static zero { 0x30 }\n"
" static nine { 0x39 }\n"
"\n"
" static colon { 0x3a }\n"
" static less { 0x3c }\n"
" static equal { 0x3d }\n"
" static greater { 0x3e }\n"
" static question { 0x3f }\n"
"\n"
" static upperA { 0x41 }\n"
" static upperF { 0x46 }\n"
" static upperZ { 0x5a }\n"
"\n"
" static leftBracket { 0x5b }\n"
" static backslash { 0x5c }\n"
" static rightBracket { 0x5d }\n"
" static caret { 0x5e }\n"
" static underscore { 0x5f }\n"
"\n"
" static lowerA { 0x61 }\n"
" static lowerF { 0x66 }\n"
" static lowerX { 0x78 }\n"
" static lowerZ { 0x7a }\n"
"\n"
" static leftBrace { 0x7b }\n"
" static pipe { 0x7c }\n"
" static rightBrace { 0x7d }\n"
" static tilde { 0x7e }\n"
" static delete { 0x7f }\n"
"\n"
" static isAlpha(c) {\n"
" return c >= lowerA && c <= lowerZ ||\n"
" c >= upperA && c <= upperZ ||\n"
" c == underscore\n"
" }\n"
"\n"
" static isDigit(c) { c >= zero && c <= nine }\n"
"\n"
" static isAlphaNumeric(c) { isAlpha(c) || isDigit(c) }\n"
"\n"
" static isHexDigit(c) {\n"
" return c >= zero && c <= nine ||\n"
" c >= lowerA && c <= lowerF ||\n"
" c >= upperA && c <= upperF\n"
" }\n"
"\n"
" static isLowerAlpha(c) { c >= lowerA && c <= lowerZ }\n"
"\n"
" static isWhitespace(c) { c == space || c == tab || c == carriageReturn }\n"
"}\n"
"\n"
"class EscapeBracket {\n"
" static delete { 0x33 }\n"
" static up { 0x41 }\n"
" static down { 0x42 }\n"
" static right { 0x43 }\n"
" static left { 0x44 }\n"
" static end { 0x46 }\n"
" static home { 0x48 }\n"
"}\n"
"\n"
"class Token {\n"
" // Punctuators.\n"
" static leftParen { \"leftParen\" }\n"
" static rightParen { \"rightParen\" }\n"
" static leftBracket { \"leftBracket\" }\n"
" static rightBracket { \"rightBracket\" }\n"
" static leftBrace { \"leftBrace\" }\n"
" static rightBrace { \"rightBrace\" }\n"
" static colon { \"colon\" }\n"
" static dot { \"dot\" }\n"
" static dotDot { \"dotDot\" }\n"
" static dotDotDot { \"dotDotDot\" }\n"
" static comma { \"comma\" }\n"
" static star { \"star\" }\n"
" static slash { \"slash\" }\n"
" static percent { \"percent\" }\n"
" static plus { \"plus\" }\n"
" static minus { \"minus\" }\n"
" static pipe { \"pipe\" }\n"
" static pipePipe { \"pipePipe\" }\n"
" static caret { \"caret\" }\n"
" static amp { \"amp\" }\n"
" static ampAmp { \"ampAmp\" }\n"
" static question { \"question\" }\n"
" static bang { \"bang\" }\n"
" static tilde { \"tilde\" }\n"
" static equal { \"equal\" }\n"
" static less { \"less\" }\n"
" static lessEqual { \"lessEqual\" }\n"
" static lessLess { \"lessLess\" }\n"
" static greater { \"greater\" }\n"
" static greaterEqual { \"greaterEqual\" }\n"
" static greaterGreater { \"greaterGreater\" }\n"
" static equalEqual { \"equalEqual\" }\n"
" static bangEqual { \"bangEqual\" }\n"
"\n"
" // Keywords.\n"
" static breakKeyword { \"break\" }\n"
" static classKeyword { \"class\" }\n"
" static constructKeyword { \"construct\" }\n"
" static elseKeyword { \"else\" }\n"
" static falseKeyword { \"false\" }\n"
" static forKeyword { \"for\" }\n"
" static foreignKeyword { \"foreign\" }\n"
" static ifKeyword { \"if\" }\n"
" static importKeyword { \"import\" }\n"
" static inKeyword { \"in\" }\n"
" static isKeyword { \"is\" }\n"
" static nullKeyword { \"null\" }\n"
" static returnKeyword { \"return\" }\n"
" static staticKeyword { \"static\" }\n"
" static superKeyword { \"super\" }\n"
" static thisKeyword { \"this\" }\n"
" static trueKeyword { \"true\" }\n"
" static varKeyword { \"var\" }\n"
" static whileKeyword { \"while\" }\n"
"\n"
" static field { \"field\" }\n"
" static name { \"name\" }\n"
" static number { \"number\" }\n"
" static string { \"string\" }\n"
" static interpolation { \"interpolation\" }\n"
" static comment { \"comment\" }\n"
" static whitespace { \"whitespace\" }\n"
" static line { \"line\" }\n"
" static error { \"error\" }\n"
" static eof { \"eof\" }\n"
"\n"
" construct new(source, type, start, length) {\n"
" _source = source\n"
" _type = type\n"
" _start = start\n"
" _length = length\n"
" }\n"
"\n"
" type { _type }\n"
" text { _source[_start...(_start + _length)] }\n"
"\n"
" start { _start }\n"
" length { _length }\n"
"\n"
" toString { text }\n"
"}\n"
"\n"
"var KEYWORDS = {\n"
" \"break\": Token.breakKeyword,\n"
" \"class\": Token.classKeyword,\n"
" \"construct\": Token.constructKeyword,\n"
" \"else\": Token.elseKeyword,\n"
" \"false\": Token.falseKeyword,\n"
" \"for\": Token.forKeyword,\n"
" \"foreign\": Token.foreignKeyword,\n"
" \"if\": Token.ifKeyword,\n"
" \"import\": Token.importKeyword,\n"
" \"in\": Token.inKeyword,\n"
" \"is\": Token.isKeyword,\n"
" \"null\": Token.nullKeyword,\n"
" \"return\": Token.returnKeyword,\n"
" \"static\": Token.staticKeyword,\n"
" \"super\": Token.superKeyword,\n"
" \"this\": Token.thisKeyword,\n"
" \"true\": Token.trueKeyword,\n"
" \"var\": Token.varKeyword,\n"
" \"while\": Token.whileKeyword\n"
"}\n"
"\n"
"var TOKEN_COLORS = {\n"
" Token.leftParen: Color.gray,\n"
" Token.rightParen: Color.gray,\n"
" Token.leftBracket: Color.gray,\n"
" Token.rightBracket: Color.gray,\n"
" Token.leftBrace: Color.gray,\n"
" Token.rightBrace: Color.gray,\n"
" Token.colon: Color.gray,\n"
" Token.dot: Color.gray,\n"
" Token.dotDot: Color.none,\n"
" Token.dotDotDot: Color.none,\n"
" Token.comma: Color.gray,\n"
" Token.star: Color.none,\n"
" Token.slash: Color.none,\n"
" Token.percent: Color.none,\n"
" Token.plus: Color.none,\n"
" Token.minus: Color.none,\n"
" Token.pipe: Color.none,\n"
" Token.pipePipe: Color.none,\n"
" Token.caret: Color.none,\n"
" Token.amp: Color.none,\n"
" Token.ampAmp: Color.none,\n"
" Token.question: Color.none,\n"
" Token.bang: Color.none,\n"
" Token.tilde: Color.none,\n"
" Token.equal: Color.none,\n"
" Token.less: Color.none,\n"
" Token.lessEqual: Color.none,\n"
" Token.lessLess: Color.none,\n"
" Token.greater: Color.none,\n"
" Token.greaterEqual: Color.none,\n"
" Token.greaterGreater: Color.none,\n"
" Token.equalEqual: Color.none,\n"
" Token.bangEqual: Color.none,\n"
"\n"
" // Keywords.\n"
" Token.breakKeyword: Color.cyan,\n"
" Token.classKeyword: Color.cyan,\n"
" Token.constructKeyword: Color.cyan,\n"
" Token.elseKeyword: Color.cyan,\n"
" Token.falseKeyword: Color.cyan,\n"
" Token.forKeyword: Color.cyan,\n"
" Token.foreignKeyword: Color.cyan,\n"
" Token.ifKeyword: Color.cyan,\n"
" Token.importKeyword: Color.cyan,\n"
" Token.inKeyword: Color.cyan,\n"
" Token.isKeyword: Color.cyan,\n"
" Token.nullKeyword: Color.cyan,\n"
" Token.returnKeyword: Color.cyan,\n"
" Token.staticKeyword: Color.cyan,\n"
" Token.superKeyword: Color.cyan,\n"
" Token.thisKeyword: Color.cyan,\n"
" Token.trueKeyword: Color.cyan,\n"
" Token.varKeyword: Color.cyan,\n"
" Token.whileKeyword: Color.cyan,\n"
"\n"
" Token.field: Color.none,\n"
" Token.name: Color.none,\n"
" Token.number: Color.magenta,\n"
" Token.string: Color.yellow,\n"
" Token.interpolation: Color.yellow,\n"
" Token.comment: Color.gray,\n"
" Token.whitespace: Color.none,\n"
" Token.line: Color.none,\n"
" Token.error: Color.red,\n"
" Token.eof: Color.none,\n"
"}\n"
"\n"
"// Data table for tokens that are tokenized using maximal munch.\n"
"//\n"
"// The key is the character that starts the token or tokens. After that is a\n"
"// list of token types and characters. As long as the next character is matched,\n"
"// the type will update to the type after that character.\n"
"var PUNCTUATORS = {\n"
" Chars.leftParen: [Token.leftParen],\n"
" Chars.rightParen: [Token.rightParen],\n"
" Chars.leftBracket: [Token.leftBracket],\n"
" Chars.rightBracket: [Token.rightBracket],\n"
" Chars.leftBrace: [Token.leftBrace],\n"
" Chars.rightBrace: [Token.rightBrace],\n"
" Chars.colon: [Token.colon],\n"
" Chars.comma: [Token.comma],\n"
" Chars.star: [Token.star],\n"
" Chars.percent: [Token.percent],\n"
" Chars.plus: [Token.plus],\n"
" Chars.minus: [Token.minus],\n"
" Chars.tilde: [Token.tilde],\n"
" Chars.caret: [Token.caret],\n"
" Chars.question: [Token.question],\n"
" Chars.lineFeed: [Token.line],\n"
"\n"
" Chars.pipe: [Token.pipe, Chars.pipe, Token.pipePipe],\n"
" Chars.amp: [Token.amp, Chars.amp, Token.ampAmp],\n"
" Chars.bang: [Token.bang, Chars.equal, Token.bangEqual],\n"
" Chars.equal: [Token.equal, Chars.equal, Token.equalEqual],\n"
"\n"
" Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot]\n"
"}\n"
"\n"
"/// Tokenizes a string of input. This lexer differs from most in that it\n"
"/// silently ignores errors from incomplete input, like a string literal with\n"
"/// no closing quote. That's because this is intended to be run on a line of\n"
"/// input while the user is still typing it.\n"
"class Lexer {\n"
" construct new(source) {\n"
" _source = source\n"
"\n"
" // Due to the magic of UTF-8, we can safely treat Wren source as a series\n"
" // of bytes, since the only code points that are meaningful to Wren fit in\n"
" // ASCII. The only place where non-ASCII code points can occur is inside\n"
" // string literals and comments and the lexer safely treats those as opaque\n"
" // bytes.\n"
" _bytes = source.bytes\n"
"\n"
" _start = 0\n"
" _current = 0\n"
"\n"
" // The stack of ongoing interpolated strings. Each element in the list is\n"
" // a single level of interpolation nesting. The value of the element is the\n"
" // number of unbalanced \"(\" still remaining to be closed.\n"
" _interpolations = []\n"
" }\n"
"\n"
" readToken() {\n"
" if (_current >= _bytes.count) return makeToken(Token.eof)\n"
"\n"
" _start = _current\n"
" var c = _bytes[_current]\n"
" advance()\n"
"\n"
" if (!_interpolations.isEmpty) {\n"
" if (c == Chars.leftParen) {\n"
" _interpolations[-1] = _interpolations[-1] + 1\n"
" } else if (c == Chars.rightParen) {\n"
" _interpolations[-1] = _interpolations[-1] - 1\n"
"\n"
" // The last \")\" in an interpolated expression ends the expression and\n"
" // resumes the string.\n"
" if (_interpolations[-1] == 0) {\n"
" // This is the final \")\", so the interpolation expression has ended.\n"
" // This \")\" now begins the next section of the template string.\n"
" _interpolations.removeAt(-1)\n"
" return readString()\n"
" }\n"
" }\n"
" }\n"
"\n"
" if (PUNCTUATORS.containsKey(c)) {\n"
" var punctuator = PUNCTUATORS[c]\n"
" var type = punctuator[0]\n"
" var i = 1\n"
" while (i < punctuator.count) {\n"
" if (!match(punctuator[i])) break\n"
" type = punctuator[i + 1]\n"
" i = i + 2\n"
" }\n"
"\n"
" return makeToken(type)\n"
" }\n"
"\n"
" // Handle \"<\", \"<<\", and \"<=\".\n"
" if (c == Chars.less) {\n"
" if (match(Chars.less)) return makeToken(Token.lessLess)\n"
" if (match(Chars.equal)) return makeToken(Token.lessEqual)\n"
" return makeToken(Token.less)\n"
" }\n"
"\n"
" // Handle \">\", \">>\", and \">=\".\n"
" if (c == Chars.greater) {\n"
" if (match(Chars.greater)) return makeToken(Token.greaterGreater)\n"
" if (match(Chars.equal)) return makeToken(Token.greaterEqual)\n"
" return makeToken(Token.greater)\n"
" }\n"
"\n"
" // Handle \"/\", \"//\", and \"/*\".\n"
" if (c == Chars.slash) {\n"
" if (match(Chars.slash)) return readLineComment()\n"
" if (match(Chars.star)) return readBlockComment()\n"
" return makeToken(Token.slash)\n"
" }\n"
"\n"
" if (c == Chars.underscore) return readField()\n"
" if (c == Chars.quote) return readString()\n"
"\n"
" if (c == Chars.zero && peek() == Chars.lowerX) return readHexNumber()\n"
" if (Chars.isWhitespace(c)) return readWhitespace()\n"
" if (Chars.isDigit(c)) return readNumber()\n"
" if (Chars.isAlpha(c)) return readName()\n"
"\n"
" return makeToken(Token.error)\n"
" }\n"
"\n"
" // Reads a line comment until the end of the line is reached.\n"
" readLineComment() {\n"
" // A line comment stops at the newline since newlines are significant.\n"
" while (peek() != Chars.lineFeed && !isAtEnd) {\n"
" advance()\n"
" }\n"
"\n"
" return makeToken(Token.comment)\n"
" }\n"
"\n"
" readBlockComment() {\n"
" // Block comments can nest.\n"
" var nesting = 1\n"
" while (nesting > 0) {\n"
" // TODO: Report error.\n"
" if (isAtEnd) break\n"
"\n"
" if (peek() == Chars.slash && peek(1) == Chars.star) {\n"
" advance()\n"
" advance()\n"
" nesting = nesting + 1\n"
" } else if (peek() == Chars.star && peek(1) == Chars.slash) {\n"
" advance()\n"
" advance()\n"
" nesting = nesting - 1\n"
" if (nesting == 0) break\n"
" } else {\n"
" advance()\n"
" }\n"
" }\n"
"\n"
" return makeToken(Token.comment)\n"
" }\n"
"\n"
" // Reads a static or instance field.\n"
" readField() {\n"
" var type = Token.field\n"
"\n"
" // Read the rest of the name.\n"
" while (match {|c| Chars.isAlphaNumeric(c) }) {}\n"
"\n"
" return makeToken(type)\n"
" }\n"
"\n"
" // Reads a string literal.\n"
" readString() {\n"
" var type = Token.string\n"
"\n"
" while (!isAtEnd) {\n"
" var c = _bytes[_current]\n"
" advance()\n"
"\n"
" if (c == Chars.backslash) {\n"
" // TODO: Process specific escapes and validate them.\n"
" if (!isAtEnd) advance()\n"
" } else if (c == Chars.percent) {\n"
" // Consume the '('.\n"
" if (!isAtEnd) advance()\n"
" // TODO: Handle missing '('.\n"
" _interpolations.add(1)\n"
" type = Token.interpolation\n"
" break\n"
" } else if (c == Chars.quote) {\n"
" break\n"
" }\n"
" }\n"
"\n"
" return makeToken(type)\n"
" }\n"
"\n"
" // Reads a number literal.\n"
" readHexNumber() {\n"
" // Skip past the `x`.\n"
" advance()\n"
"\n"
" // Read the rest of the number.\n"
" while (match {|c| Chars.isHexDigit(c) }) {}\n"
" return makeToken(Token.number)\n"
" }\n"
"\n"
" // Reads a series of whitespace characters.\n"
" readWhitespace() {\n"
" // Read the rest of the whitespace.\n"
" while (match {|c| Chars.isWhitespace(c) }) {}\n"
"\n"
" return makeToken(Token.whitespace)\n"
" }\n"
"\n"
" // Reads a number literal.\n"
" readNumber() {\n"
" // Read the rest of the number.\n"
" while (match {|c| Chars.isDigit(c) }) {}\n"
"\n"
" // TODO: Floating point, scientific.\n"
" return makeToken(Token.number)\n"
" }\n"
"\n"
" // Reads an identifier or keyword token.\n"
" readName() {\n"
" // Read the rest of the name.\n"
" while (match {|c| Chars.isAlphaNumeric(c) }) {}\n"
"\n"
" var text = _source[_start..._current]\n"
" var type = Token.name\n"
" if (KEYWORDS.containsKey(text)) {\n"
" type = KEYWORDS[text]\n"
" }\n"
"\n"
" return Token.new(_source, type, _start, _current - _start)\n"
" }\n"
"\n"
" // Returns `true` if we have scanned all characters.\n"
" isAtEnd { _current >= _bytes.count }\n"
"\n"
" // Advances past the current character.\n"
" advance() {\n"
" _current = _current + 1\n"
" }\n"
"\n"
" // Returns the byte value of the current character.\n"
" peek() { peek(0) }\n"
"\n"
" // Returns the byte value of the character [n] bytes past the current\n"
" // character.\n"
" peek(n) {\n"
" if (_current + n >= _bytes.count) return -1\n"
" return _bytes[_current + n]\n"
" }\n"
"\n"
" // Consumes the current character if it matches [condition], which can be a\n"
" // numeric code point value or a function that takes a code point and returns\n"
" // `true` if the code point matches.\n"
" match(condition) {\n"
" if (isAtEnd) return false\n"
"\n"
" var c = _bytes[_current]\n"
" if (condition is Fn) {\n"
" if (!condition.call(c)) return false\n"
" } else if (c != condition) {\n"
" return false\n"
" }\n"
"\n"
" advance()\n"
" return true\n"
" }\n"
"\n"
" // Creates a token of [type] from the current character range.\n"
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n"
"}\n"
"\n"
"// Fire up the REPL. We use ANSI when talking to a POSIX TTY.\n"
"if (Platform.isPosix && Stdin.isTerminal) {\n"
" AnsiRepl.new().run()\n"
"} else {\n"
" // ANSI escape sequences probably aren't supported, so degrade.\n"
" SimpleRepl.new().run()\n"
"}\n";

View File

@ -0,0 +1,79 @@
#include <stdlib.h>
#include <string.h>
#include "uv.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
// A handle to the "Scheduler" class object. Used to call static methods on it.
static WrenHandle* 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 WrenHandle* resume1;
static WrenHandle* resume2;
static WrenHandle* resumeError;
static void resume(WrenHandle* method)
{
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.
if (result == WREN_RESULT_RUNTIME_ERROR)
{
uv_stop(getLoop());
setExitCode(70); // EX_SOFTWARE.
}
}
void schedulerCaptureMethods(WrenVM* vm)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "scheduler", "Scheduler", 0);
schedulerClass = wrenGetSlotHandle(vm, 0);
resume1 = wrenMakeCallHandle(vm, "resume_(_)");
resume2 = wrenMakeCallHandle(vm, "resume_(_,_)");
resumeError = wrenMakeCallHandle(vm, "resumeError_(_,_)");
}
void schedulerResume(WrenHandle* fiber, bool hasArgument)
{
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 2 + (hasArgument ? 1 : 0));
wrenSetSlotHandle(vm, 0, schedulerClass);
wrenSetSlotHandle(vm, 1, fiber);
wrenReleaseHandle(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 schedulerFinishResume()
{
resume(resume2);
}
void schedulerResumeError(WrenHandle* fiber, const char* error)
{
schedulerResume(fiber, true);
wrenSetSlotString(getVM(), 2, error);
resume(resumeError);
}
void schedulerShutdown()
{
// If the module was never loaded, we don't have anything to release.
if (schedulerClass == NULL) return;
WrenVM* vm = getVM();
wrenReleaseHandle(vm, schedulerClass);
wrenReleaseHandle(vm, resume1);
wrenReleaseHandle(vm, resume2);
wrenReleaseHandle(vm, resumeError);
}

View File

@ -0,0 +1,20 @@
#ifndef scheduler_h
#define scheduler_h
#include "wren.h"
// 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(WrenHandle* fiber, bool hasArgument);
void schedulerFinishResume();
void schedulerResumeError(WrenHandle* fiber, const char* error);
void schedulerShutdown();
#endif

View File

@ -0,0 +1,27 @@
class Scheduler {
static add(callable) {
if (__scheduled == null) __scheduled = []
__scheduled.add(Fiber.new {
callable.call()
runNextScheduled_()
})
}
// Called by native code.
static resume_(fiber) { fiber.transfer() }
static resume_(fiber, arg) { fiber.transfer(arg) }
static resumeError_(fiber, error) { fiber.transferError(error) }
static runNextScheduled_() {
if (__scheduled == null || __scheduled.isEmpty) {
return Fiber.suspend()
} else {
return __scheduled.removeAt(0).transfer()
}
}
foreign static captureMethods_()
}
Scheduler.captureMethods_()

View File

@ -0,0 +1,29 @@
// Generated automatically from src/module/scheduler.wren. Do not edit.
static const char* schedulerModuleSource =
"class Scheduler {\n"
" static add(callable) {\n"
" if (__scheduled == null) __scheduled = []\n"
"\n"
" __scheduled.add(Fiber.new {\n"
" callable.call()\n"
" runNextScheduled_()\n"
" })\n"
" }\n"
"\n"
" // Called by native code.\n"
" static resume_(fiber) { fiber.transfer() }\n"
" static resume_(fiber, arg) { fiber.transfer(arg) }\n"
" static resumeError_(fiber, error) { fiber.transferError(error) }\n"
"\n"
" static runNextScheduled_() {\n"
" if (__scheduled == null || __scheduled.isEmpty) {\n"
" return Fiber.suspend()\n"
" } else {\n"
" return __scheduled.removeAt(0).transfer()\n"
" }\n"
" }\n"
"\n"
" foreign static captureMethods_()\n"
"}\n"
"\n"
"Scheduler.captureMethods_()\n";

View File

@ -0,0 +1,39 @@
#include <stdlib.h>
#include <string.h>
#include "uv.h"
#include "scheduler.h"
#include "vm.h"
#include "wren.h"
// Called by libuv when the timer finished closing.
static void timerCloseCallback(uv_handle_t* handle)
{
free(handle);
}
// Called by libuv when the timer has completed.
static void timerCallback(uv_timer_t* handle)
{
WrenHandle* fiber = (WrenHandle*)handle->data;
// Tell libuv that we don't need the timer anymore.
uv_close((uv_handle_t*)handle, timerCloseCallback);
// Run the fiber that was sleeping.
schedulerResume(fiber, false);
}
void timerStartTimer(WrenVM* vm)
{
int milliseconds = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
// Store the fiber to resume when the timer completes.
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));
handle->data = fiber;
uv_timer_init(getLoop(), handle);
uv_timer_start(handle, timerCallback, milliseconds, 0);
}

View File

@ -0,0 +1,13 @@
import "scheduler" for Scheduler
class Timer {
static sleep(milliseconds) {
if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.")
if (milliseconds < 0) Fiber.abort("Milliseconds cannot be negative.")
startTimer_(milliseconds, Fiber.current)
Scheduler.runNextScheduled_()
}
foreign static startTimer_(milliseconds, fiber)
}

View File

@ -0,0 +1,15 @@
// Generated automatically from src/module/timer.wren. Do not edit.
static const char* timerModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Timer {\n"
" static sleep(milliseconds) {\n"
" if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n"
" if (milliseconds < 0) Fiber.abort(\"Milliseconds cannot be negative.\")\n"
"\n"
" startTimer_(milliseconds, Fiber.current)\n"
" Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static startTimer_(milliseconds, fiber)\n"
"}\n";

View File

@ -0,0 +1,96 @@
#include "wren_opt_meta.h"
#if WREN_OPT_META
#include <string.h>
#include "wren_vm.h"
#include "wren_opt_meta.wren.inc"
void metaCompile(WrenVM* vm)
{
const char* source = wrenGetSlotString(vm, 1);
bool isExpression = wrenGetSlotBool(vm, 2);
bool printErrors = wrenGetSlotBool(vm, 3);
// TODO: Allow passing in module?
// Look up the module surrounding the callsite. This is brittle. The -2 walks
// up the callstack assuming that the meta module has one level of
// indirection before hitting the user's code. Any change to meta may require
// this constant to be tweaked.
ObjFiber* currentFiber = vm->fiber;
ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn;
ObjString* module = fn->module->name;
ObjClosure* closure = wrenCompileSource(vm, module->value, source,
isExpression, printErrors);
// Return the result. We can't use the public API for this since we have a
// bare ObjClosure*.
if (closure == NULL)
{
vm->apiStack[0] = NULL_VAL;
}
else
{
vm->apiStack[0] = OBJ_VAL(closure);
}
}
void metaGetModuleVariables(WrenVM* vm)
{
wrenEnsureSlots(vm, 3);
Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]);
if (IS_UNDEFINED(moduleValue))
{
vm->apiStack[0] = NULL_VAL;
return;
}
ObjModule* module = AS_MODULE(moduleValue);
ObjList* names = wrenNewList(vm, module->variableNames.count);
vm->apiStack[0] = OBJ_VAL(names);
// Initialize the elements to null in case a collection happens when we
// allocate the strings below.
for (int i = 0; i < names->elements.count; i++)
{
names->elements.data[i] = NULL_VAL;
}
for (int i = 0; i < names->elements.count; i++)
{
names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]);
}
}
const char* wrenMetaSource()
{
return metaModuleSource;
}
WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature)
{
// There is only one foreign method in the meta module.
ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class.");
ASSERT(isStatic, "Should be static.");
if (strcmp(signature, "compile_(_,_,_)") == 0)
{
return metaCompile;
}
if (strcmp(signature, "getModuleVariables_(_)") == 0)
{
return metaGetModuleVariables;
}
ASSERT(false, "Unknown method.");
return NULL;
}
#endif

View File

@ -0,0 +1,18 @@
#ifndef wren_opt_meta_h
#define wren_opt_meta_h
#include "wren_common.h"
#include "wren.h"
// This module defines the Meta class and its associated methods.
#if WREN_OPT_META
const char* wrenMetaSource();
WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature);
#endif
#endif

View File

@ -0,0 +1,32 @@
class Meta {
static getModuleVariables(module) {
if (!(module is String)) Fiber.abort("Module name must be a string.")
var result = getModuleVariables_(module)
if (result != null) return result
Fiber.abort("Could not find a module named '%(module)'.")
}
static eval(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.")
var closure = compile_(source, false, false)
// TODO: Include compile errors.
if (closure == null) Fiber.abort("Could not compile source code.")
closure.call()
}
static compileExpression(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.")
return compile_(source, true, true)
}
static compile(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.")
return compile_(source, false, true)
}
foreign static compile_(source, isExpression, printErrors)
foreign static getModuleVariables_(module)
}

View File

@ -0,0 +1,34 @@
// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
static const char* metaModuleSource =
"class Meta {\n"
" static getModuleVariables(module) {\n"
" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n"
" var result = getModuleVariables_(module)\n"
" if (result != null) return result\n"
"\n"
" Fiber.abort(\"Could not find a module named '%(module)'.\")\n"
" }\n"
"\n"
" static eval(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
"\n"
" var closure = compile_(source, false, false)\n"
" // TODO: Include compile errors.\n"
" if (closure == null) Fiber.abort(\"Could not compile source code.\")\n"
"\n"
" closure.call()\n"
" }\n"
"\n"
" static compileExpression(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
" return compile_(source, true, true)\n"
" }\n"
"\n"
" static compile(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
" return compile_(source, false, true)\n"
" }\n"
"\n"
" foreign static compile_(source, isExpression, printErrors)\n"
" foreign static getModuleVariables_(module)\n"
"}\n";

View File

@ -0,0 +1,144 @@
#include "wren_opt_random.h"
#if WREN_OPT_RANDOM
#include <string.h>
#include <time.h>
#include "wren.h"
#include "wren_vm.h"
#include "wren_opt_random.wren.inc"
// Implements the well equidistributed long-period linear PRNG (WELL512a).
//
// https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear
typedef struct
{
uint32_t state[16];
uint32_t index;
} Well512;
// Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
static uint32_t advanceState(Well512* well)
{
uint32_t a, b, c, d;
a = well->state[well->index];
c = well->state[(well->index + 13) & 15];
b = a ^ c ^ (a << 16) ^ (c << 15);
c = well->state[(well->index + 9) & 15];
c ^= (c >> 11);
a = well->state[well->index] = b ^ c;
d = a ^ ((a << 5) & 0xda442d24U);
well->index = (well->index + 15) & 15;
a = well->state[well->index];
well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28);
return well->state[well->index];
}
static void randomAllocate(WrenVM* vm)
{
Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512));
well->index = 0;
}
static void randomSeed0(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)time(NULL));
for (int i = 0; i < 16; i++)
{
well->state[i] = rand();
}
}
static void randomSeed1(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)wrenGetSlotDouble(vm, 1));
for (int i = 0; i < 16; i++)
{
well->state[i] = rand();
}
}
static void randomSeed16(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
for (int i = 0; i < 16; i++)
{
well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1);
}
}
static void randomFloat(WrenVM* vm)
{
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.
// First, start with 32 random bits, shifted to the left 21 bits.
double result = (double)advanceState(well) * (1 << 21);
// Then add another 21 random bits.
result += (double)(advanceState(well) & ((1 << 21) - 1));
// Now we have a number from 0 - (2^53). Divide be the range to get a double
// from 0 to 1.0 (half-inclusive).
result /= 9007199254740992.0;
wrenSetSlotDouble(vm, 0, result);
}
static void randomInt0(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)advanceState(well));
}
const char* wrenRandomSource()
{
return randomModuleSource;
}
WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
const char* module,
const char* className)
{
ASSERT(strcmp(className, "Random") == 0, "Should be in Random class.");
WrenForeignClassMethods methods;
methods.allocate = randomAllocate;
methods.finalize = NULL;
return methods;
}
WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature)
{
ASSERT(strcmp(className, "Random") == 0, "Should be in Random class.");
if (strcmp(signature, "<allocate>") == 0) return randomAllocate;
if (strcmp(signature, "seed_()") == 0) return randomSeed0;
if (strcmp(signature, "seed_(_)") == 0) return randomSeed1;
if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0)
{
return randomSeed16;
}
if (strcmp(signature, "float()") == 0) return randomFloat;
if (strcmp(signature, "int()") == 0) return randomInt0;
ASSERT(false, "Unknown method.");
return NULL;
}
#endif

View File

@ -0,0 +1,20 @@
#ifndef wren_opt_random_h
#define wren_opt_random_h
#include "wren_common.h"
#include "wren.h"
#if WREN_OPT_RANDOM
const char* wrenRandomSource();
WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
const char* module,
const char* className);
WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature);
#endif
#endif

View File

@ -0,0 +1,122 @@
foreign class Random {
construct new() {
seed_()
}
construct new(seed) {
if (seed is Num) {
seed_(seed)
} else if (seed is Sequence) {
if (seed.isEmpty) Fiber.abort("Sequence cannot be empty.")
// TODO: Empty sequence.
var seeds = []
for (element in seed) {
if (!(element is Num)) Fiber.abort("Sequence elements must all be numbers.")
seeds.add(element)
if (seeds.count == 16) break
}
// Cycle the values to fill in any missing slots.
var i = 0
while (seeds.count < 16) {
seeds.add(seeds[i])
i = i + 1
}
seed_(
seeds[0], seeds[1], seeds[2], seeds[3],
seeds[4], seeds[5], seeds[6], seeds[7],
seeds[8], seeds[9], seeds[10], seeds[11],
seeds[12], seeds[13], seeds[14], seeds[15])
} else {
Fiber.abort("Seed must be a number or a sequence of numbers.")
}
}
foreign seed_()
foreign seed_(seed)
foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)
foreign float()
float(end) { float() * end }
float(start, end) { float() * (end - start) + start }
foreign int()
int(end) { (float() * end).floor }
int(start, end) { (float() * (end - start)).floor + start }
sample(list) { sample(list, 1)[0] }
sample(list, count) {
if (count > list.count) Fiber.abort("Not enough elements to sample.")
// There at (at least) two simple algorithms for choosing a number of
// samples from a list without replacement -- where we don't pick the same
// element more than once.
//
// The first is faster when the number of samples is small relative to the
// size of the collection. In many cases, it avoids scanning the entire
// list. In the common case of just wanting one sample, it's a single
// random index lookup.
//
// However, its performance degrades badly as the sample size increases.
// Vitter's algorithm always scans the entire list, but it's also always
// O(n).
//
// The cutoff point between the two follows a quadratic curve on the same
// size. Based on some empirical testing, scaling that by 5 seems to fit
// pretty closely and chooses the fastest one for the given sample and
// collection size.
if (count * count * 5 < list.count) {
// Pick random elements and retry if you hit a previously chosen one.
var picked = {}
var result = []
for (i in 0...count) {
// Find an index that we haven't already selected.
var index
while (true) {
index = int(count)
if (!picked.containsKey(index)) break
}
picked[index] = true
result.add(list[index])
}
return result
} else {
// Jeffrey Vitter's Algorithm R.
// Fill the reservoir with the first elements in the list.
var result = list[0...count]
// We want to ensure the results are always in random order, so shuffle
// them. In cases where the sample size is the entire collection, this
// devolves to running Fisher-Yates on a copy of the list.
shuffle(result)
// Now walk the rest of the list. For each element, randomly consider
// replacing one of the reservoir elements with it. The probability here
// works out such that it does this uniformly.
for (i in count...list.count) {
var slot = int(0, i + 1)
if (slot < count) result[slot] = list[i]
}
return result
}
}
shuffle(list) {
if (list.isEmpty) return
// Fisher-Yates shuffle.
for (i in 0...list.count - 1) {
var from = int(i, list.count)
var temp = list[from]
list[from] = list[i]
list[i] = temp
}
}
}

View File

@ -0,0 +1,124 @@
// Generated automatically from src/optional/wren_opt_random.wren. Do not edit.
static const char* randomModuleSource =
"foreign class Random {\n"
" construct new() {\n"
" seed_()\n"
" }\n"
"\n"
" construct new(seed) {\n"
" if (seed is Num) {\n"
" seed_(seed)\n"
" } else if (seed is Sequence) {\n"
" if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n"
"\n"
" // TODO: Empty sequence.\n"
" var seeds = []\n"
" for (element in seed) {\n"
" if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n"
"\n"
" seeds.add(element)\n"
" if (seeds.count == 16) break\n"
" }\n"
"\n"
" // Cycle the values to fill in any missing slots.\n"
" var i = 0\n"
" while (seeds.count < 16) {\n"
" seeds.add(seeds[i])\n"
" i = i + 1\n"
" }\n"
"\n"
" seed_(\n"
" seeds[0], seeds[1], seeds[2], seeds[3],\n"
" seeds[4], seeds[5], seeds[6], seeds[7],\n"
" seeds[8], seeds[9], seeds[10], seeds[11],\n"
" seeds[12], seeds[13], seeds[14], seeds[15])\n"
" } else {\n"
" Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n"
" }\n"
" }\n"
"\n"
" foreign seed_()\n"
" foreign seed_(seed)\n"
" foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n"
"\n"
" foreign float()\n"
" float(end) { float() * end }\n"
" float(start, end) { float() * (end - start) + start }\n"
"\n"
" foreign int()\n"
" int(end) { (float() * end).floor }\n"
" int(start, end) { (float() * (end - start)).floor + start }\n"
"\n"
" sample(list) { sample(list, 1)[0] }\n"
" sample(list, count) {\n"
" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n"
"\n"
" // There at (at least) two simple algorithms for choosing a number of\n"
" // samples from a list without replacement -- where we don't pick the same\n"
" // element more than once.\n"
" //\n"
" // The first is faster when the number of samples is small relative to the\n"
" // size of the collection. In many cases, it avoids scanning the entire\n"
" // list. In the common case of just wanting one sample, it's a single\n"
" // random index lookup.\n"
" //\n"
" // However, its performance degrades badly as the sample size increases.\n"
" // Vitter's algorithm always scans the entire list, but it's also always\n"
" // O(n).\n"
" //\n"
" // The cutoff point between the two follows a quadratic curve on the same\n"
" // size. Based on some empirical testing, scaling that by 5 seems to fit\n"
" // pretty closely and chooses the fastest one for the given sample and\n"
" // collection size.\n"
" if (count * count * 5 < list.count) {\n"
" // Pick random elements and retry if you hit a previously chosen one.\n"
" var picked = {}\n"
" var result = []\n"
" for (i in 0...count) {\n"
" // Find an index that we haven't already selected.\n"
" var index\n"
" while (true) {\n"
" index = int(count)\n"
" if (!picked.containsKey(index)) break\n"
" }\n"
"\n"
" picked[index] = true\n"
" result.add(list[index])\n"
" }\n"
"\n"
" return result\n"
" } else {\n"
" // Jeffrey Vitter's Algorithm R.\n"
"\n"
" // Fill the reservoir with the first elements in the list.\n"
" var result = list[0...count]\n"
"\n"
" // We want to ensure the results are always in random order, so shuffle\n"
" // them. In cases where the sample size is the entire collection, this\n"
" // devolves to running Fisher-Yates on a copy of the list.\n"
" shuffle(result)\n"
"\n"
" // Now walk the rest of the list. For each element, randomly consider\n"
" // replacing one of the reservoir elements with it. The probability here\n"
" // works out such that it does this uniformly.\n"
" for (i in count...list.count) {\n"
" var slot = int(0, i + 1)\n"
" if (slot < count) result[slot] = list[i]\n"
" }\n"
"\n"
" return result\n"
" }\n"
" }\n"
"\n"
" shuffle(list) {\n"
" if (list.isEmpty) return\n"
"\n"
" // Fisher-Yates shuffle.\n"
" for (i in 0...list.count - 1) {\n"
" var from = int(i, list.count)\n"
" var temp = list[from]\n"
" list[from] = list[i]\n"
" list[i] = temp\n"
" }\n"
" }\n"
"}\n";

View File

@ -0,0 +1,202 @@
#ifndef wren_common_h
#define wren_common_h
// This header contains macros and defines used across the entire Wren
// implementation. In particular, it contains "configuration" defines that
// control how Wren works. Some of these are only used while hacking on Wren
// itself.
//
// This header is *not* intended to be included by code outside of Wren itself.
// Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some
// of the associated limit constants (UINT32_MAX, etc.). The constants are not
// part of standard C++, so aren't included by default by C++ compilers when you
// include <stdint> unless __STDC_LIMIT_MACROS is defined.
#define __STDC_LIMIT_MACROS
#include <stdint.h>
// These flags let you control some details of the interpreter's implementation.
// Usually they trade-off a bit of portability for speed. They default to the
// most efficient behavior.
// If true, then Wren uses a NaN-tagged double for its core value
// representation. Otherwise, it uses a larger more conventional struct. The
// former is significantly faster and more compact. The latter is useful for
// debugging and may be more portable.
//
// Defaults to on.
#ifndef WREN_NAN_TAGGING
#define WREN_NAN_TAGGING 1
#endif
// If true, the VM's interpreter loop uses computed gotos. See this for more:
// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html
// Enabling this speeds up the main dispatch loop a bit, but requires compiler
// support.
//
// Defaults to true on supported compilers.
#ifndef WREN_COMPUTED_GOTO
#ifdef _MSC_VER
// No computed gotos in Visual Studio.
#define WREN_COMPUTED_GOTO 0
#else
#define WREN_COMPUTED_GOTO 1
#endif
#endif
// The VM includes a number of optional modules. You can choose to include
// these or not. By default, they are all available. To disable one, set the
// corresponding `WREN_OPT_<name>` define to `0`.
#ifndef WREN_OPT_META
#define WREN_OPT_META 1
#endif
#ifndef WREN_OPT_RANDOM
#define WREN_OPT_RANDOM 1
#endif
// These flags are useful for debugging and hacking on Wren itself. They are not
// intended to be used for production code. They default to off.
// Set this to true to stress test the GC. It will perform a collection before
// every allocation. This is useful to ensure that memory is always correctly
// reachable.
#define WREN_DEBUG_GC_STRESS 0
// Set this to true to log memory operations as they occur.
#define WREN_DEBUG_TRACE_MEMORY 0
// Set this to true to log garbage collections as they occur.
#define WREN_DEBUG_TRACE_GC 0
// Set this to true to print out the compiled bytecode of each function.
#define WREN_DEBUG_DUMP_COMPILED_CODE 0
// Set this to trace each instruction as it's executed.
#define WREN_DEBUG_TRACE_INSTRUCTIONS 0
// The maximum number of module-level variables that may be defined at one time.
// This limitation comes from the 16 bits used for the arguments to
// `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`.
#define MAX_MODULE_VARS 65536
// The maximum number of arguments that can be passed to a method. Note that
// this limitation is hardcoded in other places in the VM, in particular, the
// `CODE_CALL_XX` instructions assume a certain maximum number.
#define MAX_PARAMETERS 16
// The maximum name of a method, not including the signature. This is an
// arbitrary but enforced maximum just so we know how long the method name
// strings need to be in the parser.
#define MAX_METHOD_NAME 64
// The maximum length of a method signature. Signatures look like:
//
// foo // Getter.
// foo() // No-argument method.
// foo(_) // One-argument method.
// foo(_,_) // Two-argument method.
// init foo() // Constructor initializer.
//
// The maximum signature length takes into account the longest method name, the
// maximum number of parameters with separators between them, "init ", and "()".
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6)
// The maximum length of an identifier. The only real reason for this limitation
// is so that error messages mentioning variables can be stack allocated.
#define MAX_VARIABLE_NAME 64
// The maximum number of fields a class can have, including inherited fields.
// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take
// a single byte for the number of fields. Note that it's 255 and not 256
// because creating a class takes the *number* of fields, not the *highest
// field index*.
#define MAX_FIELDS 255
// Use the VM's allocator to allocate an object of [type].
#define ALLOCATE(vm, type) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
// Use the VM's allocator to allocate an object of [mainType] containing a
// flexible array of [count] objects of [arrayType].
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * (count)))
// Use the VM's allocator to allocate an array of [count] elements of [type].
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count)))
// Use the VM's allocator to free the previously allocated memory at [pointer].
#define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0)
// The Microsoft compiler does not support the "inline" modifier when compiling
// as plain C.
#if defined( _MSC_VER ) && !defined(__cplusplus)
#define inline _inline
#endif
// This is used to clearly mark flexible-sized arrays that appear at the end of
// some dynamically-allocated structs, known as the "struct hack".
#if __STDC_VERSION__ >= 199901L
// In C99, a flexible array member is just "[]".
#define FLEXIBLE_ARRAY
#else
// Elsewhere, use a zero-sized array. It's technically undefined behavior,
// but works reliably in most known compilers.
#define FLEXIBLE_ARRAY 0
#endif
// Assertions are used to validate program invariants. They indicate things the
// program expects to be true about its internal state during execution. If an
// assertion fails, there is a bug in Wren.
//
// Assertions add significant overhead, so are only enabled in debug builds.
#ifdef DEBUG
#include <stdio.h>
#define ASSERT(condition, message) \
do \
{ \
if (!(condition)) \
{ \
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
__FILE__, __LINE__, __func__, message); \
abort(); \
} \
} \
while(0)
// Indicates that we know execution should never reach this point in the
// program. In debug mode, we assert this fact because it's a bug to get here.
//
// In release mode, we use compiler-specific built in functions to tell the
// compiler the code can't be reached. This avoids "missing return" warnings
// in some cases and also lets it perform some optimizations by assuming the
// code is never reached.
#define UNREACHABLE() \
do \
{ \
fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \
__FILE__, __LINE__, __func__); \
abort(); \
} \
while (0)
#else
#define ASSERT(condition, message) do {} while (0)
// Tell the compiler that this part of the code will never be reached.
#if defined( _MSC_VER )
#define UNREACHABLE() __assume(0)
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
#define UNREACHABLE() __builtin_unreachable()
#else
#define UNREACHABLE()
#endif
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
#ifndef wren_compiler_h
#define wren_compiler_h
#include "wren.h"
#include "wren_value.h"
typedef struct sCompiler Compiler;
// This module defines the compiler for Wren. It takes a string of source code
// and lexes, parses, and compiles it. Wren uses a single-pass compiler. It
// does not build an actual AST during parsing and then consume that to
// generate code. Instead, the parser directly emits bytecode.
//
// This forces a few restrictions on the grammar and semantics of the language.
// Things like forward references and arbitrary lookahead are much harder. We
// get a lot in return for that, though.
//
// The implementation is much simpler since we don't need to define a bunch of
// AST data structures. More so, we don't have to deal with managing memory for
// AST objects. The compiler does almost no dynamic allocation while running.
//
// Compilation is also faster since we don't create a bunch of temporary data
// structures and destroy them after generating code.
// Compiles [source], a string of Wren source code located in [module], to an
// [ObjFn] that will execute that code when invoked. Returns `NULL` if the
// source contains any syntax errors.
//
// If [isExpression] is `true`, [source] should be a single expression, and
// this compiles it to a function that evaluates and returns that expression.
// Otherwise, [source] should be a series of top level statements.
//
// If [printErrors] is `true`, any compile errors are output to stderr.
// Otherwise, they are silently discarded.
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
bool isExpression, bool printErrors);
// When a class is defined, its superclass is not known until runtime since
// class definitions are just imperative statements. Most of the bytecode for a
// a method doesn't care, but there are two places where it matters:
//
// - To load or store a field, we need to know the index of the field in the
// instance's field array. We need to adjust this so that subclass fields
// are positioned after superclass fields, and we don't know this until the
// superclass is known.
//
// - Superclass calls need to know which superclass to dispatch to.
//
// We could handle this dynamically, but that adds overhead. Instead, when a
// method is bound, we walk the bytecode for the function and patch it up.
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn);
// Reaches all of the heap-allocated objects in use by [compiler] (and all of
// its parents) so that they are not collected by the GC.
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
#ifndef wren_core_h
#define wren_core_h
#include "wren_vm.h"
// This module defines the built-in classes and their primitives methods that
// are implemented directly in C code. Some languages try to implement as much
// of the core module itself in the primary language instead of in the host
// language.
//
// With Wren, we try to do as much of it in C as possible. Primitive methods
// are always faster than code written in Wren, and it minimizes startup time
// since we don't have to parse, compile, and execute Wren code.
//
// There is one limitation, though. Methods written in C cannot call Wren ones.
// They can only be the top of the callstack, and immediately return. This
// makes it difficult to have primitive methods that rely on polymorphic
// behavior. For example, `IO.write` should call `toString` on its argument,
// including user-defined `toString` methods on user-defined classes.
void wrenInitializeCore(WrenVM* vm);
#endif

View File

@ -0,0 +1,438 @@
class Bool {}
class Fiber {}
class Fn {}
class Null {}
class Num {}
class Sequence {
all(f) {
var result = true
for (element in this) {
result = f.call(element)
if (!result) return result
}
return result
}
any(f) {
var result = false
for (element in this) {
result = f.call(element)
if (result) return result
}
return result
}
contains(element) {
for (item in this) {
if (element == item) return true
}
return false
}
count {
var result = 0
for (element in this) {
result = result + 1
}
return result
}
count(f) {
var result = 0
for (element in this) {
if (f.call(element)) result = result + 1
}
return result
}
each(f) {
for (element in this) {
f.call(element)
}
}
isEmpty { iterate(null) ? false : true }
map(transformation) { MapSequence.new(this, transformation) }
skip(count) {
if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.")
}
return SkipSequence.new(this, count)
}
take(count) {
if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.")
}
return TakeSequence.new(this, count)
}
where(predicate) { WhereSequence.new(this, predicate) }
reduce(acc, f) {
for (element in this) {
acc = f.call(acc, element)
}
return acc
}
reduce(f) {
var iter = iterate(null)
if (!iter) Fiber.abort("Can't reduce an empty sequence.")
// Seed with the first element.
var result = iteratorValue(iter)
while (iter = iterate(iter)) {
result = f.call(result, iteratorValue(iter))
}
return result
}
join() { join("") }
join(sep) {
var first = true
var result = ""
for (element in this) {
if (!first) result = result + sep
first = false
result = result + element.toString
}
return result
}
toList {
var result = List.new()
for (element in this) {
result.add(element)
}
return result
}
}
class MapSequence is Sequence {
construct new(sequence, fn) {
_sequence = sequence
_fn = fn
}
iterate(iterator) { _sequence.iterate(iterator) }
iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }
}
class SkipSequence is Sequence {
construct new(sequence, count) {
_sequence = sequence
_count = count
}
iterate(iterator) {
if (iterator) {
return _sequence.iterate(iterator)
} else {
iterator = _sequence.iterate(iterator)
var count = _count
while (count > 0 && iterator) {
iterator = _sequence.iterate(iterator)
count = count - 1
}
return iterator
}
}
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
}
class TakeSequence is Sequence {
construct new(sequence, count) {
_sequence = sequence
_count = count
}
iterate(iterator) {
if (!iterator) _taken = 1 else _taken = _taken + 1
return _taken > _count ? null : _sequence.iterate(iterator)
}
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
}
class WhereSequence is Sequence {
construct new(sequence, fn) {
_sequence = sequence
_fn = fn
}
iterate(iterator) {
while (iterator = _sequence.iterate(iterator)) {
if (_fn.call(_sequence.iteratorValue(iterator))) break
}
return iterator
}
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
}
class String is Sequence {
bytes { StringByteSequence.new(this) }
codePoints { StringCodePointSequence.new(this) }
split(delimiter) {
if (!(delimiter is String) || delimiter.isEmpty) {
Fiber.abort("Delimiter must be a non-empty string.")
}
var result = []
var last = 0
var index = 0
var delimSize = delimiter.byteCount_
var size = byteCount_
while (last < size && (index = indexOf(delimiter, last)) != -1) {
result.add(this[last...index])
last = index + delimSize
}
if (last < size) {
result.add(this[last..-1])
} else {
result.add("")
}
return result
}
replace(from, to) {
if (!(from is String) || from.isEmpty) {
Fiber.abort("From must be a non-empty string.")
} else if (!(to is String)) {
Fiber.abort("To must be a string.")
}
var result = ""
var last = 0
var index = 0
var fromSize = from.byteCount_
var size = byteCount_
while (last < size && (index = indexOf(from, last)) != -1) {
result = result + this[last...index] + to
last = index + fromSize
}
if (last < size) result = result + this[last..-1]
return result
}
trim() { trim_("\t\r\n ", true, true) }
trim(chars) { trim_(chars, true, true) }
trimEnd() { trim_("\t\r\n ", false, true) }
trimEnd(chars) { trim_(chars, false, true) }
trimStart() { trim_("\t\r\n ", true, false) }
trimStart(chars) { trim_(chars, true, false) }
trim_(chars, trimStart, trimEnd) {
if (!(chars is String)) {
Fiber.abort("Characters must be a string.")
}
var codePoints = chars.codePoints.toList
var start
if (trimStart) {
while (start = iterate(start)) {
if (!codePoints.contains(codePointAt_(start))) break
}
if (start == false) return ""
} else {
start = 0
}
var end
if (trimEnd) {
end = byteCount_ - 1
while (end >= start) {
var codePoint = codePointAt_(end)
if (codePoint != -1 && !codePoints.contains(codePoint)) break
end = end - 1
}
if (end < start) return ""
} else {
end = -1
}
return this[start..end]
}
*(count) {
if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.")
}
var result = ""
for (i in 0...count) {
result = result + this
}
return result
}
}
class StringByteSequence is Sequence {
construct new(string) {
_string = string
}
[index] { _string.byteAt_(index) }
iterate(iterator) { _string.iterateByte_(iterator) }
iteratorValue(iterator) { _string.byteAt_(iterator) }
count { _string.byteCount_ }
}
class StringCodePointSequence is Sequence {
construct new(string) {
_string = string
}
[index] { _string.codePointAt_(index) }
iterate(iterator) { _string.iterate(iterator) }
iteratorValue(iterator) { _string.codePointAt_(iterator) }
count { _string.count }
}
class List is Sequence {
addAll(other) {
for (element in other) {
add(element)
}
return other
}
toString { "[%(join(", "))]" }
+(other) {
var result = this[0..-1]
for (element in other) {
result.add(element)
}
return result
}
*(count) {
if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.")
}
var result = []
for (i in 0...count) {
result.addAll(this)
}
return result
}
}
class Map is Sequence {
keys { MapKeySequence.new(this) }
values { MapValueSequence.new(this) }
toString {
var first = true
var result = "{"
for (key in keys) {
if (!first) result = result + ", "
first = false
result = result + "%(key): %(this[key])"
}
return result + "}"
}
iteratorValue(iterator) {
return MapEntry.new(
keyIteratorValue_(iterator),
valueIteratorValue_(iterator))
}
}
class MapEntry {
construct new(key, value) {
_key = key
_value = value
}
key { _key }
value { _value }
toString { "%(_key):%(_value)" }
}
class MapKeySequence is Sequence {
construct new(map) {
_map = map
}
iterate(n) { _map.iterate(n) }
iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }
}
class MapValueSequence is Sequence {
construct new(map) {
_map = map
}
iterate(n) { _map.iterate(n) }
iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }
}
class Range is Sequence {}
class System {
static print() {
writeString_("\n")
}
static print(obj) {
writeObject_(obj)
writeString_("\n")
return obj
}
static printAll(sequence) {
for (object in sequence) writeObject_(object)
writeString_("\n")
}
static write(obj) {
writeObject_(obj)
return obj
}
static writeAll(sequence) {
for (object in sequence) writeObject_(object)
}
static writeObject_(obj) {
var string = obj.toString
if (string is String) {
writeString_(string)
} else {
writeString_("[invalid toString]")
}
}
}

View File

@ -0,0 +1,440 @@
// Generated automatically from src/vm/wren_core.wren. Do not edit.
static const char* coreModuleSource =
"class Bool {}\n"
"class Fiber {}\n"
"class Fn {}\n"
"class Null {}\n"
"class Num {}\n"
"\n"
"class Sequence {\n"
" all(f) {\n"
" var result = true\n"
" for (element in this) {\n"
" result = f.call(element)\n"
" if (!result) return result\n"
" }\n"
" return result\n"
" }\n"
"\n"
" any(f) {\n"
" var result = false\n"
" for (element in this) {\n"
" result = f.call(element)\n"
" if (result) return result\n"
" }\n"
" return result\n"
" }\n"
"\n"
" contains(element) {\n"
" for (item in this) {\n"
" if (element == item) return true\n"
" }\n"
" return false\n"
" }\n"
"\n"
" count {\n"
" var result = 0\n"
" for (element in this) {\n"
" result = result + 1\n"
" }\n"
" return result\n"
" }\n"
"\n"
" count(f) {\n"
" var result = 0\n"
" for (element in this) {\n"
" if (f.call(element)) result = result + 1\n"
" }\n"
" return result\n"
" }\n"
"\n"
" each(f) {\n"
" for (element in this) {\n"
" f.call(element)\n"
" }\n"
" }\n"
"\n"
" isEmpty { iterate(null) ? false : true }\n"
"\n"
" map(transformation) { MapSequence.new(this, transformation) }\n"
"\n"
" skip(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" return SkipSequence.new(this, count)\n"
" }\n"
"\n"
" take(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" return TakeSequence.new(this, count)\n"
" }\n"
"\n"
" where(predicate) { WhereSequence.new(this, predicate) }\n"
"\n"
" reduce(acc, f) {\n"
" for (element in this) {\n"
" acc = f.call(acc, element)\n"
" }\n"
" return acc\n"
" }\n"
"\n"
" reduce(f) {\n"
" var iter = iterate(null)\n"
" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n"
"\n"
" // Seed with the first element.\n"
" var result = iteratorValue(iter)\n"
" while (iter = iterate(iter)) {\n"
" result = f.call(result, iteratorValue(iter))\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" join() { join(\"\") }\n"
"\n"
" join(sep) {\n"
" var first = true\n"
" var result = \"\"\n"
"\n"
" for (element in this) {\n"
" if (!first) result = result + sep\n"
" first = false\n"
" result = result + element.toString\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" toList {\n"
" var result = List.new()\n"
" for (element in this) {\n"
" result.add(element)\n"
" }\n"
" return result\n"
" }\n"
"}\n"
"\n"
"class MapSequence is Sequence {\n"
" construct new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
"\n"
" iterate(iterator) { _sequence.iterate(iterator) }\n"
" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n"
"}\n"
"\n"
"class SkipSequence is Sequence {\n"
" construct new(sequence, count) {\n"
" _sequence = sequence\n"
" _count = count\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" if (iterator) {\n"
" return _sequence.iterate(iterator)\n"
" } else {\n"
" iterator = _sequence.iterate(iterator)\n"
" var count = _count\n"
" while (count > 0 && iterator) {\n"
" iterator = _sequence.iterate(iterator)\n"
" count = count - 1\n"
" }\n"
" return iterator\n"
" }\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class TakeSequence is Sequence {\n"
" construct new(sequence, count) {\n"
" _sequence = sequence\n"
" _count = count\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" if (!iterator) _taken = 1 else _taken = _taken + 1\n"
" return _taken > _count ? null : _sequence.iterate(iterator)\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class WhereSequence is Sequence {\n"
" construct new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" while (iterator = _sequence.iterate(iterator)) {\n"
" if (_fn.call(_sequence.iteratorValue(iterator))) break\n"
" }\n"
" return iterator\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class String is Sequence {\n"
" bytes { StringByteSequence.new(this) }\n"
" codePoints { StringCodePointSequence.new(this) }\n"
"\n"
" split(delimiter) {\n"
" if (!(delimiter is String) || delimiter.isEmpty) {\n"
" Fiber.abort(\"Delimiter must be a non-empty string.\")\n"
" }\n"
"\n"
" var result = []\n"
"\n"
" var last = 0\n"
" var index = 0\n"
"\n"
" var delimSize = delimiter.byteCount_\n"
" var size = byteCount_\n"
"\n"
" while (last < size && (index = indexOf(delimiter, last)) != -1) {\n"
" result.add(this[last...index])\n"
" last = index + delimSize\n"
" }\n"
"\n"
" if (last < size) {\n"
" result.add(this[last..-1])\n"
" } else {\n"
" result.add(\"\")\n"
" }\n"
" return result\n"
" }\n"
"\n"
" replace(from, to) {\n"
" if (!(from is String) || from.isEmpty) {\n"
" Fiber.abort(\"From must be a non-empty string.\")\n"
" } else if (!(to is String)) {\n"
" Fiber.abort(\"To must be a string.\")\n"
" }\n"
"\n"
" var result = \"\"\n"
"\n"
" var last = 0\n"
" var index = 0\n"
"\n"
" var fromSize = from.byteCount_\n"
" var size = byteCount_\n"
"\n"
" while (last < size && (index = indexOf(from, last)) != -1) {\n"
" result = result + this[last...index] + to\n"
" last = index + fromSize\n"
" }\n"
"\n"
" if (last < size) result = result + this[last..-1]\n"
"\n"
" return result\n"
" }\n"
"\n"
" trim() { trim_(\"\t\r\n \", true, true) }\n"
" trim(chars) { trim_(chars, true, true) }\n"
" trimEnd() { trim_(\"\t\r\n \", false, true) }\n"
" trimEnd(chars) { trim_(chars, false, true) }\n"
" trimStart() { trim_(\"\t\r\n \", true, false) }\n"
" trimStart(chars) { trim_(chars, true, false) }\n"
"\n"
" trim_(chars, trimStart, trimEnd) {\n"
" if (!(chars is String)) {\n"
" Fiber.abort(\"Characters must be a string.\")\n"
" }\n"
"\n"
" var codePoints = chars.codePoints.toList\n"
"\n"
" var start\n"
" if (trimStart) {\n"
" while (start = iterate(start)) {\n"
" if (!codePoints.contains(codePointAt_(start))) break\n"
" }\n"
"\n"
" if (start == false) return \"\"\n"
" } else {\n"
" start = 0\n"
" }\n"
"\n"
" var end\n"
" if (trimEnd) {\n"
" end = byteCount_ - 1\n"
" while (end >= start) {\n"
" var codePoint = codePointAt_(end)\n"
" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n"
" end = end - 1\n"
" }\n"
"\n"
" if (end < start) return \"\"\n"
" } else {\n"
" end = -1\n"
" }\n"
"\n"
" return this[start..end]\n"
" }\n"
"\n"
" *(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" var result = \"\"\n"
" for (i in 0...count) {\n"
" result = result + this\n"
" }\n"
" return result\n"
" }\n"
"}\n"
"\n"
"class StringByteSequence is Sequence {\n"
" construct new(string) {\n"
" _string = string\n"
" }\n"
"\n"
" [index] { _string.byteAt_(index) }\n"
" iterate(iterator) { _string.iterateByte_(iterator) }\n"
" iteratorValue(iterator) { _string.byteAt_(iterator) }\n"
"\n"
" count { _string.byteCount_ }\n"
"}\n"
"\n"
"class StringCodePointSequence is Sequence {\n"
" construct new(string) {\n"
" _string = string\n"
" }\n"
"\n"
" [index] { _string.codePointAt_(index) }\n"
" iterate(iterator) { _string.iterate(iterator) }\n"
" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n"
"\n"
" count { _string.count }\n"
"}\n"
"\n"
"class List is Sequence {\n"
" addAll(other) {\n"
" for (element in other) {\n"
" add(element)\n"
" }\n"
" return other\n"
" }\n"
"\n"
" toString { \"[%(join(\", \"))]\" }\n"
"\n"
" +(other) {\n"
" var result = this[0..-1]\n"
" for (element in other) {\n"
" result.add(element)\n"
" }\n"
" return result\n"
" }\n"
"\n"
" *(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.addAll(this)\n"
" }\n"
" return result\n"
" }\n"
"}\n"
"\n"
"class Map is Sequence {\n"
" keys { MapKeySequence.new(this) }\n"
" values { MapValueSequence.new(this) }\n"
"\n"
" toString {\n"
" var first = true\n"
" var result = \"{\"\n"
"\n"
" for (key in keys) {\n"
" if (!first) result = result + \", \"\n"
" first = false\n"
" result = result + \"%(key): %(this[key])\"\n"
" }\n"
"\n"
" return result + \"}\"\n"
" }\n"
"\n"
" iteratorValue(iterator) {\n"
" return MapEntry.new(\n"
" keyIteratorValue_(iterator),\n"
" valueIteratorValue_(iterator))\n"
" }\n"
"}\n"
"\n"
"class MapEntry {\n"
" construct new(key, value) {\n"
" _key = key\n"
" _value = value\n"
" }\n"
"\n"
" key { _key }\n"
" value { _value }\n"
"\n"
" toString { \"%(_key):%(_value)\" }\n"
"}\n"
"\n"
"class MapKeySequence is Sequence {\n"
" construct new(map) {\n"
" _map = map\n"
" }\n"
"\n"
" iterate(n) { _map.iterate(n) }\n"
" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n"
"}\n"
"\n"
"class MapValueSequence is Sequence {\n"
" construct new(map) {\n"
" _map = map\n"
" }\n"
"\n"
" iterate(n) { _map.iterate(n) }\n"
" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n"
"}\n"
"\n"
"class Range is Sequence {}\n"
"\n"
"class System {\n"
" static print() {\n"
" writeString_(\"\n\")\n"
" }\n"
"\n"
" static print(obj) {\n"
" writeObject_(obj)\n"
" writeString_(\"\n\")\n"
" return obj\n"
" }\n"
"\n"
" static printAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" writeString_(\"\n\")\n"
" }\n"
"\n"
" static write(obj) {\n"
" writeObject_(obj)\n"
" return obj\n"
" }\n"
"\n"
" static writeAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" }\n"
"\n"
" static writeObject_(obj) {\n"
" var string = obj.toString\n"
" if (string is String) {\n"
" writeString_(string)\n"
" } else {\n"
" writeString_(\"[invalid toString]\")\n"
" }\n"
" }\n"
"}\n";

View File

@ -0,0 +1,387 @@
#include <stdio.h>
#include "wren_debug.h"
void wrenDebugPrintStackTrace(WrenVM* vm)
{
// Bail if the host doesn't enable printing errors.
if (vm->config.errorFn == NULL) return;
ObjFiber* fiber = vm->fiber;
if (IS_STRING(fiber->error))
{
vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
NULL, -1, AS_CSTRING(fiber->error));
}
else
{
// TODO: Print something a little useful here. Maybe the name of the error's
// class?
vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
NULL, -1, "[error object]");
}
for (int i = fiber->numFrames - 1; i >= 0; i--)
{
CallFrame* frame = &fiber->frames[i];
ObjFn* fn = frame->closure->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.
if (fn->module->name == NULL) continue;
// -1 because IP has advanced past the instruction that it just executed.
int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1];
vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE,
fn->module->name->value, line,
fn->debug->name);
}
}
static void dumpObject(Obj* obj)
{
switch (obj->type)
{
case OBJ_CLASS:
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
break;
case OBJ_CLOSURE: printf("[closure %p]", obj); break;
case OBJ_FIBER: printf("[fiber %p]", obj); break;
case OBJ_FN: printf("[fn %p]", obj); break;
case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
case OBJ_INSTANCE: printf("[instance %p]", obj); break;
case OBJ_LIST: printf("[list %p]", obj); break;
case OBJ_MAP: printf("[map %p]", obj); break;
case OBJ_MODULE: printf("[module %p]", obj); break;
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 %d]", obj->type); break;
}
}
void wrenDumpValue(Value value)
{
#if WREN_NAN_TAGGING
if (IS_NUM(value))
{
printf("%.14g", AS_NUM(value));
}
else if (IS_OBJ(value))
{
dumpObject(AS_OBJ(value));
}
else
{
switch (GET_TAG(value))
{
case TAG_FALSE: printf("false"); break;
case TAG_NAN: printf("NaN"); break;
case TAG_NULL: printf("null"); break;
case TAG_TRUE: printf("true"); break;
case TAG_UNDEFINED: UNREACHABLE();
}
}
#else
switch (value.type)
{
case VAL_FALSE: printf("false"); break;
case VAL_NULL: printf("null"); break;
case VAL_NUM: printf("%.14g", AS_NUM(value)); break;
case VAL_TRUE: printf("true"); break;
case VAL_OBJ: dumpObject(AS_OBJ(value)); break;
case VAL_UNDEFINED: UNREACHABLE();
}
#endif
}
static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
{
int start = i;
uint8_t* bytecode = fn->code.data;
Code code = (Code)bytecode[i];
int line = fn->debug->sourceLines.data[i];
if (lastLine == NULL || *lastLine != line)
{
printf("%4d:", line);
if (lastLine != NULL) *lastLine = line;
}
else
{
printf(" ");
}
printf(" %04d ", i++);
#define READ_BYTE() (bytecode[i++])
#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1])
#define BYTE_INSTRUCTION(name) \
printf("%-16s %5d\n", name, READ_BYTE()); \
break; \
switch (code)
{
case CODE_CONSTANT:
{
int constant = READ_SHORT();
printf("%-16s %5d '", "CONSTANT", constant);
wrenDumpValue(fn->constants.data[constant]);
printf("'\n");
break;
}
case CODE_NULL: printf("NULL\n"); break;
case CODE_FALSE: printf("FALSE\n"); break;
case CODE_TRUE: printf("TRUE\n"); break;
case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break;
case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break;
case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break;
case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break;
case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break;
case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break;
case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break;
case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break;
case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break;
case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL");
case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL");
case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE");
case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE");
case CODE_LOAD_MODULE_VAR:
{
int slot = READ_SHORT();
printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot,
fn->module->variableNames.data[slot]->value);
break;
}
case CODE_STORE_MODULE_VAR:
{
int slot = READ_SHORT();
printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot,
fn->module->variableNames.data[slot]->value);
break;
}
case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS");
case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS");
case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD");
case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD");
case CODE_POP: printf("POP\n"); break;
case CODE_CALL_0:
case CODE_CALL_1:
case CODE_CALL_2:
case CODE_CALL_3:
case CODE_CALL_4:
case CODE_CALL_5:
case CODE_CALL_6:
case CODE_CALL_7:
case CODE_CALL_8:
case CODE_CALL_9:
case CODE_CALL_10:
case CODE_CALL_11:
case CODE_CALL_12:
case CODE_CALL_13:
case CODE_CALL_14:
case CODE_CALL_15:
case CODE_CALL_16:
{
int numArgs = bytecode[i - 1] - CODE_CALL_0;
int symbol = READ_SHORT();
printf("CALL_%-11d %5d '%s'\n", numArgs, symbol,
vm->methodNames.data[symbol]->value);
break;
}
case CODE_SUPER_0:
case CODE_SUPER_1:
case CODE_SUPER_2:
case CODE_SUPER_3:
case CODE_SUPER_4:
case CODE_SUPER_5:
case CODE_SUPER_6:
case CODE_SUPER_7:
case CODE_SUPER_8:
case CODE_SUPER_9:
case CODE_SUPER_10:
case CODE_SUPER_11:
case CODE_SUPER_12:
case CODE_SUPER_13:
case CODE_SUPER_14:
case CODE_SUPER_15:
case CODE_SUPER_16:
{
int numArgs = bytecode[i - 1] - CODE_SUPER_0;
int symbol = READ_SHORT();
int superclass = READ_SHORT();
printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol,
vm->methodNames.data[symbol]->value, superclass);
break;
}
case CODE_JUMP:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "JUMP", offset, i + offset);
break;
}
case CODE_LOOP:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "LOOP", offset, i - offset);
break;
}
case CODE_JUMP_IF:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset);
break;
}
case CODE_AND:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "AND", offset, i + offset);
break;
}
case CODE_OR:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "OR", offset, i + offset);
break;
}
case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break;
case CODE_RETURN: printf("RETURN\n"); break;
case CODE_CLOSURE:
{
int constant = READ_SHORT();
printf("%-16s %5d ", "CLOSURE", constant);
wrenDumpValue(fn->constants.data[constant]);
printf(" ");
ObjFn* loadedFn = AS_FN(fn->constants.data[constant]);
for (int j = 0; j < loadedFn->numUpvalues; j++)
{
int isLocal = READ_BYTE();
int index = READ_BYTE();
if (j > 0) printf(", ");
printf("%s %d", isLocal ? "local" : "upvalue", index);
}
printf("\n");
break;
}
case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break;
case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break;
case CODE_CLASS:
{
int numFields = READ_BYTE();
printf("%-16s %5d fields\n", "CLASS", numFields);
break;
}
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
case CODE_METHOD_INSTANCE:
{
int symbol = READ_SHORT();
printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol,
vm->methodNames.data[symbol]->value);
break;
}
case CODE_METHOD_STATIC:
{
int symbol = READ_SHORT();
printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol,
vm->methodNames.data[symbol]->value);
break;
}
case CODE_END_MODULE:
printf("END_MODULE\n");
break;
case CODE_IMPORT_MODULE:
{
int name = READ_SHORT();
printf("%-16s %5d '", "IMPORT_MODULE", name);
wrenDumpValue(fn->constants.data[name]);
printf("'\n");
break;
}
case CODE_IMPORT_VARIABLE:
{
int variable = READ_SHORT();
printf("%-16s %5d '", "IMPORT_VARIABLE", variable);
wrenDumpValue(fn->constants.data[variable]);
printf("'\n");
break;
}
case CODE_END:
printf("END\n");
break;
default:
printf("UKNOWN! [%d]\n", bytecode[i - 1]);
break;
}
// Return how many bytes this instruction takes, or -1 if it's an END.
if (code == CODE_END) return -1;
return i - start;
#undef READ_BYTE
#undef READ_SHORT
}
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i)
{
return dumpInstruction(vm, fn, i, NULL);
}
void wrenDumpCode(WrenVM* vm, ObjFn* fn)
{
printf("%s: %s\n",
fn->module->name == NULL ? "<core>" : fn->module->name->value,
fn->debug->name);
int i = 0;
int lastLine = -1;
for (;;)
{
int offset = dumpInstruction(vm, fn, i, &lastLine);
if (offset == -1) break;
i += offset;
}
printf("\n");
}
void wrenDumpStack(ObjFiber* fiber)
{
printf("(fiber %p) ", fiber);
for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++)
{
wrenDumpValue(*slot);
printf(" | ");
}
printf("\n");
}

View File

@ -0,0 +1,27 @@
#ifndef wren_debug_h
#define wren_debug_h
#include "wren_value.h"
#include "wren_vm.h"
// Prints the stack trace for the current fiber.
//
// Used when a fiber throws a runtime error which is not caught.
void wrenDebugPrintStackTrace(WrenVM* vm);
// The "dump" functions are used for debugging Wren itself. Normal code paths
// will not call them unless one of the various DEBUG_ flags is enabled.
// Prints a representation of [value] to stdout.
void wrenDumpValue(Value value);
// Prints a representation of the bytecode for [fn] at instruction [i].
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i);
// Prints the disassembled code for [fn] to stdout.
void wrenDumpCode(WrenVM* vm, ObjFn* fn);
// Prints the contents of the current stack for [fiber] to stdout.
void wrenDumpStack(ObjFiber* fiber);
#endif

View File

@ -0,0 +1,213 @@
// This defines the bytecode instructions used by the VM. It does so by invoking
// an OPCODE() macro which is expected to be defined at the point that this is
// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.)
//
// The first argument is the name of the opcode. The second is its "stack
// effect" -- the amount that the op code changes the size of the stack. A
// stack effect of 1 means it pushes a value and the stack grows one larger.
// -2 means it pops two values, etc.
//
// Note that the order of instructions here affects the order of the dispatch
// table in the VM's interpreter loop. That in turn affects caching which
// affects overall performance. Take care to run benchmarks if you change the
// order here.
// Load the constant at index [arg].
OPCODE(CONSTANT, 1)
// Push null onto the stack.
OPCODE(NULL, 1)
// Push false onto the stack.
OPCODE(FALSE, 1)
// Push true onto the stack.
OPCODE(TRUE, 1)
// Pushes the value in the given local slot.
OPCODE(LOAD_LOCAL_0, 1)
OPCODE(LOAD_LOCAL_1, 1)
OPCODE(LOAD_LOCAL_2, 1)
OPCODE(LOAD_LOCAL_3, 1)
OPCODE(LOAD_LOCAL_4, 1)
OPCODE(LOAD_LOCAL_5, 1)
OPCODE(LOAD_LOCAL_6, 1)
OPCODE(LOAD_LOCAL_7, 1)
OPCODE(LOAD_LOCAL_8, 1)
// Note: The compiler assumes the following _STORE instructions always
// immediately follow their corresponding _LOAD ones.
// Pushes the value in local slot [arg].
OPCODE(LOAD_LOCAL, 1)
// Stores the top of stack in local slot [arg]. Does not pop it.
OPCODE(STORE_LOCAL, 0)
// Pushes the value in upvalue [arg].
OPCODE(LOAD_UPVALUE, 1)
// Stores the top of stack in upvalue [arg]. Does not pop it.
OPCODE(STORE_UPVALUE, 0)
// Pushes the value of the top-level variable in slot [arg].
OPCODE(LOAD_MODULE_VAR, 1)
// Stores the top of stack in top-level variable slot [arg]. Does not pop it.
OPCODE(STORE_MODULE_VAR, 0)
// Pushes the value of the field in slot [arg] of the receiver of the current
// function. This is used for regular field accesses on "this" directly in
// methods. This instruction is faster than the more general CODE_LOAD_FIELD
// instruction.
OPCODE(LOAD_FIELD_THIS, 1)
// Stores the top of the stack in field slot [arg] in the receiver of the
// current value. Does not pop the value. This instruction is faster than the
// more general CODE_LOAD_FIELD instruction.
OPCODE(STORE_FIELD_THIS, 0)
// Pops an instance and pushes the value of the field in slot [arg] of it.
OPCODE(LOAD_FIELD, 0)
// Pops an instance and stores the subsequent top of stack in field slot
// [arg] in it. Does not pop the value.
OPCODE(STORE_FIELD, -1)
// Pop and discard the top of stack.
OPCODE(POP, -1)
// Invoke the method with symbol [arg]. The number indicates the number of
// arguments (not including the receiver).
OPCODE(CALL_0, 0)
OPCODE(CALL_1, -1)
OPCODE(CALL_2, -2)
OPCODE(CALL_3, -3)
OPCODE(CALL_4, -4)
OPCODE(CALL_5, -5)
OPCODE(CALL_6, -6)
OPCODE(CALL_7, -7)
OPCODE(CALL_8, -8)
OPCODE(CALL_9, -9)
OPCODE(CALL_10, -10)
OPCODE(CALL_11, -11)
OPCODE(CALL_12, -12)
OPCODE(CALL_13, -13)
OPCODE(CALL_14, -14)
OPCODE(CALL_15, -15)
OPCODE(CALL_16, -16)
// Invoke a superclass method with symbol [arg]. The number indicates the
// number of arguments (not including the receiver).
OPCODE(SUPER_0, 0)
OPCODE(SUPER_1, -1)
OPCODE(SUPER_2, -2)
OPCODE(SUPER_3, -3)
OPCODE(SUPER_4, -4)
OPCODE(SUPER_5, -5)
OPCODE(SUPER_6, -6)
OPCODE(SUPER_7, -7)
OPCODE(SUPER_8, -8)
OPCODE(SUPER_9, -9)
OPCODE(SUPER_10, -10)
OPCODE(SUPER_11, -11)
OPCODE(SUPER_12, -12)
OPCODE(SUPER_13, -13)
OPCODE(SUPER_14, -14)
OPCODE(SUPER_15, -15)
OPCODE(SUPER_16, -16)
// Jump the instruction pointer [arg] forward.
OPCODE(JUMP, 0)
// Jump the instruction pointer [arg] backward.
OPCODE(LOOP, 0)
// Pop and if not truthy then jump the instruction pointer [arg] forward.
OPCODE(JUMP_IF, -1)
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and
// continue.
OPCODE(AND, -1)
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
// and continue.
OPCODE(OR, -1)
// Close the upvalue for the local on the top of the stack, then pop it.
OPCODE(CLOSE_UPVALUE, -1)
// Exit from the current function and return the value on the top of the
// stack.
OPCODE(RETURN, 0)
// Creates a closure for the function stored at [arg] in the constant table.
//
// Following the function argument is a number of arguments, two for each
// upvalue. The first is true if the variable being captured is a local (as
// opposed to an upvalue), and the second is the index of the local or
// upvalue being captured.
//
// Pushes the created closure.
OPCODE(CLOSURE, 1)
// Creates a new instance of a class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT, 0)
// Creates a new instance of a foreign class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(FOREIGN_CONSTRUCT, 0)
// Creates a class. Top of stack is the superclass. Below that is a string for
// the name of the class. Byte [arg] is the number of fields in the class.
OPCODE(CLASS, -1)
// Creates a foreign class. Top of stack is the superclass. Below that is a
// string for the name of the class.
OPCODE(FOREIGN_CLASS, -1)
// Define a method for symbol [arg]. The class receiving the method is popped
// off the stack, then the function defining the body is popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_INSTANCE, -2)
// Define a method for symbol [arg]. The class whose metaclass will receive
// the method is popped off the stack, then the function defining the body is
// popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_STATIC, -2)
// This is executed at the end of the module's body. Pushes NULL onto the stack
// as the "return value" of the import statement and stores the module as the
// most recently imported one.
OPCODE(END_MODULE, 1)
// Import a module whose name is the string stored at [arg] in the constant
// table.
//
// Pushes null onto the stack so that the fiber for the imported module can
// replace that with a dummy value when it returns. (Fibers always return a
// value when resuming a caller.)
OPCODE(IMPORT_MODULE, 1)
// Import a variable from the most recently imported module. The name of the
// variable to import is at [arg] in the constant table. Pushes the loaded
// variable's value.
OPCODE(IMPORT_VARIABLE, 1)
// This pseudo-instruction indicates the end of the bytecode. It should
// always be preceded by a `CODE_RETURN`, so is never actually executed.
OPCODE(END, 0)

View File

@ -0,0 +1,125 @@
#include "wren_primitive.h"
#include <math.h>
// Validates that [value] is an integer within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value. If invalid, reports an error and returns `UINT32_MAX`.
static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value,
const char* argName)
{
if (!validateIntValue(vm, value, argName)) return UINT32_MAX;
// Negative indices count from the end.
if (value < 0) value = count + value;
// Check bounds.
if (value >= 0 && value < count) return (uint32_t)value;
vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName);
return UINT32_MAX;
}
bool validateFn(WrenVM* vm, Value arg, const char* argName)
{
if (IS_CLOSURE(arg)) return true;
vm->fiber->error = wrenStringFormat(vm, "$ must be a function.", argName);
return false;
}
bool validateNum(WrenVM* vm, Value arg, const char* argName)
{
if (IS_NUM(arg)) return true;
RETURN_ERROR_FMT("$ must be a number.", argName);
}
bool validateIntValue(WrenVM* vm, double value, const char* argName)
{
if (trunc(value) == value) return true;
RETURN_ERROR_FMT("$ must be an integer.", argName);
}
bool validateInt(WrenVM* vm, Value arg, const char* argName)
{
// Make sure it's a number first.
if (!validateNum(vm, arg, argName)) return false;
return validateIntValue(vm, AS_NUM(arg), argName);
}
bool validateKey(WrenVM* vm, Value arg)
{
if (IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) ||
IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg))
{
return true;
}
RETURN_ERROR("Key must be a value type.");
}
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
const char* argName)
{
if (!validateNum(vm, arg, argName)) return UINT32_MAX;
return validateIndexValue(vm, count, AS_NUM(arg), argName);
}
bool validateString(WrenVM* vm, Value arg, const char* argName)
{
if (IS_STRING(arg)) return true;
RETURN_ERROR_FMT("$ must be a string.", argName);
}
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
int* step)
{
*step = 0;
// Edge case: an empty range is allowed at the end of a sequence. This way,
// list[0..-1] and list[0...list.count] can be used to copy a list even when
// empty.
if (range->from == *length &&
range->to == (range->isInclusive ? -1.0 : (double)*length))
{
*length = 0;
return 0;
}
uint32_t from = validateIndexValue(vm, *length, range->from, "Range start");
if (from == UINT32_MAX) return UINT32_MAX;
// Bounds check the end manually to handle exclusive ranges.
double value = range->to;
if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX;
// Negative indices count from the end.
if (value < 0) value = *length + value;
// Convert the exclusive range to an inclusive one.
if (!range->isInclusive)
{
// An exclusive range with the same start and end points is empty.
if (value == from)
{
*length = 0;
return from;
}
// Shift the endpoint to make it inclusive, handling both increasing and
// decreasing ranges.
value += value >= from ? -1 : 1;
}
// Check bounds.
if (value < 0 || value >= *length)
{
vm->fiber->error = CONST_STRING(vm, "Range end out of bounds.");
return UINT32_MAX;
}
uint32_t to = (uint32_t)value;
*length = abs((int)(from - to)) + 1;
*step = from < to ? 1 : -1;
return from;
}

View File

@ -0,0 +1,88 @@
#ifndef wren_primitive_h
#define wren_primitive_h
#include "wren_vm.h"
// Binds a primitive method named [name] (in Wren) implemented using C function
// [fn] to `ObjClass` [cls].
#define PRIMITIVE(cls, name, function) \
{ \
int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
Method method; \
method.type = METHOD_PRIMITIVE; \
method.as.primitive = prim_##function; \
wrenBindMethod(vm, cls, symbol, method); \
}
// Defines a primitive method whose C function name is [name]. This abstracts
// the actual type signature of a primitive function and makes it clear which C
// functions are invoked as primitives.
#define DEF_PRIMITIVE(name) \
static bool prim_##name(WrenVM* vm, Value* args)
#define RETURN_VAL(value) do { args[0] = value; return true; } while (0)
#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj))
#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value))
#define RETURN_FALSE RETURN_VAL(FALSE_VAL)
#define RETURN_NULL RETURN_VAL(NULL_VAL)
#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value))
#define RETURN_TRUE RETURN_VAL(TRUE_VAL)
#define RETURN_ERROR(msg) \
do { \
vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \
return false; \
} while (0);
#define RETURN_ERROR_FMT(msg, arg) \
do { \
vm->fiber->error = wrenStringFormat(vm, msg, arg); \
return false; \
} while (0);
// Validates that the given [arg] is a function. Returns true if it is. If not,
// reports an error and returns false.
bool validateFn(WrenVM* vm, Value arg, const char* argName);
// Validates that the given [arg] is a Num. Returns true if it is. If not,
// reports an error and returns false.
bool validateNum(WrenVM* vm, Value arg, const char* argName);
// Validates that [value] is an integer. Returns true if it is. If not, reports
// an error and returns false.
bool validateIntValue(WrenVM* vm, double value, const char* argName);
// Validates that the given [arg] is an integer. Returns true if it is. If not,
// reports an error and returns false.
bool validateInt(WrenVM* vm, Value arg, const char* argName);
// Validates that [arg] is a valid object for use as a map key. Returns true if
// it is. If not, reports an error and returns false.
bool validateKey(WrenVM* vm, Value arg);
// Validates that the argument at [argIndex] is an integer within `[0, count)`.
// Also allows negative indices which map backwards from the end. Returns the
// valid positive index value. If invalid, reports an error and returns
// `UINT32_MAX`.
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
const char* argName);
// Validates that the given [arg] is a String. Returns true if it is. If not,
// reports an error and returns false.
bool validateString(WrenVM* vm, Value arg, const char* argName);
// Given a [range] and the [length] of the object being operated on, determines
// the series of elements that should be chosen from the underlying object.
// Handles ranges that count backwards from the end as well as negative ranges.
//
// Returns the index from which the range should start or `UINT32_MAX` if the
// range is invalid. After calling, [length] will be updated with the number of
// elements in the resulting sequence. [step] will be direction that the range
// is going: `1` if the range is increasing from the start index or `-1` if the
// range is decreasing.
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
int* step);
#endif

View File

@ -0,0 +1,196 @@
#include <string.h>
#include "wren_utils.h"
#include "wren_vm.h"
DEFINE_BUFFER(Byte, uint8_t);
DEFINE_BUFFER(Int, int);
DEFINE_BUFFER(String, ObjString*);
void wrenSymbolTableInit(SymbolTable* symbols)
{
wrenStringBufferInit(symbols);
}
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols)
{
wrenStringBufferClear(vm, symbols);
}
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length)
{
ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length));
wrenPushRoot(vm, &symbol->obj);
wrenStringBufferWrite(vm, symbols, symbol);
wrenPopRoot(vm);
return symbols->count - 1;
}
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length)
{
// See if the symbol is already defined.
int existing = wrenSymbolTableFind(symbols, name, length);
if (existing != -1) return existing;
// New symbol, so add it.
return wrenSymbolTableAdd(vm, symbols, name, length);
}
int wrenSymbolTableFind(const SymbolTable* symbols,
const char* name, size_t length)
{
// See if the symbol is already defined.
// TODO: O(n). Do something better.
for (int i = 0; i < symbols->count; i++)
{
if (wrenStringEqualsCString(symbols->data[i], name, length)) return i;
}
return -1;
}
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable)
{
for (int i = 0; i < symbolTable->count; i++)
{
wrenGrayObj(vm, &symbolTable->data[i]->obj);
}
// Keep track of how much memory is still in use.
vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data);
}
int wrenUtf8EncodeNumBytes(int value)
{
ASSERT(value >= 0, "Cannot encode a negative value.");
if (value <= 0x7f) return 1;
if (value <= 0x7ff) return 2;
if (value <= 0xffff) return 3;
if (value <= 0x10ffff) return 4;
return 0;
}
int wrenUtf8Encode(int value, uint8_t* bytes)
{
if (value <= 0x7f)
{
// Single byte (i.e. fits in ASCII).
*bytes = value & 0x7f;
return 1;
}
else if (value <= 0x7ff)
{
// Two byte sequence: 110xxxxx 10xxxxxx.
*bytes = 0xc0 | ((value & 0x7c0) >> 6);
bytes++;
*bytes = 0x80 | (value & 0x3f);
return 2;
}
else if (value <= 0xffff)
{
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
*bytes = 0xe0 | ((value & 0xf000) >> 12);
bytes++;
*bytes = 0x80 | ((value & 0xfc0) >> 6);
bytes++;
*bytes = 0x80 | (value & 0x3f);
return 3;
}
else if (value <= 0x10ffff)
{
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
*bytes = 0xf0 | ((value & 0x1c0000) >> 18);
bytes++;
*bytes = 0x80 | ((value & 0x3f000) >> 12);
bytes++;
*bytes = 0x80 | ((value & 0xfc0) >> 6);
bytes++;
*bytes = 0x80 | (value & 0x3f);
return 4;
}
// Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629
UNREACHABLE();
return 0;
}
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length)
{
// Single byte (i.e. fits in ASCII).
if (*bytes <= 0x7f) return *bytes;
int value;
uint32_t remainingBytes;
if ((*bytes & 0xe0) == 0xc0)
{
// Two byte sequence: 110xxxxx 10xxxxxx.
value = *bytes & 0x1f;
remainingBytes = 1;
}
else if ((*bytes & 0xf0) == 0xe0)
{
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
value = *bytes & 0x0f;
remainingBytes = 2;
}
else if ((*bytes & 0xf8) == 0xf0)
{
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
value = *bytes & 0x07;
remainingBytes = 3;
}
else
{
// Invalid UTF-8 sequence.
return -1;
}
// Don't read past the end of the buffer on truncated UTF-8.
if (remainingBytes > length - 1) return -1;
while (remainingBytes > 0)
{
bytes++;
remainingBytes--;
// Remaining bytes must be of form 10xxxxxx.
if ((*bytes & 0xc0) != 0x80) return -1;
value = value << 6 | (*bytes & 0x3f);
}
return value;
}
int wrenUtf8DecodeNumBytes(uint8_t byte)
{
// If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so
// don't count it at all.
if ((byte & 0xc0) == 0x80) return 0;
// The first byte's high bits tell us how many bytes are in the UTF-8
// sequence.
if ((byte & 0xf8) == 0xf0) return 4;
if ((byte & 0xf0) == 0xe0) return 3;
if ((byte & 0xe0) == 0xc0) return 2;
return 1;
}
// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
int wrenPowerOf2Ceil(int n)
{
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
return n;
}

View File

@ -0,0 +1,121 @@
#ifndef wren_utils_h
#define wren_utils_h
#include "wren.h"
#include "wren_common.h"
// Reusable data structures and other utility functions.
// Forward declare this here to break a cycle between wren_utils.h and
// wren_value.h.
typedef struct sObjString ObjString;
// We need buffers of a few different types. To avoid lots of casting between
// void* and back, we'll use the preprocessor as a poor man's generics and let
// it generate a few type-specific ones.
#define DECLARE_BUFFER(name, type) \
typedef struct \
{ \
type* data; \
int count; \
int capacity; \
} name##Buffer; \
void wren##name##BufferInit(name##Buffer* buffer); \
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count); \
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data)
// This should be used once for each type instantiation, somewhere in a .c file.
#define DEFINE_BUFFER(name, type) \
void wren##name##BufferInit(name##Buffer* buffer) \
{ \
buffer->data = NULL; \
buffer->capacity = 0; \
buffer->count = 0; \
} \
\
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
{ \
wrenReallocate(vm, buffer->data, 0, 0); \
wren##name##BufferInit(buffer); \
} \
\
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count) \
{ \
if (buffer->capacity < buffer->count + count) \
{ \
int capacity = wrenPowerOf2Ceil(buffer->count + count); \
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
buffer->capacity = capacity; \
} \
\
for (int i = 0; i < count; i++) \
{ \
buffer->data[buffer->count++] = data; \
} \
} \
\
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
{ \
wren##name##BufferFill(vm, buffer, data, 1); \
}
DECLARE_BUFFER(Byte, uint8_t);
DECLARE_BUFFER(Int, int);
DECLARE_BUFFER(String, ObjString*);
// TODO: Change this to use a map.
typedef StringBuffer SymbolTable;
// Initializes the symbol table.
void wrenSymbolTableInit(SymbolTable* symbols);
// Frees all dynamically allocated memory used by the symbol table, but not the
// SymbolTable itself.
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols);
// Adds name to the symbol table. Returns the index of it in the table.
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length);
// Adds name to the symbol table. Returns the index of it in the table. Will
// use an existing symbol if already present.
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length);
// Looks up name in the symbol table. Returns its index if found or -1 if not.
int wrenSymbolTableFind(const SymbolTable* symbols,
const char* name, size_t length);
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable);
// Returns the number of bytes needed to encode [value] in UTF-8.
//
// Returns 0 if [value] is too large to encode.
int wrenUtf8EncodeNumBytes(int value);
// Encodes value as a series of bytes in [bytes], which is assumed to be large
// enough to hold the encoded result.
//
// Returns the number of written bytes.
int wrenUtf8Encode(int value, uint8_t* bytes);
// Decodes the UTF-8 sequence starting at [bytes] (which has max [length]),
// returning the code point.
//
// Returns -1 if the bytes are not a valid UTF-8 sequence.
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length);
// Returns the number of bytes in the UTF-8 sequence starting with [byte].
//
// If the character at that index is not the beginning of a UTF-8 sequence,
// returns 0.
int wrenUtf8DecodeNumBytes(uint8_t byte);
// Returns the smallest power of two that is equal to or greater than [n].
int wrenPowerOf2Ceil(int n);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,876 @@
#ifndef wren_value_h
#define wren_value_h
#include <stdbool.h>
#include <string.h>
#include "wren_common.h"
#include "wren_utils.h"
// This defines the built-in types and their core representations in memory.
// Since Wren is dynamically typed, any variable can hold a value of any type,
// and the type can change at runtime. Implementing this efficiently is
// critical for performance.
//
// The main type exposed by this is [Value]. A C variable of that type is a
// storage location that can hold any Wren value. The stack, module variables,
// and instance fields are all implemented in C as variables of type Value.
//
// The built-in types for booleans, numbers, and null are unboxed: their value
// is stored directly in the Value, and copying a Value copies the value. Other
// types--classes, instances of classes, functions, lists, and strings--are all
// reference types. They are stored on the heap and the Value just stores a
// pointer to it. Copying the Value copies a reference to the same object. The
// Wren implementation calls these "Obj", or objects, though to a user, all
// values are objects.
//
// There is also a special singleton value "undefined". It is used internally
// but never appears as a real value to a user. It has two uses:
//
// - It is used to identify module variables that have been implicitly declared
// by use in a forward reference but not yet explicitly declared. These only
// exist during compilation and do not appear at runtime.
//
// - It is used to represent unused map entries in an ObjMap.
//
// There are two supported Value representations. The main one uses a technique
// called "NaN tagging" (explained in detail below) to store a number, any of
// the value types, or a pointer, all inside one double-precision floating
// point number. A larger, slower, Value type that uses a struct to store these
// is also supported, and is useful for debugging the VM.
//
// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
// defined, Nan tagging is used.
// These macros cast a Value to one of the specific object types. These do *not*
// perform any validation, so must only be used after the Value has been
// ensured to be the right type.
#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass*
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule*
#define AS_NUM(value) (wrenValueToNum(value)) // double
#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange*
#define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString*
#define AS_CSTRING(v) (AS_STRING(v)->value) // const char*
// These macros promote a primitive C value to a full Wren Value. There are
// more defined below that are specific to the Nan tagged or other
// representation.
#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean
#define NUM_VAL(num) (wrenNumToValue(num)) // double
#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___*
// These perform type tests on a Value, returning `true` if the Value is of the
// given type.
#define IS_BOOL(value) (wrenIsBool(value)) // Bool
#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
#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
// Creates a new string object from [text], which should be a bare C string
// literal. This determines the length of the string automatically at compile
// time based on the size of the character array (-1 for the terminating '\0').
#define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1)
// Identifies which specific type a heap-allocated object is.
typedef enum {
OBJ_CLASS,
OBJ_CLOSURE,
OBJ_FIBER,
OBJ_FN,
OBJ_FOREIGN,
OBJ_INSTANCE,
OBJ_LIST,
OBJ_MAP,
OBJ_MODULE,
OBJ_RANGE,
OBJ_STRING,
OBJ_UPVALUE
} ObjType;
typedef struct sObjClass ObjClass;
// Base struct for all heap-allocated objects.
typedef struct sObj Obj;
struct sObj
{
ObjType type;
bool isDark;
// The object's class.
ObjClass* classObj;
// The next object in the linked list of all currently allocated objects.
struct sObj* next;
};
#if WREN_NAN_TAGGING
typedef uint64_t Value;
#else
typedef enum
{
VAL_FALSE,
VAL_NULL,
VAL_NUM,
VAL_TRUE,
VAL_UNDEFINED,
VAL_OBJ
} ValueType;
typedef struct
{
ValueType type;
union
{
double num;
Obj* obj;
} as;
} Value;
#endif
DECLARE_BUFFER(Value, Value);
// A heap-allocated string object.
struct sObjString
{
Obj obj;
// Number of bytes in the string, not including the null terminator.
uint32_t length;
// The hash value of the string's contents.
uint32_t hash;
// Inline array of the string's bytes followed by a null terminator.
char value[FLEXIBLE_ARRAY];
};
// The dynamically allocated data structure for a variable that has been used
// by a closure. Whenever a function accesses a variable declared in an
// enclosing function, it will get to it through this.
//
// An upvalue can be either "closed" or "open". An open upvalue points directly
// to a [Value] that is still stored on the fiber's stack because the local
// variable is still in scope in the function where it's declared.
//
// When that local variable goes out of scope, the upvalue pointing to it will
// be closed. When that happens, the value gets copied off the stack into the
// upvalue itself. That way, it can have a longer lifetime than the stack
// variable.
typedef struct sObjUpvalue
{
// The object header. Note that upvalues have this because they are garbage
// collected, but they are not first class Wren objects.
Obj obj;
// Pointer to the variable this upvalue is referencing.
Value* value;
// If the upvalue is closed (i.e. the local variable it was pointing too has
// been popped off the stack) then the closed-over value will be hoisted out
// of the stack into here. [value] will then be changed to point to this.
Value closed;
// Open upvalues are stored in a linked list by the fiber. This points to the
// next upvalue in that list.
struct sObjUpvalue* next;
} ObjUpvalue;
// The type of a primitive function.
//
// Primitives are similiar to foreign functions, but have more direct access to
// VM internals. It is passed the arguments in [args]. If it returns a value,
// it places it in `args[0]` and returns `true`. If it causes a runtime error
// or modifies the running fiber, it returns `false`.
typedef bool (*Primitive)(WrenVM* vm, Value* args);
// TODO: See if it's actually a perf improvement to have this in a separate
// struct instead of in ObjFn.
// Stores debugging information for a function used for things like stack
// traces.
typedef struct
{
// The name of the function. Heap allocated and owned by the FnDebug.
char* name;
// An array of line numbers. There is one element in this array for each
// bytecode in the function's bytecode array. The value of that element is
// the line in the source code that generated that instruction.
IntBuffer sourceLines;
} FnDebug;
// A loaded module and the top-level variables it defines.
//
// While this is an Obj and is managed by the GC, it never appears as a
// first-class object in Wren.
typedef struct
{
Obj obj;
// The currently defined top-level variables.
ValueBuffer variables;
// Symbol table for the names of all module variables. Indexes here directly
// correspond to entries in [variables].
SymbolTable variableNames;
// The name of the module.
ObjString* name;
} ObjModule;
// A function object. It wraps and owns the bytecode and other debug information
// for a callable chunk of code.
//
// Function objects are not passed around and invoked directly. Instead, they
// are always referenced by an [ObjClosure] which is the real first-class
// representation of a function. This isn't strictly necessary if they function
// has no upvalues, but lets the rest of the VM assume all called objects will
// be closures.
typedef struct
{
Obj obj;
ByteBuffer code;
ValueBuffer constants;
// The module where this function was defined.
ObjModule* module;
// The maximum number of stack slots this function may use.
int maxSlots;
// The number of upvalues this function closes over.
int numUpvalues;
// The number of parameters this function expects. Used to ensure that .call
// handles a mismatch between number of parameters and arguments. This will
// only be set for fns, and not ObjFns that represent methods or scripts.
int arity;
FnDebug* debug;
} ObjFn;
// An instance of a first-class function and the environment it has closed over.
// Unlike [ObjFn], this has captured the upvalues that the function accesses.
typedef struct
{
Obj obj;
// The function that this closure is an instance of.
ObjFn* fn;
// The upvalues this function has closed over.
ObjUpvalue* upvalues[FLEXIBLE_ARRAY];
} ObjClosure;
typedef struct
{
// Pointer to the current (really next-to-be-executed) instruction in the
// function's bytecode.
uint8_t* ip;
// The closure being executed.
ObjClosure* closure;
// Pointer to the first stack slot used by this call frame. This will contain
// the receiver, followed by the function's parameters, then local variables
// and temporaries.
Value* stackStart;
} CallFrame;
// Tracks how this fiber has been invoked, aside from the ways that can be
// detected from the state of other fields in the fiber.
typedef enum
{
// The fiber is being run from another fiber using a call to `try()`.
FIBER_TRY,
// The fiber was directly invoked by `runInterpreter()`. This means it's the
// initial fiber used by a call to `wrenCall()` or `wrenInterpret()`.
FIBER_ROOT,
// The fiber is invoked some other way. If [caller] is `NULL` then the fiber
// was invoked using `call()`. If [numFrames] is zero, then the fiber has
// finished running and is done. If [numFrames] is one and that frame's `ip`
// points to the first byte of code, the fiber has not been started yet.
FIBER_OTHER,
} FiberState;
typedef struct sObjFiber
{
Obj obj;
// The stack of value slots. This is used for holding local variables and
// temporaries while the fiber is executing. It heap-allocated and grown as
// needed.
Value* stack;
// A pointer to one past the top-most value on the stack.
Value* stackTop;
// The number of allocated slots in the stack array.
int stackCapacity;
// The stack of call frames. This is a dynamic array that grows as needed but
// never shrinks.
CallFrame* frames;
// The number of frames currently in use in [frames].
int numFrames;
// The number of [frames] allocated.
int frameCapacity;
// Pointer to the first node in the linked list of open upvalues that are
// pointing to values still on the stack. The head of the list will be the
// upvalue closest to the top of the stack, and then the list works downwards.
ObjUpvalue* openUpvalues;
// The fiber that ran this one. If this fiber is yielded, control will resume
// to this one. May be `NULL`.
struct sObjFiber* caller;
// If the fiber failed because of a runtime error, this will contain the
// error object. Otherwise, it will be null.
Value error;
FiberState state;
} ObjFiber;
typedef enum
{
// A primitive method implemented in C in the VM. Unlike foreign methods,
// this can directly manipulate the fiber's stack.
METHOD_PRIMITIVE,
// A externally-defined C method.
METHOD_FOREIGN,
// A normal user-defined method.
METHOD_BLOCK,
// No method for the given symbol.
METHOD_NONE
} MethodType;
typedef struct
{
MethodType type;
// The method function itself. The [type] determines which field of the union
// is used.
union
{
Primitive primitive;
WrenForeignMethodFn foreign;
ObjClosure* closure;
} as;
} Method;
DECLARE_BUFFER(Method, Method);
struct sObjClass
{
Obj obj;
ObjClass* superclass;
// The number of fields needed for an instance of this class, including all
// of its superclass fields.
int numFields;
// The table of methods that are defined in or inherited by this class.
// Methods are called by symbol, and the symbol directly maps to an index in
// this table. This makes method calls fast at the expense of empty cells in
// the list for methods the class doesn't support.
//
// You can think of it as a hash table that never has collisions but has a
// really low load factor. Since methods are pretty small (just a type and a
// pointer), this should be a worthwhile trade-off.
MethodBuffer methods;
// The name of the class.
ObjString* name;
};
typedef struct
{
Obj obj;
uint8_t data[FLEXIBLE_ARRAY];
} ObjForeign;
typedef struct
{
Obj obj;
Value fields[FLEXIBLE_ARRAY];
} ObjInstance;
typedef struct
{
Obj obj;
// The elements in the list.
ValueBuffer elements;
} ObjList;
typedef struct
{
// The entry's key, or UNDEFINED_VAL if the entry is not in use.
Value key;
// The value associated with the key. If the key is UNDEFINED_VAL, this will
// be false to indicate an open available entry or true to indicate a
// tombstone -- an entry that was previously in use but was then deleted.
Value value;
} MapEntry;
// A hash table mapping keys to values.
//
// We use something very simple: open addressing with linear probing. The hash
// table is an array of entries. Each entry is a key-value pair. If the key is
// the special UNDEFINED_VAL, it indicates no value is currently in that slot.
// Otherwise, it's a valid key, and the value is the value associated with it.
//
// When entries are added, the array is dynamically scaled by GROW_FACTOR to
// keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map
// gets empty enough, it will be resized to a smaller array. When this happens,
// all existing entries are rehashed and re-added to the new array.
//
// When an entry is removed, its slot is replaced with a "tombstone". This is an
// entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing
// for a key, we will continue past tombstones, because the desired key may be
// found after them if the key that was removed was part of a prior collision.
// When the array gets resized, all tombstones are discarded.
typedef struct
{
Obj obj;
// The number of entries allocated.
uint32_t capacity;
// The number of entries in the map.
uint32_t count;
// Pointer to a contiguous array of [capacity] entries.
MapEntry* entries;
} ObjMap;
typedef struct
{
Obj obj;
// The beginning of the range.
double from;
// The end of the range. May be greater or less than [from].
double to;
// True if [to] is included in the range.
bool isInclusive;
} ObjRange;
// An IEEE 754 double-precision float is a 64-bit value with bits laid out like:
//
// 1 Sign bit
// | 11 Exponent bits
// | | 52 Mantissa (i.e. fraction) bits
// | | |
// S[Exponent-][Mantissa------------------------------------------]
//
// The details of how these are used to represent numbers aren't really
// relevant here as long we don't interfere with them. The important bit is NaN.
//
// An IEEE double can represent a few magical values like NaN ("not a number"),
// Infinity, and -Infinity. A NaN is any value where all exponent bits are set:
//
// v--NaN bits
// -11111111111----------------------------------------------------
//
// Here, "-" means "doesn't matter". Any bit sequence that matches the above is
// a NaN. With all of those "-", it obvious there are a *lot* of different
// bit patterns that all mean the same thing. NaN tagging takes advantage of
// this. We'll use those available bit patterns to represent things other than
// numbers without giving up any valid numeric values.
//
// NaN values come in two flavors: "signalling" and "quiet". The former are
// intended to halt execution, while the latter just flow through arithmetic
// operations silently. We want the latter. Quiet NaNs are indicated by setting
// the highest mantissa bit:
//
// v--Highest mantissa bit
// -[NaN ]1---------------------------------------------------
//
// If all of the NaN bits are set, it's not a number. Otherwise, it is.
// That leaves all of the remaining bits as available for us to play with. We
// stuff a few different kinds of things here: special singleton values like
// "true", "false", and "null", and pointers to objects allocated on the heap.
// We'll use the sign bit to distinguish singleton values from pointers. If
// it's set, it's a pointer.
//
// v--Pointer or singleton?
// S[NaN ]1---------------------------------------------------
//
// For singleton values, we just enumerate the different values. We'll use the
// low bits of the mantissa for that, and only need a few:
//
// 3 Type bits--v
// 0[NaN ]1------------------------------------------------[T]
//
// For pointers, we are left with 51 bits of mantissa to store an address.
// That's more than enough room for a 32-bit address. Even 64-bit machines
// only actually use 48 bits for addresses, so we've got plenty. We just stuff
// the address right into the mantissa.
//
// Ta-da, double precision numbers, pointers, and a bunch of singleton values,
// all stuffed into a single 64-bit sequence. Even better, we don't have to
// do any masking or work to extract number values: they are unmodified. This
// means math on numbers is fast.
#if WREN_NAN_TAGGING
// A mask that selects the sign bit.
#define SIGN_BIT ((uint64_t)1 << 63)
// The bits that must be set to indicate a quiet NaN.
#define QNAN ((uint64_t)0x7ffc000000000000)
// If the NaN bits are set, it's not a number.
#define IS_NUM(value) (((value) & QNAN) != QNAN)
// An object pointer is a NaN with a set sign bit.
#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
#define IS_FALSE(value) ((value) == FALSE_VAL)
#define IS_NULL(value) ((value) == NULL_VAL)
#define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL)
// Masks out the tag bits used to identify the singleton value.
#define MASK_TAG (7)
// Tag values for the different singleton values.
#define TAG_NAN (0)
#define TAG_NULL (1)
#define TAG_FALSE (2)
#define TAG_TRUE (3)
#define TAG_UNDEFINED (4)
#define TAG_UNUSED2 (5)
#define TAG_UNUSED3 (6)
#define TAG_UNUSED4 (7)
// Value -> 0 or 1.
#define AS_BOOL(value) ((value) == TRUE_VAL)
// Value -> Obj*.
#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN)))
// Singleton values.
#define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL))
#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE))
#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE))
#define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED))
// Gets the singleton type tag for a Value (which must be a singleton).
#define GET_TAG(value) ((int)((value) & MASK_TAG))
#else
// Value -> 0 or 1.
#define AS_BOOL(value) ((value).type == VAL_TRUE)
// Value -> Obj*.
#define AS_OBJ(v) ((v).as.obj)
// Determines if [value] is a garbage-collected object or not.
#define IS_OBJ(value) ((value).type == VAL_OBJ)
#define IS_FALSE(value) ((value).type == VAL_FALSE)
#define IS_NULL(value) ((value).type == VAL_NULL)
#define IS_NUM(value) ((value).type == VAL_NUM)
#define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED)
// Singleton values.
#define FALSE_VAL ((Value){ VAL_FALSE, { 0 } })
#define NULL_VAL ((Value){ VAL_NULL, { 0 } })
#define TRUE_VAL ((Value){ VAL_TRUE, { 0 } })
#define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } })
#endif
// A union to let us reinterpret a double as raw bits and back.
typedef union
{
uint64_t bits64;
uint32_t bits32[2];
double num;
} DoubleBits;
// Creates a new "raw" class. It has no metaclass or superclass whatsoever.
// This is only used for bootstrapping the initial Object and Class classes,
// which are a little special.
ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name);
// Makes [superclass] the superclass of [subclass], and causes subclass to
// inherit its methods. This should be called before any methods are defined
// on subclass.
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass);
// Creates a new class object as well as its associated metaclass.
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjString* name);
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method);
// Creates a new closure object that invokes [fn]. Allocates room for its
// upvalues, but assumes outside code will populate it.
ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
// Creates a new fiber object that will invoke [closure].
ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure);
// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at
// [stackStart].
static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
ObjClosure* closure, Value* stackStart)
{
// The caller should have ensured we already have enough capacity.
ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame.");
CallFrame* frame = &fiber->frames[fiber->numFrames++];
frame->stackStart = stackStart;
frame->closure = closure;
frame->ip = closure->fn->code.data;
}
// Ensures [fiber]'s stack has at least [needed] slots.
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
static inline bool wrenHasError(const ObjFiber* fiber)
{
return !IS_NULL(fiber->error);
}
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
// Creates a new empty function. Before being used, it must have code,
// constants, etc. added to it.
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots);
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length);
// Creates a new instance of the given [classObj].
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj);
// Creates a new list with [numElements] elements (which are left
// uninitialized.)
ObjList* wrenNewList(WrenVM* vm, uint32_t numElements);
// Inserts [value] in [list] at [index], shifting down the other elements.
void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index);
// Removes and returns the item at [index] from [list].
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index);
// Creates a new empty map.
ObjMap* wrenNewMap(WrenVM* vm);
// Looks up [key] in [map]. If found, returns the value. Otherwise, returns
// `UNDEFINED_VAL`.
Value wrenMapGet(ObjMap* map, Value key);
// Associates [key] with [value] in [map].
void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value);
void wrenMapClear(WrenVM* vm, ObjMap* map);
// Removes [key] from [map], if present. Returns the value for the key if found
// or `NULL_VAL` otherwise.
Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key);
// Creates a new module.
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name);
// Creates a new range from [from] to [to].
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive);
// Creates a new string object and copies [text] into it.
//
// [text] must be non-NULL.
Value wrenNewString(WrenVM* vm, const char* text);
// Creates a new string object of [length] and copies [text] into it.
//
// [text] may be NULL if [length] is zero.
Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length);
// Creates a new string object by taking a range of characters from [source].
// The range starts at [start], contains [count] bytes, and increments by
// [step].
Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start,
uint32_t count, int step);
// Produces a string representation of [value].
Value wrenNumToString(WrenVM* vm, double value);
// Creates a new formatted string from [format] and any additional arguments
// used in the format string.
//
// This is a very restricted flavor of formatting, intended only for internal
// use by the VM. Two formatting characters are supported, each of which reads
// the next argument as a certain type:
//
// $ - A C string.
// @ - A Wren string object.
Value wrenStringFormat(WrenVM* vm, const char* format, ...);
// Creates a new string containing the UTF-8 encoding of [value].
Value wrenStringFromCodePoint(WrenVM* vm, int value);
// Creates a new string from the integer representation of a byte
Value wrenStringFromByte(WrenVM* vm, uint8_t value);
// Creates a new string containing the code point in [string] starting at byte
// [index]. If [index] points into the middle of a UTF-8 sequence, returns an
// empty string.
Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index);
// Search for the first occurence of [needle] within [haystack] and returns its
// zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain
// [needle].
uint32_t wrenStringFind(ObjString* haystack, ObjString* needle,
uint32_t startIndex);
// Returns true if [a] and [b] represent the same string.
static inline bool wrenStringEqualsCString(const ObjString* a,
const char* b, size_t length)
{
return a->length == length && memcmp(a->value, b, length) == 0;
}
// Creates a new open upvalue pointing to [value] on the stack.
ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value);
// Mark [obj] as reachable and still in use. This should only be called
// during the sweep phase of a garbage collection.
void wrenGrayObj(WrenVM* vm, Obj* obj);
// Mark [value] as reachable and still in use. This should only be called
// during the sweep phase of a garbage collection.
void wrenGrayValue(WrenVM* vm, Value value);
// Mark the values in [buffer] as reachable and still in use. This should only
// be called during the sweep phase of a garbage collection.
void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer);
// Processes every object in the gray stack until all reachable objects have
// been marked. After that, all objects are either white (freeable) or black
// (in use and fully traversed).
void wrenBlackenObjects(WrenVM* vm);
// Releases all memory owned by [obj], including [obj] itself.
void wrenFreeObj(WrenVM* vm, Obj* obj);
// Returns the class of [value].
//
// Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps
// performance (significantly) in some cases, but degrades it in others. The
// ones used by the implementation were chosen to give the best results in the
// benchmarks.
ObjClass* wrenGetClass(WrenVM* vm, Value value);
// Returns true if [a] and [b] are strictly the same value. This is identity
// for object values, and value equality for unboxed values.
static inline bool wrenValuesSame(Value a, Value b)
{
#if WREN_NAN_TAGGING
// Value types have unique bit representations and we compare object types
// by identity (i.e. pointer), so all we need to do is compare the bits.
return a == b;
#else
if (a.type != b.type) return false;
if (a.type == VAL_NUM) return a.as.num == b.as.num;
return a.as.obj == b.as.obj;
#endif
}
// Returns true if [a] and [b] are equivalent. Immutable values (null, bools,
// numbers, ranges, and strings) are equal if they have the same data. All
// other values are equal if they are identical objects.
bool wrenValuesEqual(Value a, Value b);
// Returns true if [value] is a bool. Do not call this directly, instead use
// [IS_BOOL].
static inline bool wrenIsBool(Value value)
{
#if WREN_NAN_TAGGING
return value == TRUE_VAL || value == FALSE_VAL;
#else
return value.type == VAL_FALSE || value.type == VAL_TRUE;
#endif
}
// Returns true if [value] is an object of type [type]. Do not call this
// directly, instead use the [IS___] macro for the type in question.
static inline bool wrenIsObjType(Value value, ObjType type)
{
return IS_OBJ(value) && AS_OBJ(value)->type == type;
}
// Converts the raw object pointer [obj] to a [Value].
static inline Value wrenObjectToValue(Obj* obj)
{
#if WREN_NAN_TAGGING
// The triple casting is necessary here to satisfy some compilers:
// 1. (uintptr_t) Convert the pointer to a number of the right size.
// 2. (uint64_t) Pad it up to 64 bits in 32-bit builds.
// 3. Or in the bits to make a tagged Nan.
// 4. Cast to a typedef'd value.
return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj));
#else
Value value;
value.type = VAL_OBJ;
value.as.obj = obj;
return value;
#endif
}
// Interprets [value] as a [double].
static inline double wrenValueToNum(Value value)
{
#if WREN_NAN_TAGGING
DoubleBits data;
data.bits64 = value;
return data.num;
#else
return value.as.num;
#endif
}
// Converts [num] to a [Value].
static inline Value wrenNumToValue(double num)
{
#if WREN_NAN_TAGGING
DoubleBits data;
data.num = num;
return data.bits64;
#else
Value value;
value.type = VAL_NUM;
value.as.num = num;
return value;
#endif
}
#endif

1778
src/logic/wren/vm/wren_vm.c Normal file

File diff suppressed because it is too large Load Diff

236
src/logic/wren/vm/wren_vm.h Normal file
View File

@ -0,0 +1,236 @@
#ifndef wren_vm_h
#define wren_vm_h
#include "wren_common.h"
#include "wren_compiler.h"
#include "wren_value.h"
#include "wren_utils.h"
// The maximum number of temporary objects that can be made visible to the GC
// at one time.
#define WREN_MAX_TEMP_ROOTS 5
typedef enum
{
#define OPCODE(name, _) CODE_##name,
#include "wren_opcodes.h"
#undef OPCODE
} Code;
// A handle to a value, basically just a linked list of extra GC roots.
//
// Note that even non-heap-allocated values can be stored here.
struct WrenHandle
{
Value value;
WrenHandle* prev;
WrenHandle* next;
};
struct WrenVM
{
ObjClass* boolClass;
ObjClass* classClass;
ObjClass* fiberClass;
ObjClass* fnClass;
ObjClass* listClass;
ObjClass* mapClass;
ObjClass* nullClass;
ObjClass* numClass;
ObjClass* objectClass;
ObjClass* rangeClass;
ObjClass* stringClass;
// The fiber that is currently running.
ObjFiber* fiber;
// The loaded modules. Each key is an ObjString (except for the main module,
// whose key is null) for the module's name and the value is the ObjModule
// for the module.
ObjMap* modules;
// The most recently imported module. More specifically, the module whose
// code has most recently finished executing.
//
// Not treated like a GC root since the module is already in [modules].
ObjModule* lastModule;
// Memory management data:
// The number of bytes that are known to be currently allocated. Includes all
// memory that was proven live after the last GC, as well as any new bytes
// that were allocated since then. Does *not* include bytes for objects that
// were freed since the last GC.
size_t bytesAllocated;
// The number of total allocated bytes that will trigger the next GC.
size_t nextGC;
// The first object in the linked list of all currently allocated objects.
Obj* first;
// The "gray" set for the garbage collector. This is the stack of unprocessed
// objects while a garbage collection pass is in process.
Obj** gray;
int grayCount;
int grayCapacity;
// The list of temporary roots. This is for temporary or new objects that are
// not otherwise reachable but should not be collected.
//
// They are organized as a stack of pointers stored in this array. This
// implies that temporary roots need to have stack semantics: only the most
// recently pushed object can be released.
Obj* tempRoots[WREN_MAX_TEMP_ROOTS];
int numTempRoots;
// Pointer to the first node in the linked list of active handles or NULL if
// there are none.
WrenHandle* handles;
// 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;
// Compiler and debugger data:
// The compiler that is currently compiling code. This is used so that heap
// allocated objects used by the compiler can be found if a GC is kicked off
// in the middle of a compile.
Compiler* compiler;
// There is a single global symbol table for all method names on all classes.
// Method calls are dispatched directly by index in this table.
SymbolTable methodNames;
};
// A generic allocation function that handles all explicit memory management.
// It's used like so:
//
// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should
// return the allocated memory or NULL on failure.
//
// - To attempt to grow an existing allocation, [memory] is the memory,
// [oldSize] is its previous size, and [newSize] is the desired size.
// It should return [memory] if it was able to grow it in place, or a new
// pointer if it had to move it.
//
// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
// but it will always return [memory].
//
// - To free memory, [memory] will be the memory to free and [newSize] and
// [oldSize] will be zero. It should return NULL.
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
// Invoke the finalizer for the foreign object referenced by [foreign].
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign);
// Creates a new [WrenHandle] for [value].
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value);
// Compile [source] in the context of [module] and wrap in a fiber that can
// execute it.
//
// Returns NULL if a compile error occurred.
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module,
const char* source, bool isExpression,
bool printErrors);
// Looks up a variable from a previously-loaded module.
//
// Aborts the current fiber if the module or variable could not be found.
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName);
// Returns the value of the module-level variable named [name] in the main
// module.
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name);
// Adds a new implicitly declared top-level variable named [name] to [module]
// based on a use site occurring on [line].
//
// Does not check to see if a variable with that name is already declared or
// defined. Returns the symbol for the new variable or -2 if there are too many
// variables defined.
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, int line);
// Adds a new top-level variable named [name] to [module].
//
// Returns the symbol for the new variable, -1 if a variable with the given name
// is already defined, or -2 if there are too many variables defined.
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, Value value);
// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already.
static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber,
ObjClosure* closure, int numArgs)
{
// Grow the call frame array if needed.
if (fiber->numFrames + 1 > fiber->frameCapacity)
{
int max = fiber->frameCapacity * 2;
fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames,
sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max);
fiber->frameCapacity = max;
}
// Grow the stack if needed.
int stackSize = (int)(fiber->stackTop - fiber->stack);
int needed = stackSize + closure->fn->maxSlots;
wrenEnsureStack(vm, fiber, needed);
wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs);
}
// Marks [obj] as a GC root so that it doesn't get collected.
void wrenPushRoot(WrenVM* vm, Obj* obj);
// Removes the most recently pushed temporary root.
void wrenPopRoot(WrenVM* vm);
// Returns the class of [value].
//
// Defined here instead of in wren_value.h because it's critical that this be
// inlined. That means it must be defined in the header, but the wren_value.h
// header doesn't have a full definitely of WrenVM yet.
static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value)
{
if (IS_NUM(value)) return vm->numClass;
if (IS_OBJ(value)) return AS_OBJ(value)->classObj;
#if WREN_NAN_TAGGING
switch (GET_TAG(value))
{
case TAG_FALSE: return vm->boolClass; break;
case TAG_NAN: return vm->numClass; break;
case TAG_NULL: return vm->nullClass; break;
case TAG_TRUE: return vm->boolClass; break;
case TAG_UNDEFINED: UNREACHABLE();
}
#else
switch (value.type)
{
case VAL_FALSE: return vm->boolClass;
case VAL_NULL: return vm->nullClass;
case VAL_NUM: return vm->numClass;
case VAL_TRUE: return vm->boolClass;
case VAL_OBJ: return AS_OBJ(value)->classObj;
case VAL_UNDEFINED: UNREACHABLE();
}
#endif
UNREACHABLE();
return NULL;
}
#endif

11
src/main.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "engine/engine.hpp"
#include "logic/logic.hpp"
int main() {
Engine e;
Logic l(&e);
l.interpret();
return 0;
}