Add API to call Wren method from C code.
This gives you a simple, efficient way to invoke a method on some Wren object from C code, passing in arguments. The basic API is in place and works, but there's still lots to do: - Lots of error handling. - Documentation. - Tests!
This commit is contained in:
parent
6b05610c6a
commit
876c2d9208
@ -6,6 +6,11 @@
|
|||||||
|
|
||||||
typedef struct WrenVM WrenVM;
|
typedef struct WrenVM WrenVM;
|
||||||
|
|
||||||
|
// A handle to a method, bound to a receiver.
|
||||||
|
//
|
||||||
|
// This is used to call a Wren method on some object from C code.
|
||||||
|
typedef struct WrenMethod WrenMethod;
|
||||||
|
|
||||||
// A generic allocation function that handles all explicit memory management
|
// A generic allocation function that handles all explicit memory management
|
||||||
// used by Wren. It's used like so:
|
// used by Wren. It's used like so:
|
||||||
//
|
//
|
||||||
@ -118,6 +123,34 @@ void wrenFreeVM(WrenVM* vm);
|
|||||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
||||||
const char* source);
|
const char* source);
|
||||||
|
|
||||||
|
// Creates a handle that can be used to invoke a method with [signature] on the
|
||||||
|
// object in [module] currently stored in top-level [variable].
|
||||||
|
//
|
||||||
|
// This handle can be used repeatedly to directly invoke that method from C
|
||||||
|
// code using [wrenCall].
|
||||||
|
//
|
||||||
|
// When done with this handle, it must be released by calling
|
||||||
|
// [wrenReleaseMethod].
|
||||||
|
WrenMethod* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
|
||||||
|
const char* signature);
|
||||||
|
|
||||||
|
// Calls [method], passing in a series of arguments whose types must match the
|
||||||
|
// specifed [argTypes]. This is a string where each character identifies the
|
||||||
|
// type of a single argument, in orde. The allowed types are:
|
||||||
|
//
|
||||||
|
// - "b" - A C `int` converted to a Wren Bool.
|
||||||
|
// - "d" - A C `double` converted to a Wren Num.
|
||||||
|
// - "i" - A C `int` converted to a Wren Num.
|
||||||
|
// - "s" - A C null-terminated `const char*` converted to a Wren String. Wren
|
||||||
|
// will allocate its own string and copy the characters from this, so
|
||||||
|
// you don't have to worry about the lifetime of the string you pass to
|
||||||
|
// Wren.
|
||||||
|
void wrenCall(WrenVM* vm, WrenMethod* method, const char* argTypes, ...);
|
||||||
|
|
||||||
|
// Releases memory associated with [method]. After calling this, [method] can
|
||||||
|
// no longer be used.
|
||||||
|
void wrenReleaseMethod(WrenVM* vm, WrenMethod* method);
|
||||||
|
|
||||||
// TODO: Figure out how these interact with modules.
|
// TODO: Figure out how these interact with modules.
|
||||||
|
|
||||||
// Defines a foreign method implemented by the host application. Looks for a
|
// Defines a foreign method implemented by the host application. Looks for a
|
||||||
|
|||||||
10
src/main.c
10
src/main.c
@ -128,6 +128,16 @@ static int runFile(WrenVM* vm, const char* path)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WrenMethod* method = wrenGetMethod(vm, "main", "Foo", "bar(_,_,_)");
|
||||||
|
wrenCall(vm, method, "nsd", NULL, "some string", 78.9);
|
||||||
|
wrenCall(vm, method, "iii", 1, 2, 3);
|
||||||
|
wrenCall(vm, method, "nsd", NULL, "another string", 78.9);
|
||||||
|
wrenCall(vm, method, "iii", 4, 5, 6);
|
||||||
|
|
||||||
|
wrenReleaseMethod(vm, method);
|
||||||
|
*/
|
||||||
|
|
||||||
wrenFreeVM(vm);
|
wrenFreeVM(vm);
|
||||||
free(source);
|
free(source);
|
||||||
|
|
||||||
|
|||||||
@ -108,6 +108,20 @@
|
|||||||
// field index*.
|
// field index*.
|
||||||
#define MAX_FIELDS 255
|
#define MAX_FIELDS 255
|
||||||
|
|
||||||
|
// Use the VM's allocator to allocate an object of [type].
|
||||||
|
#define ALLOCATE(vm, type) \
|
||||||
|
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
|
||||||
|
|
||||||
|
// Use the VM's allocator to allocate an object of [mainType] containing a
|
||||||
|
// flexible array of [count] objects of [arrayType].
|
||||||
|
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
|
||||||
|
((mainType*)wrenReallocate(vm, NULL, 0, \
|
||||||
|
sizeof(mainType) + sizeof(arrayType) * count))
|
||||||
|
|
||||||
|
// Use the VM's allocator to allocate an array of [count] elements of [type].
|
||||||
|
#define ALLOCATE_ARRAY(vm, type, count) \
|
||||||
|
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * count))
|
||||||
|
|
||||||
// The Microsoft compiler does not support the "inline" modifier when compiling
|
// The Microsoft compiler does not support the "inline" modifier when compiling
|
||||||
// as plain C.
|
// as plain C.
|
||||||
#if defined( _MSC_VER ) && !defined(__cplusplus)
|
#if defined( _MSC_VER ) && !defined(__cplusplus)
|
||||||
|
|||||||
@ -1527,11 +1527,9 @@ static void finishParameterList(Compiler* compiler, Signature* signature)
|
|||||||
while (match(compiler, TOKEN_COMMA));
|
while (match(compiler, TOKEN_COMMA));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the symbol for a method [name]. If [length] is 0, it will be calculated
|
// Gets the symbol for a method [name] with [length].
|
||||||
// from a null-terminated [name].
|
|
||||||
static int methodSymbol(Compiler* compiler, const char* name, int length)
|
static int methodSymbol(Compiler* compiler, const char* name, int length)
|
||||||
{
|
{
|
||||||
if (length == 0) length = (int)strlen(name);
|
|
||||||
return wrenSymbolTableEnsure(compiler->parser->vm,
|
return wrenSymbolTableEnsure(compiler->parser->vm,
|
||||||
&compiler->parser->vm->methodNames, name, length);
|
&compiler->parser->vm->methodNames, name, length);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,16 +23,6 @@
|
|||||||
DEFINE_BUFFER(Value, Value);
|
DEFINE_BUFFER(Value, Value);
|
||||||
DEFINE_BUFFER(Method, Method);
|
DEFINE_BUFFER(Method, Method);
|
||||||
|
|
||||||
#define ALLOCATE(vm, type) \
|
|
||||||
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
|
|
||||||
|
|
||||||
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
|
|
||||||
((mainType*)wrenReallocate(vm, NULL, 0, \
|
|
||||||
sizeof(mainType) + sizeof(arrayType) * count))
|
|
||||||
|
|
||||||
#define ALLOCATE_ARRAY(vm, type, count) \
|
|
||||||
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * count))
|
|
||||||
|
|
||||||
static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
|
static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
|
||||||
{
|
{
|
||||||
obj->type = type;
|
obj->type = type;
|
||||||
@ -145,6 +135,13 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
|||||||
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
|
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
|
||||||
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
|
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
|
||||||
|
|
||||||
|
wrenResetFiber(fiber, fn);
|
||||||
|
|
||||||
|
return fiber;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wrenResetFiber(ObjFiber* fiber, Obj* fn)
|
||||||
|
{
|
||||||
// Push the stack frame for the function.
|
// Push the stack frame for the function.
|
||||||
fiber->stackTop = fiber->stack;
|
fiber->stackTop = fiber->stack;
|
||||||
fiber->numFrames = 1;
|
fiber->numFrames = 1;
|
||||||
@ -164,8 +161,6 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
|||||||
{
|
{
|
||||||
frame->ip = ((ObjClosure*)fn)->fn->bytecode;
|
frame->ip = ((ObjClosure*)fn)->fn->bytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fiber;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
||||||
@ -795,7 +790,10 @@ static void markFn(WrenVM* vm, ObjFn* fn)
|
|||||||
wrenMarkValue(vm, fn->constants[i]);
|
wrenMarkValue(vm, fn->constants[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
wrenMarkObj(vm, (Obj*)fn->debug->sourcePath);
|
if (fn->debug->sourcePath != NULL)
|
||||||
|
{
|
||||||
|
wrenMarkObj(vm, (Obj*)fn->debug->sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
// Keep track of how much memory is still in use.
|
// Keep track of how much memory is still in use.
|
||||||
vm->bytesAllocated += sizeof(ObjFn);
|
vm->bytesAllocated += sizeof(ObjFn);
|
||||||
|
|||||||
@ -646,6 +646,9 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
|
|||||||
// closure.
|
// closure.
|
||||||
ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn);
|
ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn);
|
||||||
|
|
||||||
|
// Resets [fiber] back to an initial state where it is ready to invoke [fn].
|
||||||
|
void wrenResetFiber(ObjFiber* fiber, Obj* fn);
|
||||||
|
|
||||||
// TODO: The argument list here is getting a bit gratuitous.
|
// TODO: The argument list here is getting a bit gratuitous.
|
||||||
// Creates a new function object with the given code and constants. The new
|
// Creates a new function object with the given code and constants. The new
|
||||||
// function will take over ownership of [bytecode] and [sourceLines]. It will
|
// function will take over ownership of [bytecode] and [sourceLines]. It will
|
||||||
|
|||||||
158
src/wren_vm.c
158
src/wren_vm.c
@ -1,3 +1,4 @@
|
|||||||
|
#include <stdarg.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -35,6 +36,8 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
|||||||
WrenVM* vm = (WrenVM*)reallocate(NULL, 0, sizeof(WrenVM));
|
WrenVM* vm = (WrenVM*)reallocate(NULL, 0, sizeof(WrenVM));
|
||||||
|
|
||||||
vm->reallocate = reallocate;
|
vm->reallocate = reallocate;
|
||||||
|
|
||||||
|
vm->methodHandles = NULL;
|
||||||
vm->foreignCallSlot = NULL;
|
vm->foreignCallSlot = NULL;
|
||||||
vm->foreignCallNumArgs = 0;
|
vm->foreignCallNumArgs = 0;
|
||||||
vm->loadModule = configuration->loadModuleFn;
|
vm->loadModule = configuration->loadModuleFn;
|
||||||
@ -99,7 +102,14 @@ void wrenFreeVM(WrenVM* vm)
|
|||||||
obj = next;
|
obj = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tell the user if they didn't free any method handles. We don't want to
|
||||||
|
// just free them here because the host app may still have pointers to them
|
||||||
|
// that they may try to use. Better to tell them about the bug early.
|
||||||
|
ASSERT(vm->methodHandles == NULL, "All methods have not been released.");
|
||||||
|
|
||||||
wrenSymbolTableClear(vm, &vm->methodNames);
|
wrenSymbolTableClear(vm, &vm->methodNames);
|
||||||
|
|
||||||
|
// TODO: Make macro for freeing.
|
||||||
wrenReallocate(vm, vm, 0, 0);
|
wrenReallocate(vm, vm, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +150,14 @@ static void collectGarbage(WrenVM* vm)
|
|||||||
// The current fiber.
|
// The current fiber.
|
||||||
if (vm->fiber != NULL) wrenMarkObj(vm, (Obj*)vm->fiber);
|
if (vm->fiber != NULL) wrenMarkObj(vm, (Obj*)vm->fiber);
|
||||||
|
|
||||||
|
// The method handles.
|
||||||
|
for (WrenMethod* handle = vm->methodHandles;
|
||||||
|
handle != NULL;
|
||||||
|
handle = handle->next)
|
||||||
|
{
|
||||||
|
wrenMarkObj(vm, (Obj*)handle->fiber);
|
||||||
|
}
|
||||||
|
|
||||||
// Any object the compiler is using (if there is one).
|
// Any object the compiler is using (if there is one).
|
||||||
if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler);
|
if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler);
|
||||||
|
|
||||||
@ -1221,6 +1239,146 @@ static bool runInterpreter(WrenVM* vm)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates an [ObjFn] that invokes a method with [signature] when called.
|
||||||
|
static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
|
||||||
|
{
|
||||||
|
int signatureLength = (int)strlen(signature);
|
||||||
|
|
||||||
|
// Count the number parameters the method expects.
|
||||||
|
int numParams = 0;
|
||||||
|
for (const char* s = signature; *s != '\0'; s++)
|
||||||
|
{
|
||||||
|
if (*s == '_') numParams++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
|
||||||
|
signature, signatureLength);
|
||||||
|
|
||||||
|
uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5);
|
||||||
|
bytecode[0] = CODE_CALL_0 + numParams;
|
||||||
|
bytecode[1] = (method >> 8) & 0xff;
|
||||||
|
bytecode[2] = method & 0xff;
|
||||||
|
bytecode[3] = CODE_RETURN;
|
||||||
|
bytecode[4] = CODE_END;
|
||||||
|
|
||||||
|
int* debugLines = ALLOCATE_ARRAY(vm, int, 5);
|
||||||
|
memset(debugLines, 1, 5);
|
||||||
|
|
||||||
|
return wrenNewFunction(vm, module, NULL, 0, 0, 0, bytecode, 5, NULL,
|
||||||
|
signature, signatureLength, debugLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
WrenMethod* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
|
||||||
|
const char* signature)
|
||||||
|
{
|
||||||
|
Value moduleName = wrenNewString(vm, module, strlen(module));
|
||||||
|
wrenPushRoot(vm, AS_OBJ(moduleName));
|
||||||
|
|
||||||
|
uint32_t moduleEntry = wrenMapFind(vm->modules, moduleName);
|
||||||
|
// TODO: Handle module not being found.
|
||||||
|
ObjModule* moduleObj = AS_MODULE(vm->modules->entries[moduleEntry].value);
|
||||||
|
|
||||||
|
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
|
||||||
|
variable, strlen(variable));
|
||||||
|
// TODO: Handle the variable not being found.
|
||||||
|
|
||||||
|
ObjFn* fn = makeCallStub(vm, moduleObj, signature);
|
||||||
|
wrenPushRoot(vm, (Obj*)fn);
|
||||||
|
|
||||||
|
// Create a single fiber that we can reuse each time the method is invoked.
|
||||||
|
ObjFiber* fiber = wrenNewFiber(vm, (Obj*)fn);
|
||||||
|
wrenPushRoot(vm, (Obj*)fiber);
|
||||||
|
|
||||||
|
// Create a handle that keeps track of the function that calls the method.
|
||||||
|
WrenMethod* method = ALLOCATE(vm, WrenMethod);
|
||||||
|
method->fiber = fiber;
|
||||||
|
|
||||||
|
// Store the receiver in the fiber's stack so we can use it later in the call.
|
||||||
|
*fiber->stackTop++ = moduleObj->variables.data[variableSlot];
|
||||||
|
|
||||||
|
// Add it to the front of the linked list of handles.
|
||||||
|
if (vm->methodHandles != NULL) vm->methodHandles->prev = method;
|
||||||
|
method->prev = NULL;
|
||||||
|
method->next = vm->methodHandles;
|
||||||
|
vm->methodHandles = method;
|
||||||
|
|
||||||
|
wrenPopRoot(vm); // fiber.
|
||||||
|
wrenPopRoot(vm); // fn.
|
||||||
|
wrenPopRoot(vm); // moduleName.
|
||||||
|
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wrenCall(WrenVM* vm, WrenMethod* method, const char* argTypes, ...)
|
||||||
|
{
|
||||||
|
// TODO: Validate that the number of arguments matches what the method
|
||||||
|
// expects.
|
||||||
|
|
||||||
|
// Push the arguments.
|
||||||
|
va_list argList;
|
||||||
|
va_start(argList, argTypes);
|
||||||
|
|
||||||
|
for (const char* argType = argTypes; *argType != '\0'; argType++)
|
||||||
|
{
|
||||||
|
Value value = NULL_VAL;
|
||||||
|
switch (*argType)
|
||||||
|
{
|
||||||
|
case 'b': value = BOOL_VAL(va_arg(argList, int)); break;
|
||||||
|
case 'd': value = NUM_VAL(va_arg(argList, double)); break;
|
||||||
|
case 'i': value = NUM_VAL((double)va_arg(argList, int)); break;
|
||||||
|
case 'n': value = NULL_VAL; va_arg(argList, void*); break;
|
||||||
|
case 's':
|
||||||
|
{
|
||||||
|
const char* text = va_arg(argList, const char*);
|
||||||
|
value = wrenNewString(vm, text, strlen(text));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
ASSERT(false, "Uknown argument type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*method->fiber->stackTop++ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(argList);
|
||||||
|
|
||||||
|
vm->fiber = method->fiber;
|
||||||
|
|
||||||
|
Value receiver = method->fiber->stack[0];
|
||||||
|
Obj* fn = method->fiber->frames[0].fn;
|
||||||
|
|
||||||
|
// TODO: How does this handle a runtime error occurring?
|
||||||
|
runInterpreter(vm);
|
||||||
|
|
||||||
|
// Reset the fiber to get ready for the next call.
|
||||||
|
wrenResetFiber(method->fiber, fn);
|
||||||
|
|
||||||
|
// Push the receiver back on the stack.
|
||||||
|
*method->fiber->stackTop++ = receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wrenReleaseMethod(WrenVM* vm, WrenMethod* method)
|
||||||
|
{
|
||||||
|
ASSERT(method != NULL, "NULL method.");
|
||||||
|
|
||||||
|
// Update the VM's head pointer if we're releasing the first handle.
|
||||||
|
if (vm->methodHandles == method) vm->methodHandles = method->next;
|
||||||
|
|
||||||
|
// Unlink it from the list.
|
||||||
|
if (method->prev != NULL) method->prev->next = method->next;
|
||||||
|
if (method->next != NULL) method->next->prev = method->prev;
|
||||||
|
|
||||||
|
// Clear it out. This isn't strictly necessary since we're going to free it,
|
||||||
|
// but it makes for easier debugging.
|
||||||
|
method->prev = NULL;
|
||||||
|
method->next = NULL;
|
||||||
|
method->fiber = NULL;
|
||||||
|
|
||||||
|
wrenReallocate(vm, method, sizeof(WrenMethod), 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute [source] in the context of the core module.
|
// Execute [source] in the context of the core module.
|
||||||
WrenInterpretResult static loadIntoCore(WrenVM* vm, const char* source)
|
WrenInterpretResult static loadIntoCore(WrenVM* vm, const char* source)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -189,6 +189,17 @@ typedef enum
|
|||||||
CODE_END
|
CODE_END
|
||||||
} Code;
|
} Code;
|
||||||
|
|
||||||
|
struct WrenMethod
|
||||||
|
{
|
||||||
|
// The fiber that invokes the method. Its stack is pre-populated with the
|
||||||
|
// receiver for the method, and it contains a single callframe whose function
|
||||||
|
// is the bytecode stub to invoke the method.
|
||||||
|
ObjFiber* fiber;
|
||||||
|
|
||||||
|
WrenMethod* prev;
|
||||||
|
WrenMethod* next;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Move into wren_vm.c?
|
// TODO: Move into wren_vm.c?
|
||||||
struct WrenVM
|
struct WrenVM
|
||||||
{
|
{
|
||||||
@ -254,6 +265,10 @@ struct WrenVM
|
|||||||
// receiver) of the call on the fiber's stack.
|
// receiver) of the call on the fiber's stack.
|
||||||
Value* foreignCallSlot;
|
Value* foreignCallSlot;
|
||||||
|
|
||||||
|
// Pointer to the first node in the linked list of active method handles or
|
||||||
|
// NULL if there are no handles.
|
||||||
|
WrenMethod* methodHandles;
|
||||||
|
|
||||||
// During a foreign function call, this will contain the number of arguments
|
// During a foreign function call, this will contain the number of arguments
|
||||||
// to the function.
|
// to the function.
|
||||||
int foreignCallNumArgs;
|
int foreignCallNumArgs;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user