A vertical slice of real libuv integration.

This adds a "timer" module to the CLI that provides a Timer class with
a static sleep() method. Not the most exciting functionality in the
world, but it requires the full hunk of libuv integration:

- The CLI sets up libuv and runs the event loop.
- Added a new directory src/module for CLI modules.
- Updated all the make scripts to handle it.
- Reorganized some other CLI code.
This commit is contained in:
Bob Nystrom
2015-08-07 08:10:55 -07:00
parent 254729d067
commit d4acbe8a70
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()
}
}