Expose an API to let the host resolve relative import strings.
This is a breaking API change: wrenInterpret() now takes an additional parameter for the module name to interpret the code in.
This commit is contained in:
+2
-2
@@ -228,7 +228,7 @@ void runFile(const char* path)
|
||||
|
||||
initVM();
|
||||
|
||||
WrenInterpretResult result = wrenInterpret(vm, source);
|
||||
WrenInterpretResult result = wrenInterpret(vm, "main", source);
|
||||
|
||||
if (afterLoadFn != NULL) afterLoadFn(vm);
|
||||
|
||||
@@ -256,7 +256,7 @@ int runRepl()
|
||||
printf("\\\\/\"-\n");
|
||||
printf(" \\_/ wren v%s\n", WREN_VERSION_STRING);
|
||||
|
||||
wrenInterpret(vm, "import \"repl\"\n");
|
||||
wrenInterpret(vm, "main", "import \"repl\"\n");
|
||||
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
|
||||
|
||||
+39
-6
@@ -58,16 +58,21 @@ typedef void (*WrenForeignMethodFn)(WrenVM* vm);
|
||||
// collection.
|
||||
typedef void (*WrenFinalizerFn)(void* data);
|
||||
|
||||
// Gives the host a chance to canonicalize the imported module name,
|
||||
// potentially taking into account the (previously resolved) name of the module
|
||||
// that contains the import. Typically, this is used to implement relative
|
||||
// imports.
|
||||
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
|
||||
const char* importer, const char* name);
|
||||
|
||||
// Loads and returns the source code for the module [name].
|
||||
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
|
||||
|
||||
// Returns a pointer to a foreign method on [className] in [module] with
|
||||
// [signature].
|
||||
typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
|
||||
const char* module,
|
||||
const char* className,
|
||||
bool isStatic,
|
||||
const char* signature);
|
||||
const char* module, const char* className, bool isStatic,
|
||||
const char* signature);
|
||||
|
||||
// Displays a string of text to the user.
|
||||
typedef void (*WrenWriteFn)(WrenVM* vm, const char* text);
|
||||
@@ -126,6 +131,32 @@ typedef struct
|
||||
// If `NULL`, defaults to a built-in function that uses `realloc` and `free`.
|
||||
WrenReallocateFn reallocateFn;
|
||||
|
||||
// The callback Wren uses to resolve a module name.
|
||||
//
|
||||
// Some host applications may wish to support "relative" imports, where the
|
||||
// meaning of an import string depends on the module that contains it. To
|
||||
// support that without baking any policy into Wren itself, the VM gives the
|
||||
// host a chance to resolve an import string.
|
||||
//
|
||||
// Before an import is loaded, it calls this, passing in the name of the
|
||||
// module that contains the import and the import string. The host app can
|
||||
// look at both of those and produce a new "canonical" string that uniquely
|
||||
// identifies the module. This string is then used as the name of the module
|
||||
// going forward. It is what is passed to [loadModuleFn], how duplicate
|
||||
// imports of the same module are detected, and how the module is reported in
|
||||
// stack traces.
|
||||
//
|
||||
// If you leave this function NULL, then the original import string is
|
||||
// treated as the resolved string.
|
||||
//
|
||||
// If an import cannot be resolved by the embedder, it should return NULL and
|
||||
// Wren will report that as a runtime error.
|
||||
//
|
||||
// Wren will take ownership of the string you return and free it for you, so
|
||||
// it should be allocated using the same allocation function you provide
|
||||
// above.
|
||||
WrenResolveModuleFn resolveModuleFn;
|
||||
|
||||
// The callback Wren uses to load a module.
|
||||
//
|
||||
// Since Wren does not talk directly to the file system, it relies on the
|
||||
@@ -200,7 +231,8 @@ typedef struct
|
||||
//
|
||||
// For example, say that this is 50. After a garbage collection, when there
|
||||
// are 400 bytes of memory still in use, the next collection will be triggered
|
||||
// after a total of 600 bytes are allocated (including the 400 already in use.)
|
||||
// after a total of 600 bytes are allocated (including the 400 already in
|
||||
// use.)
|
||||
//
|
||||
// Setting this to a smaller number wastes less memory, but triggers more
|
||||
// frequent garbage collections.
|
||||
@@ -256,7 +288,8 @@ void wrenFreeVM(WrenVM* vm);
|
||||
void wrenCollectGarbage(WrenVM* vm);
|
||||
|
||||
// Runs [source], a string of Wren source code in a new fiber in [vm].
|
||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source);
|
||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
|
||||
const char* source);
|
||||
|
||||
// Creates a handle that can be used to invoke a method with [signature] on
|
||||
// using a receiver and arguments that are set up on the stack.
|
||||
|
||||
@@ -14,9 +14,17 @@ void metaCompile(WrenVM* vm)
|
||||
bool printErrors = wrenGetSlotBool(vm, 3);
|
||||
|
||||
// TODO: Allow passing in module?
|
||||
ObjClosure* closure = wrenCompileSource(vm, "main", source,
|
||||
isExpression, printErrors);
|
||||
// Look up the module surrounding the callsite. This is brittle. The -2 walks
|
||||
// up the callstack assuming that the meta module has one level of
|
||||
// indirection before hitting the user's code. Any change to meta may require
|
||||
// this constant to be tweaked.
|
||||
ObjFiber* currentFiber = vm->fiber;
|
||||
ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn;
|
||||
ObjString* module = fn->module->name;
|
||||
|
||||
ObjClosure* closure = wrenCompileSource(vm, module->value, source,
|
||||
isExpression, printErrors);
|
||||
|
||||
// Return the result. We can't use the public API for this since we have a
|
||||
// bare ObjClosure*.
|
||||
if (closure == NULL)
|
||||
|
||||
+1
-1
@@ -1181,7 +1181,7 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
// '---------' '-------------------' -'
|
||||
|
||||
// The rest of the classes can now be defined normally.
|
||||
wrenInterpretInModule(vm, NULL, coreModuleSource);
|
||||
wrenInterpret(vm, NULL, coreModuleSource);
|
||||
|
||||
vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool"));
|
||||
PRIMITIVE(vm->boolClass, "toString", bool_toString);
|
||||
|
||||
+34
-7
@@ -38,6 +38,7 @@ static void* defaultReallocate(void* ptr, size_t newSize)
|
||||
void wrenInitConfiguration(WrenConfiguration* config)
|
||||
{
|
||||
config->reallocateFn = defaultReallocate;
|
||||
config->resolveModuleFn = NULL;
|
||||
config->loadModuleFn = NULL;
|
||||
config->bindForeignMethodFn = NULL;
|
||||
config->bindForeignClassFn = NULL;
|
||||
@@ -693,8 +694,39 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
|
||||
finalizer(foreign->data);
|
||||
}
|
||||
|
||||
// Let the host resolve an imported module name if it wants to.
|
||||
static Value resolveModule(WrenVM* vm, Value name)
|
||||
{
|
||||
// If the host doesn't care to resolve, leave the name alone.
|
||||
if (vm->config.resolveModuleFn == NULL) return name;
|
||||
|
||||
ObjFiber* fiber = vm->fiber;
|
||||
ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn;
|
||||
ObjString* importer = fn->module->name;
|
||||
|
||||
const char* resolved = vm->config.resolveModuleFn(vm, importer->value,
|
||||
AS_CSTRING(name));
|
||||
if (resolved == NULL)
|
||||
{
|
||||
vm->fiber->error = wrenStringFormat(vm,
|
||||
"Could not resolve module '@' imported from '@'.",
|
||||
name, OBJ_VAL(importer));
|
||||
return NULL_VAL;
|
||||
}
|
||||
|
||||
// If they resolved to the exact same string, we don't need to copy it.
|
||||
if (resolved == AS_CSTRING(name)) return name;
|
||||
|
||||
// Copy the string into a Wren String object.
|
||||
name = wrenNewString(vm, resolved);
|
||||
DEALLOCATE(vm, (char*)resolved);
|
||||
return name;
|
||||
}
|
||||
|
||||
Value wrenImportModule(WrenVM* vm, Value name)
|
||||
{
|
||||
name = resolveModule(vm, name);
|
||||
|
||||
// If the module is already loaded, we don't need to do anything.
|
||||
Value existing = wrenMapGet(vm->modules, name);
|
||||
if (!IS_UNDEFINED(existing)) return existing;
|
||||
@@ -1436,13 +1468,8 @@ void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle)
|
||||
DEALLOCATE(vm, handle);
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source)
|
||||
{
|
||||
return wrenInterpretInModule(vm, "main", source);
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenInterpretInModule(WrenVM* vm, const char* module,
|
||||
const char* source)
|
||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
|
||||
const char* source)
|
||||
{
|
||||
ObjClosure* closure = wrenCompileSource(vm, module, source, false, true);
|
||||
if (closure == NULL) return WREN_RESULT_COMPILE_ERROR;
|
||||
|
||||
Reference in New Issue
Block a user