initial commit
This commit is contained in:
commit
b6c60365ab
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal 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
5
src/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
add_subdirectory(engine)
|
||||
add_subdirectory(logic)
|
||||
|
||||
add_executable(sc-eng main.cpp)
|
||||
target_link_libraries(sc-eng engine logic)
|
6
src/engine/CMakeLists.txt
Normal file
6
src/engine/CMakeLists.txt
Normal 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
8
src/engine/engine.cpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include "engine.hpp"
|
||||
|
||||
Engine::Engine() {
|
||||
}
|
||||
|
||||
int Engine::get_info() {
|
||||
return 42;
|
||||
}
|
8
src/engine/engine.hpp
Normal file
8
src/engine/engine.hpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
Engine();
|
||||
|
||||
int get_info();
|
||||
};
|
8
src/logic/CMakeLists.txt
Normal file
8
src/logic/CMakeLists.txt
Normal 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
74
src/logic/logic.cpp
Normal 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
25
src/logic/logic.hpp
Normal 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;
|
||||
};
|
13
src/logic/wren/CMakeLists.txt
Normal file
13
src/logic/wren/CMakeLists.txt
Normal 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
24
src/logic/wren/README.md
Normal 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
40
src/logic/wren/cli/main.c
Normal 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();
|
||||
}
|
278
src/logic/wren/cli/modules.c
Normal file
278
src/logic/wren/cli/modules.c
Normal 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;
|
||||
}
|
23
src/logic/wren/cli/modules.h
Normal file
23
src/logic/wren/cli/modules.h
Normal 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
312
src/logic/wren/cli/path.c
Normal 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
65
src/logic/wren/cli/path.h
Normal 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
32
src/logic/wren/cli/stat.h
Normal 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
405
src/logic/wren/cli/vm.c
Normal 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
33
src/logic/wren/cli/vm.h
Normal 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
|
488
src/logic/wren/include/wren.h
Normal file
488
src/logic/wren/include/wren.h
Normal 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
|
11
src/logic/wren/include/wren.hpp
Normal file
11
src/logic/wren/include/wren.hpp
Normal 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
600
src/logic/wren/module/io.c
Normal 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);
|
||||
}
|
11
src/logic/wren/module/io.h
Normal file
11
src/logic/wren/module/io.h
Normal 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
|
303
src/logic/wren/module/io.wren
Normal file
303
src/logic/wren/module/io.wren
Normal 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()
|
||||
}
|
305
src/logic/wren/module/io.wren.inc
Normal file
305
src/logic/wren/module/io.wren.inc
Normal 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";
|
71
src/logic/wren/module/os.c
Normal file
71
src/logic/wren/module/os.c
Normal 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);
|
||||
}
|
||||
}
|
9
src/logic/wren/module/os.h
Normal file
9
src/logic/wren/module/os.h
Normal 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
|
13
src/logic/wren/module/os.wren
Normal file
13
src/logic/wren/module/os.wren
Normal 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
|
||||
}
|
15
src/logic/wren/module/os.wren.inc
Normal file
15
src/logic/wren/module/os.wren.inc
Normal 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";
|
3
src/logic/wren/module/repl.c
Normal file
3
src/logic/wren/module/repl.c
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "repl.h"
|
||||
#include "wren.h"
|
||||
|
6
src/logic/wren/module/repl.h
Normal file
6
src/logic/wren/module/repl.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef repl_h
|
||||
#define repl_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
#endif
|
954
src/logic/wren/module/repl.wren
Normal file
954
src/logic/wren/module/repl.wren
Normal 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()
|
||||
}
|
956
src/logic/wren/module/repl.wren.inc
Normal file
956
src/logic/wren/module/repl.wren.inc
Normal 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";
|
79
src/logic/wren/module/scheduler.c
Normal file
79
src/logic/wren/module/scheduler.c
Normal 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);
|
||||
}
|
20
src/logic/wren/module/scheduler.h
Normal file
20
src/logic/wren/module/scheduler.h
Normal 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
|
27
src/logic/wren/module/scheduler.wren
Normal file
27
src/logic/wren/module/scheduler.wren
Normal 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_()
|
29
src/logic/wren/module/scheduler.wren.inc
Normal file
29
src/logic/wren/module/scheduler.wren.inc
Normal 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";
|
39
src/logic/wren/module/timer.c
Normal file
39
src/logic/wren/module/timer.c
Normal 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);
|
||||
}
|
13
src/logic/wren/module/timer.wren
Normal file
13
src/logic/wren/module/timer.wren
Normal 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)
|
||||
}
|
15
src/logic/wren/module/timer.wren.inc
Normal file
15
src/logic/wren/module/timer.wren.inc
Normal 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";
|
96
src/logic/wren/optional/wren_opt_meta.c
Normal file
96
src/logic/wren/optional/wren_opt_meta.c
Normal 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
|
18
src/logic/wren/optional/wren_opt_meta.h
Normal file
18
src/logic/wren/optional/wren_opt_meta.h
Normal 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
|
32
src/logic/wren/optional/wren_opt_meta.wren
Normal file
32
src/logic/wren/optional/wren_opt_meta.wren
Normal 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)
|
||||
}
|
34
src/logic/wren/optional/wren_opt_meta.wren.inc
Normal file
34
src/logic/wren/optional/wren_opt_meta.wren.inc
Normal 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";
|
144
src/logic/wren/optional/wren_opt_random.c
Normal file
144
src/logic/wren/optional/wren_opt_random.c
Normal 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
|
20
src/logic/wren/optional/wren_opt_random.h
Normal file
20
src/logic/wren/optional/wren_opt_random.h
Normal 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
|
122
src/logic/wren/optional/wren_opt_random.wren
Normal file
122
src/logic/wren/optional/wren_opt_random.wren
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
124
src/logic/wren/optional/wren_opt_random.wren.inc
Normal file
124
src/logic/wren/optional/wren_opt_random.wren.inc
Normal 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";
|
202
src/logic/wren/vm/wren_common.h
Normal file
202
src/logic/wren/vm/wren_common.h
Normal 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
|
3595
src/logic/wren/vm/wren_compiler.c
Normal file
3595
src/logic/wren/vm/wren_compiler.c
Normal file
File diff suppressed because it is too large
Load diff
57
src/logic/wren/vm/wren_compiler.h
Normal file
57
src/logic/wren/vm/wren_compiler.h
Normal 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
|
1393
src/logic/wren/vm/wren_core.c
Normal file
1393
src/logic/wren/vm/wren_core.c
Normal file
File diff suppressed because it is too large
Load diff
23
src/logic/wren/vm/wren_core.h
Normal file
23
src/logic/wren/vm/wren_core.h
Normal 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
|
438
src/logic/wren/vm/wren_core.wren
Normal file
438
src/logic/wren/vm/wren_core.wren
Normal 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]")
|
||||
}
|
||||
}
|
||||
}
|
440
src/logic/wren/vm/wren_core.wren.inc
Normal file
440
src/logic/wren/vm/wren_core.wren.inc
Normal 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";
|
387
src/logic/wren/vm/wren_debug.c
Normal file
387
src/logic/wren/vm/wren_debug.c
Normal 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");
|
||||
}
|
27
src/logic/wren/vm/wren_debug.h
Normal file
27
src/logic/wren/vm/wren_debug.h
Normal 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
|
213
src/logic/wren/vm/wren_opcodes.h
Normal file
213
src/logic/wren/vm/wren_opcodes.h
Normal 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)
|
125
src/logic/wren/vm/wren_primitive.c
Normal file
125
src/logic/wren/vm/wren_primitive.c
Normal 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;
|
||||
}
|
88
src/logic/wren/vm/wren_primitive.h
Normal file
88
src/logic/wren/vm/wren_primitive.h
Normal 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
|
196
src/logic/wren/vm/wren_utils.c
Normal file
196
src/logic/wren/vm/wren_utils.c
Normal 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;
|
||||
}
|
121
src/logic/wren/vm/wren_utils.h
Normal file
121
src/logic/wren/vm/wren_utils.h
Normal 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
|
1315
src/logic/wren/vm/wren_value.c
Normal file
1315
src/logic/wren/vm/wren_value.c
Normal file
File diff suppressed because it is too large
Load diff
876
src/logic/wren/vm/wren_value.h
Normal file
876
src/logic/wren/vm/wren_value.h
Normal 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
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
236
src/logic/wren/vm/wren_vm.h
Normal 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
11
src/main.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "engine/engine.hpp"
|
||||
#include "logic/logic.hpp"
|
||||
|
||||
int main() {
|
||||
Engine e;
|
||||
Logic l(&e);
|
||||
|
||||
l.interpret();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue