initial commit

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

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

9
CMakeLists.txt Normal file
View file

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

5
src/CMakeLists.txt Normal file
View file

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

View file

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

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

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

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

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

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

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

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

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

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

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

View file

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

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

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

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

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

View file

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

View file

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

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

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

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

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

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

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

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

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

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

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

View file

@ -0,0 +1,488 @@
#ifndef wren_h
#define wren_h
#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>
// The Wren semantic version number components.
#define WREN_VERSION_MAJOR 0
#define WREN_VERSION_MINOR 1
#define WREN_VERSION_PATCH 0
// A human-friendly string representation of the version.
#define WREN_VERSION_STRING "0.1.0"
// A monotonically increasing numeric representation of the version number. Use
// this if you want to do range checks over versions.
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
WREN_VERSION_MINOR * 1000 + \
WREN_VERSION_PATCH)
// A single virtual machine for executing Wren code.
//
// Wren has no global state, so all state stored by a running interpreter lives
// here.
typedef struct WrenVM WrenVM;
// A handle to a Wren object.
//
// This lets code outside of the VM hold a persistent reference to an object.
// After a handle is acquired, and until it is released, this ensures the
// garbage collector will not reclaim the object it references.
typedef struct WrenHandle WrenHandle;
// A generic allocation function that handles all explicit memory management
// used by Wren. It's used like so:
//
// - To allocate new memory, [memory] is NULL and [newSize] is the desired
// size. It should return the allocated memory or NULL on failure.
//
// - To attempt to grow an existing allocation, [memory] is the memory, and
// [newSize] is the desired size. It should return [memory] if it was able to
// grow it in place, or a new pointer if it had to move it.
//
// - To shrink memory, [memory] and [newSize] are the same as above but it will
// always return [memory].
//
// - To free memory, [memory] will be the memory to free and [newSize] will be
// zero. It should return NULL.
typedef void* (*WrenReallocateFn)(void* memory, size_t newSize);
// A function callable from Wren code, but implemented in C.
typedef void (*WrenForeignMethodFn)(WrenVM* vm);
// A finalizer function for freeing resources owned by an instance of a foreign
// class. Unlike most foreign methods, finalizers do not have access to the VM
// and should not interact with it since it's in the middle of a garbage
// collection.
typedef void (*WrenFinalizerFn)(void* data);
// Gives the host a chance to canonicalize the imported module name,
// potentially taking into account the (previously resolved) name of the module
// that contains the import. Typically, this is used to implement relative
// imports.
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
const char* importer, const char* name);
// Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
// Returns a pointer to a foreign method on [className] in [module] with
// [signature].
typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
const char* module, const char* className, bool isStatic,
const char* signature);
// Displays a string of text to the user.
typedef void (*WrenWriteFn)(WrenVM* vm, const char* text);
typedef enum
{
// A syntax or resolution error detected at compile time.
WREN_ERROR_COMPILE,
// The error message for a runtime error.
WREN_ERROR_RUNTIME,
// One entry of a runtime error's stack trace.
WREN_ERROR_STACK_TRACE
} WrenErrorType;
// Reports an error to the user.
//
// An error detected during compile time is reported by calling this once with
// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line]
// where the error occurs, and the compiler's error [message].
//
// A runtime error is reported by calling this once with [type]
// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's
// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are
// made for each line in the stack trace. Each of those has the resolved
// [module] and [line] where the method or function is defined and [message] is
// the name of the method or function.
typedef void (*WrenErrorFn)(
WrenVM* vm, WrenErrorType type, const char* module, int line,
const char* message);
typedef struct
{
// The callback invoked when the foreign object is created.
//