Bind C methods to Wren using foreign declarations.

This commit is contained in:
Bob Nystrom
2015-03-24 07:58:15 -07:00
parent 82fceb8361
commit 0bd19da392
11 changed files with 165 additions and 131 deletions
+37 -12
View File
@@ -76,6 +76,7 @@ typedef enum
TOKEN_ELSE,
TOKEN_FALSE,
TOKEN_FOR,
TOKEN_FOREIGN,
TOKEN_IF,
TOKEN_IMPORT,
TOKEN_IN,
@@ -597,6 +598,7 @@ static void readName(Parser* parser, TokenType type)
else if (isKeyword(parser, "else")) type = TOKEN_ELSE;
else if (isKeyword(parser, "false")) type = TOKEN_FALSE;
else if (isKeyword(parser, "for")) type = TOKEN_FOR;
else if (isKeyword(parser, "foreign")) type = TOKEN_FOREIGN;
else if (isKeyword(parser, "if")) type = TOKEN_IF;
else if (isKeyword(parser, "import")) type = TOKEN_IMPORT;
else if (isKeyword(parser, "in")) type = TOKEN_IN;
@@ -1281,6 +1283,13 @@ static void loadLocal(Compiler* compiler, int slot)
emitByteArg(compiler, CODE_LOAD_LOCAL, slot);
}
// Discards memory owned by [compiler].
static void freeCompiler(Compiler* compiler)
{
wrenByteBufferClear(compiler->parser->vm, &compiler->bytecode);
wrenIntBufferClear(compiler->parser->vm, &compiler->debugSourceLines);
}
// Finishes [compiler], which is compiling a function, method, or chunk of top
// level code. If there is a parent compiler, then this emits code in the
// parent compiler to load the resulting function.
@@ -1292,8 +1301,7 @@ static ObjFn* endCompiler(Compiler* compiler,
if (compiler->parser->hasError)
{
// Free the code since it won't be used.
wrenByteBufferClear(compiler->parser->vm, &compiler->bytecode);
wrenIntBufferClear(compiler->parser->vm, &compiler->debugSourceLines);
freeCompiler(compiler);
return NULL;
}
@@ -2449,6 +2457,7 @@ GrammarRule rules[] =
/* TOKEN_ELSE */ UNUSED,
/* TOKEN_FALSE */ PREFIX(boolean),
/* TOKEN_FOR */ UNUSED,
/* TOKEN_FOREIGN */ UNUSED,
/* TOKEN_IF */ UNUSED,
/* TOKEN_IMPORT */ UNUSED,
/* TOKEN_IN */ UNUSED,
@@ -2892,8 +2901,8 @@ void statement(Compiler* compiler)
// Compiles a method definition inside a class body. Returns the symbol in the
// method table for the new method.
int method(Compiler* compiler, ClassCompiler* classCompiler, bool isConstructor,
SignatureFn signatureFn)
static int method(Compiler* compiler, ClassCompiler* classCompiler,
bool isConstructor, bool isForeign, SignatureFn signatureFn)
{
// Build the method signature.
Signature signature;
@@ -2908,16 +2917,30 @@ int method(Compiler* compiler, ClassCompiler* classCompiler, bool isConstructor,
// Compile the method signature.
signatureFn(&methodCompiler, &signature);
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBody(&methodCompiler, isConstructor);
// Include the full signature in debug messages in stack traces.
char debugName[MAX_METHOD_SIGNATURE];
char fullSignature[MAX_METHOD_SIGNATURE];
int length;
signatureToString(&signature, debugName, &length);
signatureToString(&signature, fullSignature, &length);
if (isForeign)
{
// Define a constant for the signature.
int constant = addConstant(compiler, wrenNewString(compiler->parser->vm,
fullSignature, length));
emitShortArg(compiler, CODE_CONSTANT, constant);
// We don't need the function we started compiling in the parameter list
// any more.
freeCompiler(&methodCompiler);
}
else
{
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBody(&methodCompiler, isConstructor);
endCompiler(&methodCompiler, fullSignature, length);
}
endCompiler(&methodCompiler, debugName, length);
return signatureSymbol(compiler, &signature);
}
@@ -2980,6 +3003,8 @@ static void classDefinition(Compiler* compiler)
{
Code instruction = CODE_METHOD_INSTANCE;
bool isConstructor = false;
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
classCompiler.isStaticMethod = false;
@@ -3004,7 +3029,7 @@ static void classDefinition(Compiler* compiler)
}
int methodSymbol = method(compiler, &classCompiler, isConstructor,
signature);
isForeign, signature);
// Load the class. We have to do this for each method because we can't
// keep the class on top of the stack. If there are static fields, they
+17 -4
View File
@@ -106,6 +106,11 @@ static const char* libSource =
" IO.writeString_(\"[invalid toString]\")\n"
" }\n"
" }\n"
"\n"
" foreign static writeString_(string)\n"
" foreign static clock\n"
" foreign static time\n"
" foreign static read\n"
"}\n";
static void ioWriteString(WrenVM* vm)
@@ -138,10 +143,18 @@ static void ioTime(WrenVM* vm)
void wrenLoadIOLibrary(WrenVM* vm)
{
wrenInterpret(vm, "", libSource);
wrenDefineStaticMethod(vm, "IO", "writeString_(_)", ioWriteString);
wrenDefineStaticMethod(vm, "IO", "clock", ioClock);
wrenDefineStaticMethod(vm, "IO", "time", ioTime);
wrenDefineStaticMethod(vm, "IO", "read", ioRead);
}
WrenForeignMethodFn wrenBindIOForeignMethod(WrenVM* vm, const char* className,
const char* signature)
{
ASSERT(strcmp(className, "IO") == 0, "Should only have IO class.");
if (strcmp(signature, "writeString_(_)") == 0) return ioWriteString;
if (strcmp(signature, "clock") == 0) return ioClock;
if (strcmp(signature, "time") == 0) return ioTime;
if (strcmp(signature, "read") == 0) return ioRead;
return NULL;
}
#endif
+3
View File
@@ -10,6 +10,9 @@
void wrenLoadIOLibrary(WrenVM* vm);
WrenForeignMethodFn wrenBindIOForeignMethod(WrenVM* vm, const char* className,
const char* signature);
#endif
#endif
+5 -1
View File
@@ -542,7 +542,7 @@ Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key)
return value;
}
ObjModule* wrenNewModule(WrenVM* vm)
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name)
{
ObjModule* module = ALLOCATE(vm, ObjModule);
@@ -554,6 +554,8 @@ ObjModule* wrenNewModule(WrenVM* vm)
wrenSymbolTableInit(&module->variableNames);
wrenValueBufferInit(&module->variables);
module->name = name;
wrenPopRoot(vm);
return module;
}
@@ -947,6 +949,8 @@ static void markModule(WrenVM* vm, ObjModule* module)
wrenMarkValue(vm, module->variables.data[i]);
}
wrenMarkObj(vm, (Obj*)module->name);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjModule);
// TODO: Track memory for symbol table and buffer.
+4 -1
View File
@@ -283,6 +283,9 @@ typedef struct
// Symbol table for the names of all module variables. Indexes here directly
// correspond to entries in [variables].
SymbolTable variableNames;
// The name of the module.
ObjString* name;
} ObjModule;
// A first-class function object. A raw ObjFn can be used and invoked directly
@@ -656,7 +659,7 @@ void wrenMapClear(WrenVM* vm, ObjMap* map);
Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key);
// Creates a new module.
ObjModule* wrenNewModule(WrenVM* vm);
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name);
// Creates a new range from [from] to [to].
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive);
+61 -87
View File
@@ -37,6 +37,7 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
memset(vm, 0, sizeof(WrenVM));
vm->reallocate = reallocate;
vm->bindForeign = configuration->bindForeignMethodFn;
vm->loadModule = configuration->loadModuleFn;
wrenSymbolTableInit(&vm->methodNames);
@@ -62,14 +63,18 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
}
ObjString* name = AS_STRING(CONST_STRING(vm, "main"));
wrenPushRoot(vm, (Obj*)name);
// Implicitly create a "main" module for the REPL or entry script.
ObjModule* mainModule = wrenNewModule(vm);
ObjModule* mainModule = wrenNewModule(vm, name);
wrenPushRoot(vm, (Obj*)mainModule);
vm->modules = wrenNewMap(vm);
wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(mainModule));
wrenPopRoot(vm);
wrenPopRoot(vm); // mainModule.
wrenPopRoot(vm); // name.
wrenInitializeCore(vm);
#if WREN_USE_LIB_IO
@@ -270,26 +275,63 @@ static void closeUpvalue(ObjFiber* fiber)
fiber->openUpvalues = upvalue->next;
}
static void bindMethod(WrenVM* vm, int methodType, int symbol,
ObjClass* classObj, Value methodValue)
// Looks up a foreign method in [moduleName] on [className] with [signature].
//
// This will try the host's foreign method binder first. If that fails, it
// falls back to handling the built-in modules.
static WrenForeignMethodFn findForeignMethod(WrenVM* vm,
const char* moduleName,
const char* className,
bool isStatic,
const char* signature)
{
ObjFn* methodFn = IS_FN(methodValue) ? AS_FN(methodValue)
: AS_CLOSURE(methodValue)->fn;
WrenForeignMethodFn fn;
// Methods are always bound against the class, and not the metaclass, even
// for static methods, so that constructors (which are static) get bound like
// instance methods.
wrenBindMethodCode(classObj, methodFn);
Method method;
method.type = METHOD_BLOCK;
method.fn.obj = AS_OBJ(methodValue);
if (methodType == CODE_METHOD_STATIC)
// Let the host try to find it first.
if (vm->bindForeign != NULL)
{
classObj = classObj->obj.classObj;
fn = vm->bindForeign(vm, moduleName, className, isStatic, signature);
if (fn != NULL) return fn;
}
// Otherwise, try the built-in libraries.
#if WREN_USE_LIB_IO
fn = wrenBindIOForeignMethod(vm, className, signature);
if (fn != NULL) return fn;
#endif
// TODO: Report a runtime error on failure to find it.
return NULL;
}
static void bindMethod(WrenVM* vm, int methodType, int symbol,
ObjModule* module, ObjClass* classObj, Value methodValue)
{
Method method;
if (IS_STRING(methodValue))
{
const char* name = AS_CSTRING(methodValue);
method.type = METHOD_FOREIGN;
method.fn.foreign = findForeignMethod(vm, module->name->value,
classObj->name->value,
methodType == CODE_METHOD_STATIC,
name);
}
else
{
ObjFn* methodFn = IS_FN(methodValue) ? AS_FN(methodValue)
: AS_CLOSURE(methodValue)->fn;
// Methods are always bound against the class, and not the metaclass, even
// for static methods, so that constructors (which are static) get bound
// like instance methods.
wrenBindMethodCode(classObj, methodFn);
method.type = METHOD_BLOCK;
method.fn.obj = AS_OBJ(methodValue);
}
if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj;
wrenBindMethod(vm, classObj, symbol, method);
}
@@ -388,7 +430,7 @@ static ObjFiber* loadModule(WrenVM* vm, Value name, const char* source)
}
else
{
module = wrenNewModule(vm);
module = wrenNewModule(vm, AS_STRING(name));
// Store it in the VM's module registry so we don't load the same module
// multiple times.
@@ -1169,7 +1211,7 @@ static bool runInterpreter(WrenVM* vm)
uint16_t symbol = READ_SHORT();
ObjClass* classObj = AS_CLASS(PEEK());
Value method = PEEK2();
bindMethod(vm, instruction, symbol, classObj, method);
bindMethod(vm, instruction, symbol, fn->module, classObj, method);
DROP();
DROP();
DISPATCH();
@@ -1505,74 +1547,6 @@ void wrenPopRoot(WrenVM* vm)
vm->numTempRoots--;
}
static void defineMethod(WrenVM* vm, const char* className,
const char* signature,
WrenForeignMethodFn methodFn, bool isStatic)
{
ASSERT(className != NULL, "Must provide class name.");
int length = (int)strlen(signature);
ASSERT(signature != NULL, "Must provide signature.");
ASSERT(strlen(signature) < MAX_METHOD_SIGNATURE, "Signature too long.");
ASSERT(methodFn != NULL, "Must provide method function.");
// TODO: Need to be able to define methods in classes outside the core
// module.
// Find or create the class to bind the method to.
ObjModule* coreModule = getCoreModule(vm);
int classSymbol = wrenSymbolTableFind(&coreModule->variableNames,
className, strlen(className));
ObjClass* classObj;
if (classSymbol != -1)
{
// TODO: Handle name is not class.
classObj = AS_CLASS(coreModule->variables.data[classSymbol]);
}
else
{
// The class doesn't already exist, so create it.
ObjString* nameString = AS_STRING(wrenStringFormat(vm, "$", className));
wrenPushRoot(vm, (Obj*)nameString);
// TODO: Allow passing in name for superclass?
classObj = wrenNewClass(vm, vm->objectClass, 0, nameString);
wrenDefineVariable(vm, coreModule, className, nameString->length,
OBJ_VAL(classObj));
wrenPopRoot(vm);
}
// Bind the method.
int methodSymbol = wrenSymbolTableEnsure(vm, &vm->methodNames,
signature, length);
Method method;
method.type = METHOD_FOREIGN;
method.fn.foreign = methodFn;
if (isStatic) classObj = classObj->obj.classObj;
wrenBindMethod(vm, classObj, methodSymbol, method);
}
void wrenDefineMethod(WrenVM* vm, const char* className,
const char* signature,
WrenForeignMethodFn methodFn)
{
defineMethod(vm, className, signature, methodFn, false);
}
void wrenDefineStaticMethod(WrenVM* vm, const char* className,
const char* signature,
WrenForeignMethodFn methodFn)
{
defineMethod(vm, className, signature, methodFn, true);
}
bool wrenGetArgumentBool(WrenVM* vm, int index)
{
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
+11
View File
@@ -165,11 +165,19 @@ typedef enum
// Define a method for symbol [arg]. The class receiving the method is popped
// off the stack, then the function defining the body is popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
CODE_METHOD_INSTANCE,
// Define a method for symbol [arg]. The class whose metaclass will receive
// the method is popped off the stack, then the function defining the body is
// popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
CODE_METHOD_STATIC,
// Load the module whose name is stored in string constant [arg]. Pushes
@@ -271,6 +279,9 @@ struct WrenVM
// to the function.
int foreignCallNumArgs;
// The function used to locate foreign functions.
WrenBindForeignMethodFn bindForeign;
// The function used to load modules.
WrenLoadModuleFn loadModule;