feat: add WrenResolveModuleFn callback and module name parameter to wrenInterpret for relative import resolution

This breaking API change introduces a new `WrenResolveModuleFn` callback in the configuration that allows the host to canonicalize import module names, enabling relative imports. The `wrenInterpret()` function now requires a `module` parameter to specify the module name for the code being interpreted. The `wrenInterpretInModule()` function is removed in favor of the updated `wrenInterpret()`. The `metaCompile` function now dynamically looks up the caller's module name instead of hardcoding "main". New test files `resolution.c`, `resolution.h`, and `resolution.wren` are added to verify resolver behavior including null default, NULL return error, string rewriting, shared module deduplication, and importer propagation.
This commit is contained in:
Bob Nystrom
2018-03-23 14:54:09 +00:00
parent 88ad0fc138
commit 761032c0fc
12 changed files with 290 additions and 20 deletions
+1 -1
View File
@@ -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
View File
@@ -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;