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;
|
||||
|
||||
// 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
|
||||
// used by Wren. It's used like so:
|
||||
//
|
||||
@ -118,6 +123,34 @@ void wrenFreeVM(WrenVM* vm);
|
||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
||||
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.
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
free(source);
|
||||
|
||||
|
||||
@ -108,6 +108,20 @@
|
||||
// field index*.
|
||||
#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
|
||||
// as plain C.
|
||||
#if defined( _MSC_VER ) && !defined(__cplusplus)
|
||||
|
||||
@ -1527,11 +1527,9 @@ static void finishParameterList(Compiler* compiler, Signature* signature)
|
||||
while (match(compiler, TOKEN_COMMA));
|
||||
}
|
||||
|
||||
// Gets the symbol for a method [name]. If [length] is 0, it will be calculated
|
||||
// from a null-terminated [name].
|
||||
// Gets the symbol for a method [name] with [length].
|
||||
static int methodSymbol(Compiler* compiler, const char* name, int length)
|
||||
{
|
||||
if (length == 0) length = (int)strlen(name);
|
||||
return wrenSymbolTableEnsure(compiler->parser->vm,
|
||||
&compiler->parser->vm->methodNames, name, length);
|
||||
}
|
||||
|
||||
@ -23,16 +23,6 @@
|
||||
DEFINE_BUFFER(Value, Value);
|
||||
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)
|
||||
{
|
||||
obj->type = type;
|
||||
@ -145,6 +135,13 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
||||
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
|
||||
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.
|
||||
fiber->stackTop = fiber->stack;
|
||||
fiber->numFrames = 1;
|
||||
@ -164,8 +161,6 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
||||
{
|
||||
frame->ip = ((ObjClosure*)fn)->fn->bytecode;
|
||||
}
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
||||
@ -795,7 +790,10 @@ static void markFn(WrenVM* vm, ObjFn* fn)
|
||||
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.
|
||||
vm->bytesAllocated += sizeof(ObjFn);
|
||||
|
||||
@ -646,6 +646,9 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
|
||||
// closure.
|
||||
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.
|
||||
// Creates a new function object with the given code and constants. The new
|
||||
// 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 <stdio.h>
|
||||
#include <string.h>
|
||||
@ -35,6 +36,8 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
WrenVM* vm = (WrenVM*)reallocate(NULL, 0, sizeof(WrenVM));
|
||||
|
||||
vm->reallocate = reallocate;
|
||||
|
||||
vm->methodHandles = NULL;
|
||||
vm->foreignCallSlot = NULL;
|
||||
vm->foreignCallNumArgs = 0;
|
||||
vm->loadModule = configuration->loadModuleFn;
|
||||
@ -99,7 +102,14 @@ void wrenFreeVM(WrenVM* vm)
|
||||
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);
|
||||
|
||||
// TODO: Make macro for freeing.
|
||||
wrenReallocate(vm, vm, 0, 0);
|
||||
}
|
||||
|
||||
@ -140,6 +150,14 @@ static void collectGarbage(WrenVM* vm)
|
||||
// The current 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).
|
||||
if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler);
|
||||
|
||||
@ -1221,6 +1239,146 @@ static bool runInterpreter(WrenVM* vm)
|
||||
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.
|
||||
WrenInterpretResult static loadIntoCore(WrenVM* vm, const char* source)
|
||||
{
|
||||
|
||||
@ -189,6 +189,17 @@ typedef enum
|
||||
CODE_END
|
||||
} 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?
|
||||
struct WrenVM
|
||||
{
|
||||
@ -254,6 +265,10 @@ struct WrenVM
|
||||
// receiver) of the call on the fiber's stack.
|
||||
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
|
||||
// to the function.
|
||||
int foreignCallNumArgs;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user