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:
@@ -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,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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user