feat: add libuv event loop integration with timer module and CLI restructuring

Add a vertical slice of real libuv integration by introducing a "timer" CLI module with a Timer class and static sleep() method. This required significant infrastructure changes: setting up libuv event loop in the CLI, creating a new src/module directory for CLI modules, updating all build scripts (Makefile, wren.mk, Xcode project) to handle the new module compilation, and reorganizing CLI code by moving REPL logic from main.c to vm.c. The generate_builtins.py script was also updated to support copying Wren sources into the module directory.
This commit is contained in:
Bob Nystrom
2015-08-07 15:10:55 +00:00
parent e7bdca2cfa
commit 810da1f0fc
13 changed files with 456 additions and 70 deletions
+19
View File
@@ -0,0 +1,19 @@
This contains the Wren source code. It is organized like so:
* `cli`: the source code for the command line interface. This is a custom
exectuable 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/
+4
View File
@@ -4,6 +4,8 @@
#include "io.h"
#include "timer.h"
char const* rootDirectory = NULL;
// Reads the contents of the file at [path] and returns it as a heap allocated
@@ -71,6 +73,8 @@ char* wrenFilePath(const char* name)
char* readModule(WrenVM* vm, const char* module)
{
if (strcmp(module, "timer") == 0) return timerGetSource();
// First try to load the module with a ".wren" extension.
char* modulePath = wrenFilePath(module);
char* moduleContents = readFile(modulePath);
+2 -33
View File
@@ -5,37 +5,6 @@
#include "vm.h"
#include "wren.h"
#define MAX_LINE_LENGTH 1024 // TODO: Something less arbitrary.
static int runRepl()
{
WrenVM* vm = createVM(NULL);
printf("\\\\/\"-\n");
printf(" \\_/ wren v0.0.0\n");
char line[MAX_LINE_LENGTH];
for (;;)
{
printf("> ");
if (!fgets(line, MAX_LINE_LENGTH, stdin))
{
printf("\n");
break;
}
// TODO: Handle failure.
wrenInterpret(vm, "Prompt", line);
// TODO: Automatically print the result of expressions.
}
wrenFreeVM(vm);
return 0;
}
int main(int argc, const char* argv[])
{
if (argc < 1 || argc > 2)
@@ -43,14 +12,14 @@ int main(int argc, const char* argv[])
fprintf(stderr, "Usage: wren [file]\n");
return 64; // EX_USAGE.
}
if (argc == 1)
{
runRepl();
}
else if (argc == 2)
{
runFile(NULL, argv[1]);
runFile(argv[1], NULL);
}
return 0;
+100 -9
View File
@@ -3,12 +3,41 @@
#include "io.h"
#include "vm.h"
#include "timer.h"
WrenVM* createVM(WrenBindForeignMethodFn bindForeign)
#define MAX_LINE_LENGTH 1024 // TODO: Something less arbitrary.
// The single VM instance that the CLI uses.
WrenVM* vm;
WrenBindForeignMethodFn externalBindForeign;
uv_loop_t* loop;
static WrenForeignMethodFn bindForeignMethod(WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature)
{
if (strcmp(module, "timer") == 0)
{
return timerBindForeign(vm, className, isStatic, signature);
}
if (externalBindForeign != NULL)
{
return externalBindForeign(vm, module, className, isStatic, signature);
}
return NULL;
}
static void initVM()
{
WrenConfiguration config;
config.bindForeignMethodFn = bindForeign;
config.bindForeignMethodFn = bindForeignMethod;
config.loadModuleFn = readModule;
// Since we're running in a standalone process, be generous with memory.
@@ -18,12 +47,28 @@ WrenVM* createVM(WrenBindForeignMethodFn bindForeign)
config.reallocateFn = NULL;
config.minHeapSize = 0;
config.heapGrowthPercent = 0;
return wrenNewVM(&config);
vm = wrenNewVM(&config);
// Initialize the event loop.
loop = (uv_loop_t*)malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
}
void runFile(WrenBindForeignMethodFn bindForeign, const char* path)
static void freeVM()
{
timerReleaseMethods();
uv_loop_close(loop);
free(loop);
wrenFreeVM(vm);
}
void runFile(const char* path, WrenBindForeignMethodFn bindForeign)
{
externalBindForeign = bindForeign;
// Use the directory where the file is as the root to resolve imports
// relative to.
char* root = NULL;
@@ -42,12 +87,18 @@ void runFile(WrenBindForeignMethodFn bindForeign, const char* path)
fprintf(stderr, "Could not find file \"%s\".\n", path);
exit(66);
}
WrenVM* vm = createVM(bindForeign);
initVM();
WrenInterpretResult result = wrenInterpret(vm, path, source);
if (result == WREN_RESULT_SUCCESS)
{
uv_run(loop, UV_RUN_DEFAULT);
}
wrenFreeVM(vm);
freeVM();
free(source);
free(root);
@@ -55,3 +106,43 @@ void runFile(WrenBindForeignMethodFn bindForeign, const char* path)
if (result == WREN_RESULT_COMPILE_ERROR) exit(65); // EX_DATAERR.
if (result == WREN_RESULT_RUNTIME_ERROR) exit(70); // EX_SOFTWARE.
}
int runRepl()
{
initVM();
printf("\\\\/\"-\n");
printf(" \\_/ wren v0.0.0\n");
char line[MAX_LINE_LENGTH];
for (;;)
{
printf("> ");
if (!fgets(line, MAX_LINE_LENGTH, stdin))
{
printf("\n");
break;
}
// TODO: Handle failure.
wrenInterpret(vm, "Prompt", line);
// TODO: Automatically print the result of expressions.
}
freeVM();
return 0;
}
WrenVM* getVM()
{
return vm;
}
uv_loop_t* getLoop()
{
return loop;
}
+13 -5
View File
@@ -1,14 +1,22 @@
#ifndef vm_h
#define vm_h
#include "uv.h"
#include "wren.h"
// Creates a new Wren VM with the CLI's module loader and other configuration.
WrenVM* createVM(WrenBindForeignMethodFn bindForeign);
// Executes the Wren script at [path] in a new VM.
//
// Exits if the script failed or could not be loaded.
void runFile(WrenBindForeignMethodFn bindForeign, const char* path);
// If [bindForeign] is not `NULL`, it will be called to register any foreign
// methods that the CLI itself doesn't handle.
void runFile(const char* path, WrenBindForeignMethodFn bindForeign);
// Runs the Wren interactive REPL.
int runRepl();
// Gets the currently running VM.
WrenVM* getVM();
// Gets the event loop the VM is using.
uv_loop_t* getLoop();
#endif
+81
View File
@@ -0,0 +1,81 @@
#include <stdlib.h>
#include <string.h>
#include "uv.h"
#include "timer.h"
#include "wren.h"
#include "vm.h"
// This is generated from builtin/module/timer.wren. Do not edit here.
static const char* timerLibSource =
"class Timer {\n"
" static sleep(milliseconds) {\n"
" startTimer_(milliseconds, Fiber.current)\n"
" Fiber.yield()\n"
" }\n"
"\n"
" foreign static startTimer_(milliseconds, fiber)\n"
"\n"
" // Called by native code.\n"
" static resumeTimer_(fiber) {\n"
" fiber.run()\n"
" }\n"
"}\n";
// The Wren method to call when a timer has completed.
static WrenMethod* resumeTimer;
// Called by libuv when the timer has completed.
static void timerCallback(uv_timer_t* handle)
{
WrenValue* fiber = (WrenValue*)handle->data;
free(handle);
// Run the fiber that was sleeping.
wrenCall(getVM(), resumeTimer, "v", fiber);
wrenReleaseValue(getVM(), fiber);
}
static void startTimer(WrenVM* vm)
{
// If we haven't looked up the resume method yet, grab it now.
if (resumeTimer == NULL)
{
resumeTimer = wrenGetMethod(vm, "timer", "Timer", "resumeTimer_(_)");
}
int milliseconds = (int)wrenGetArgumentDouble(vm, 1);
WrenValue* fiber = wrenGetArgumentValue(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);
}
char* timerGetSource()
{
size_t length = strlen(timerLibSource);
char* copy = malloc(length + 1);
strncpy(copy, timerLibSource, length);
return copy;
}
WrenForeignMethodFn timerBindForeign(
WrenVM* vm, const char* className, bool isStatic, const char* signature)
{
if (strcmp(className, "Timer") != 0) return NULL;
if (isStatic && strcmp(signature, "startTimer_(_,_)") == 0) return startTimer;
return NULL;
}
void timerReleaseMethods()
{
if (resumeTimer != NULL) wrenReleaseMethod(getVM(), resumeTimer);
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef timer_h
#define timer_h
#include "wren.h"
// TODO: Coherent naming scheme.
char* timerGetSource();
WrenForeignMethodFn timerBindForeign(
WrenVM* vm, const char* className, bool isStatic, const char* signature);
void timerReleaseMethods();
#endif
+13
View File
@@ -0,0 +1,13 @@
class Timer {
static sleep(milliseconds) {
startTimer_(milliseconds, Fiber.current)
Fiber.yield()
}
foreign static startTimer_(milliseconds, fiber)
// Called by native code.
static resumeTimer_(fiber) {
fiber.run()
}
}