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.
|
||||
//
|
||||