forked from Mirror/wren
Start sketching in the FFI.
This commit is contained in:
@ -23,6 +23,8 @@ typedef struct WrenVM WrenVM;
|
||||
// [oldSize] will be zero. It should return NULL.
|
||||
typedef void* (*WrenReallocateFn)(void* memory, size_t oldSize, size_t newSize);
|
||||
|
||||
typedef void (*WrenNativeMethodFn)(WrenVM* vm);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The callback Wren will use to allocate, reallocate, and deallocate memory.
|
||||
@ -80,4 +82,25 @@ void wrenFreeVM(WrenVM* vm);
|
||||
// TODO: Define error codes.
|
||||
int wrenInterpret(WrenVM* vm, const char* source);
|
||||
|
||||
// Defines a foreign method implemented by the host application. Looks for a
|
||||
// global class named [className] to bind the method to. If not found, it will
|
||||
// be created automatically.
|
||||
//
|
||||
// Defines a method on that class named [methodName] accepting [numParams]
|
||||
// parameters. If a method already exists with that name and arity, it will be
|
||||
// replaced. When invoked, the method will call [method].
|
||||
void wrenDefineMethod(WrenVM* vm, const char* className,
|
||||
const char* methodName, int numParams,
|
||||
WrenNativeMethodFn method);
|
||||
|
||||
// Reads an numeric argument for a foreign call. This must only be called within
|
||||
// a function provided to [wrenDefineMethod]. Retrieves the argument at [index]
|
||||
// which ranges from 0 to the number of parameters the method expects - 1.
|
||||
double wrenGetArgumentDouble(WrenVM* vm, int index);
|
||||
|
||||
// Provides a numeric return value for a foreign call. This must only be called
|
||||
// within a function provided to [wrenDefineMethod]. Once this is called, the
|
||||
// foreign call is done, and no more arguments can be read or return calls made.
|
||||
void wrenReturnDouble(WrenVM* vm, double value);
|
||||
|
||||
#endif
|
||||
|
||||
@ -45,6 +45,20 @@
|
||||
// Set this to true to log memory operations as they occur.
|
||||
#define WREN_TRACE_MEMORY false
|
||||
|
||||
// The maximum number of arguments that can be passed to a method. Note that
|
||||
// this limtation is hardcoded in other places in the VM, in particular, the
|
||||
// `CODE_CALL_XX` instructions assume a certain maximum number.
|
||||
#define MAX_PARAMETERS (16)
|
||||
|
||||
// The maximum name of a method, not including the signature. This is an
|
||||
// arbitrary but enforced maximum just so we know how long the method name
|
||||
// strings need to be in the parser.
|
||||
#define MAX_METHOD_NAME (64)
|
||||
|
||||
// The maximum length of a method signature. This includes the name, and the
|
||||
// extra spaces added to handle arity, and another byte to terminate the string.
|
||||
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + MAX_PARAMETERS + 1)
|
||||
|
||||
// Assertions are used to validate program invariants. They indicate things the
|
||||
// program expects to be true about its internal state during execution. If an
|
||||
// assertion fails, there is a bug in Wren.
|
||||
|
||||
@ -12,11 +12,6 @@
|
||||
// parsing/code generation. This minimizes the number of explicit forward
|
||||
// declarations needed.
|
||||
|
||||
// The maximum number of arguments that can be passed to a method. Note that
|
||||
// this limtation is hardcoded in other places in the VM, in particular, the
|
||||
// `CODE_CALL_XX` instructions assume a certain maximum number.
|
||||
#define MAX_PARAMETERS (16)
|
||||
|
||||
// The maximum number of local (i.e. non-global) variables that can be declared
|
||||
// in a single function, method, or chunk of top level code. This is the
|
||||
// maximum number of variables in scope at one time, and spans block scopes.
|
||||
@ -35,15 +30,6 @@
|
||||
// argument.
|
||||
#define MAX_CONSTANTS (256)
|
||||
|
||||
// The maximum name of a method, not including the signature. This is an
|
||||
// arbitrary but enforced maximum just so we know how long the method name
|
||||
// strings need to be in the parser.
|
||||
#define MAX_METHOD_NAME (64)
|
||||
|
||||
// The maximum length of a method signature. This includes the name, and the
|
||||
// extra spaces added to handle arity, and another byte to terminate the string.
|
||||
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + MAX_PARAMETERS + 1)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TOKEN_LEFT_PAREN,
|
||||
|
||||
@ -190,12 +190,17 @@ typedef struct
|
||||
|
||||
typedef enum
|
||||
{
|
||||
// TODO: Unify these three:
|
||||
|
||||
// A primitive method implemented in C that immediately returns a value.
|
||||
METHOD_PRIMITIVE,
|
||||
|
||||
// A built-in method that modifies the fiber directly.
|
||||
METHOD_FIBER,
|
||||
|
||||
// A externally-defined C method.
|
||||
METHOD_FOREIGN,
|
||||
|
||||
// A normal user-defined method.
|
||||
METHOD_BLOCK,
|
||||
|
||||
@ -213,6 +218,7 @@ typedef struct
|
||||
{
|
||||
Primitive primitive;
|
||||
FiberPrimitive fiberPrimitive;
|
||||
WrenNativeMethodFn native;
|
||||
|
||||
// May be a [ObjFn] or [ObjClosure].
|
||||
Obj* fn;
|
||||
|
||||
102
src/wren_vm.c
102
src/wren_vm.c
@ -64,6 +64,9 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
vm->globals[i] = NULL_VAL;
|
||||
}
|
||||
|
||||
vm->nativeCallSlot = NULL;
|
||||
vm->nativeCallNumArgs = 0;
|
||||
|
||||
wrenInitializeCore(vm);
|
||||
return vm;
|
||||
}
|
||||
@ -567,6 +570,27 @@ static void bindMethod(int methodType, int symbol, ObjClass* classObj,
|
||||
}
|
||||
}
|
||||
|
||||
static void callForeign(WrenVM* vm, ObjFiber* fiber, Method* method, int numArgs)
|
||||
{
|
||||
vm->nativeCallSlot = &fiber->stack[fiber->stackSize - numArgs];
|
||||
|
||||
// Don't include the receiver.
|
||||
vm->nativeCallNumArgs = numArgs - 1;
|
||||
|
||||
method->native(vm);
|
||||
|
||||
// Discard the stack slots for the arguments (but leave one for
|
||||
// the result).
|
||||
fiber->stackSize -= numArgs - 1;
|
||||
|
||||
// If nothing was returned, implicitly return null.
|
||||
if (vm->nativeCallSlot != NULL)
|
||||
{
|
||||
*vm->nativeCallSlot = NULL_VAL;
|
||||
vm->nativeCallSlot = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// The main bytecode interpreter loop. This is where the magic happens. It is
|
||||
// also, as you can imagine, highly performance critical.
|
||||
static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
||||
@ -762,6 +786,10 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
||||
break;
|
||||
}
|
||||
|
||||
case METHOD_FOREIGN:
|
||||
callForeign(vm, fiber, method, numArgs);
|
||||
break;
|
||||
|
||||
case METHOD_BLOCK:
|
||||
STORE_FRAME();
|
||||
wrenCallFunction(fiber, method->fn, numArgs);
|
||||
@ -847,6 +875,10 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
||||
break;
|
||||
}
|
||||
|
||||
case METHOD_FOREIGN:
|
||||
callForeign(vm, fiber, method, numArgs);
|
||||
break;
|
||||
|
||||
case METHOD_BLOCK:
|
||||
STORE_FRAME();
|
||||
wrenCallFunction(fiber, method->fn, numArgs);
|
||||
@ -1202,3 +1234,73 @@ void unpinObj(WrenVM* vm)
|
||||
{
|
||||
vm->pinned = vm->pinned->previous;
|
||||
}
|
||||
|
||||
void wrenDefineMethod(WrenVM* vm, const char* className,
|
||||
const char* methodName, int numParams,
|
||||
WrenNativeMethodFn method)
|
||||
{
|
||||
ASSERT(className != NULL, "Must provide class name.");
|
||||
|
||||
int length = (int)strlen(methodName);
|
||||
ASSERT(methodName != NULL, "Must provide method name.");
|
||||
ASSERT(strlen(methodName) < MAX_METHOD_NAME, "Method name too long.");
|
||||
|
||||
ASSERT(numParams >= 0, "numParams cannot be negative.");
|
||||
ASSERT(numParams <= MAX_PARAMETERS, "Too many parameters.");
|
||||
|
||||
ASSERT(method != NULL, "Must provide method function.");
|
||||
|
||||
// Find or create the class to bind the method to.
|
||||
int classSymbol = findSymbol(&vm->globalSymbols,
|
||||
className, strlen(className));
|
||||
ObjClass* classObj;
|
||||
|
||||
if (classSymbol != -1)
|
||||
{
|
||||
// TODO: Handle name is not class.
|
||||
classObj = AS_CLASS(vm->globals[classSymbol]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The class doesn't already exist, so create it.
|
||||
// TODO: Allow passing in name for superclass?
|
||||
classObj = wrenNewClass(vm, vm->objectClass, 0);
|
||||
classSymbol = addSymbol(vm, &vm->globalSymbols,
|
||||
className, strlen(className));
|
||||
vm->globals[classSymbol] = OBJ_VAL(classObj);
|
||||
}
|
||||
|
||||
// Create a name for the method, including its arity.
|
||||
char name[MAX_METHOD_SIGNATURE];
|
||||
strncpy(name, methodName, length);
|
||||
for (int i = 0; i < numParams; i++)
|
||||
{
|
||||
name[length++] = ' ';
|
||||
}
|
||||
name[length] = '\0';
|
||||
|
||||
// Bind the method.
|
||||
int methodSymbol = ensureSymbol(vm, &vm->methods, name, length);
|
||||
|
||||
classObj->methods[methodSymbol].type = METHOD_FOREIGN;
|
||||
classObj->methods[methodSymbol].native = method;
|
||||
}
|
||||
|
||||
double wrenGetArgumentDouble(WrenVM* vm, int index)
|
||||
{
|
||||
ASSERT(vm->nativeCallSlot != NULL, "Must be in foreign call.");
|
||||
ASSERT(index >= 0, "index cannot be negative.");
|
||||
ASSERT(index < vm->nativeCallNumArgs, "Not that many arguments.");
|
||||
|
||||
// + 1 to shift past the receiver.
|
||||
// TODO: Check actual value type first.
|
||||
return AS_NUM(*(vm->nativeCallSlot + index + 1));
|
||||
}
|
||||
|
||||
void wrenReturnDouble(WrenVM* vm, double value)
|
||||
{
|
||||
ASSERT(vm->nativeCallSlot != NULL, "Must be in foreign call.");
|
||||
|
||||
*vm->nativeCallSlot = NUM_VAL(value);
|
||||
vm->nativeCallSlot = NULL;
|
||||
}
|
||||
|
||||
@ -244,6 +244,14 @@ struct WrenVM
|
||||
|
||||
// The externally-provided function used to allocate memory.
|
||||
WrenReallocateFn reallocate;
|
||||
|
||||
// During a foreign function call, this will point to the first argument (the
|
||||
// receiver) of the call on the fiber's stack.
|
||||
Value* nativeCallSlot;
|
||||
|
||||
// During a foreign function call, this will contain the number of arguments
|
||||
// to the function.
|
||||
int nativeCallNumArgs;
|
||||
};
|
||||
|
||||
// A generic allocation function that handles all explicit memory management.
|
||||
|
||||
Reference in New Issue
Block a user