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:
@@ -30,7 +30,7 @@ static void call(WrenVM* vm)
|
||||
wrenInitConfiguration(&config);
|
||||
WrenVM* otherVM = wrenNewVM(&config);
|
||||
|
||||
wrenInterpret(otherVM, testScript);
|
||||
wrenInterpret(otherVM, "main", testScript);
|
||||
|
||||
WrenHandle* method = wrenMakeCallHandle(otherVM, "method(_,_,_,_)");
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "new_vm.h"
|
||||
#include "reset_stack_after_call_abort.h"
|
||||
#include "reset_stack_after_foreign_construct.h"
|
||||
#include "resolution.h"
|
||||
#include "slots.h"
|
||||
#include "user_data.h"
|
||||
|
||||
@@ -58,6 +59,9 @@ static WrenForeignMethodFn bindForeignMethod(
|
||||
method = newVMBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = resolutionBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = slotsBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ static void nullConfig(WrenVM* vm)
|
||||
WrenVM* otherVM = wrenNewVM(NULL);
|
||||
|
||||
// We should be able to execute code.
|
||||
WrenInterpretResult result = wrenInterpret(otherVM, "1 + 2");
|
||||
WrenInterpretResult result = wrenInterpret(otherVM, "main", "1 + 2");
|
||||
wrenSetSlotBool(vm, 0, result == WREN_RESULT_SUCCESS);
|
||||
|
||||
wrenFreeVM(otherVM);
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "resolution.h"
|
||||
|
||||
static void write(WrenVM* vm, const char* text)
|
||||
{
|
||||
printf("%s", text);
|
||||
}
|
||||
|
||||
static void reportError(WrenVM* vm, WrenErrorType type,
|
||||
const char* module, int line, const char* message)
|
||||
{
|
||||
if (type == WREN_ERROR_RUNTIME) printf("%s\n", message);
|
||||
}
|
||||
|
||||
static char* loadModule(WrenVM* vm, const char* module)
|
||||
{
|
||||
printf("loading %s\n", module);
|
||||
|
||||
const char* source;
|
||||
if (strcmp(module, "main/baz/bang") == 0)
|
||||
{
|
||||
source = "import \"foo|bar\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
source = "System.print(\"ok\")";
|
||||
}
|
||||
|
||||
char* string = malloc(strlen(source) + 1);
|
||||
strcpy(string, source);
|
||||
return string;
|
||||
}
|
||||
|
||||
static void runTestVM(WrenVM* vm, WrenConfiguration* configuration,
|
||||
const char* source)
|
||||
{
|
||||
configuration->writeFn = write;
|
||||
configuration->errorFn = reportError;
|
||||
configuration->loadModuleFn = loadModule;
|
||||
|
||||
WrenVM* otherVM = wrenNewVM(configuration);
|
||||
|
||||
// We should be able to execute code.
|
||||
WrenInterpretResult result = wrenInterpret(otherVM, "main", source);
|
||||
if (result != WREN_RESULT_SUCCESS)
|
||||
{
|
||||
wrenSetSlotString(vm, 0, "error");
|
||||
}
|
||||
else
|
||||
{
|
||||
wrenSetSlotString(vm, 0, "success");
|
||||
}
|
||||
|
||||
wrenFreeVM(otherVM);
|
||||
}
|
||||
|
||||
static void noResolver(WrenVM* vm)
|
||||
{
|
||||
WrenConfiguration configuration;
|
||||
wrenInitConfiguration(&configuration);
|
||||
|
||||
// Should default to no resolution function.
|
||||
if (configuration.resolveModuleFn != NULL)
|
||||
{
|
||||
wrenSetSlotString(vm, 0, "Did not have null resolve function.");
|
||||
return;
|
||||
}
|
||||
|
||||
runTestVM(vm, &configuration, "import \"foo/bar\"");
|
||||
}
|
||||
|
||||
static const char* resolveToNull(WrenVM* vm, const char* importer,
|
||||
const char* name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void returnsNull(WrenVM* vm)
|
||||
{
|
||||
WrenConfiguration configuration;
|
||||
wrenInitConfiguration(&configuration);
|
||||
|
||||
configuration.resolveModuleFn = resolveToNull;
|
||||
runTestVM(vm, &configuration, "import \"foo/bar\"");
|
||||
}
|
||||
|
||||
static const char* resolveChange(WrenVM* vm, const char* importer,
|
||||
const char* name)
|
||||
{
|
||||
// Concatenate importer and name.
|
||||
size_t length = strlen(importer) + 1 + strlen(name) + 1;
|
||||
char* result = malloc(length);
|
||||
strcpy(result, importer);
|
||||
strcat(result, "/");
|
||||
strcat(result, name);
|
||||
|
||||
// Replace "|" with "/".
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
if (result[i] == '|') result[i] = '/';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void changesString(WrenVM* vm)
|
||||
{
|
||||
WrenConfiguration configuration;
|
||||
wrenInitConfiguration(&configuration);
|
||||
|
||||
configuration.resolveModuleFn = resolveChange;
|
||||
runTestVM(vm, &configuration, "import \"foo|bar\"");
|
||||
}
|
||||
|
||||
static void shared(WrenVM* vm)
|
||||
{
|
||||
WrenConfiguration configuration;
|
||||
wrenInitConfiguration(&configuration);
|
||||
|
||||
configuration.resolveModuleFn = resolveChange;
|
||||
runTestVM(vm, &configuration, "import \"foo|bar\"\nimport \"foo/bar\"");
|
||||
}
|
||||
|
||||
static void importer(WrenVM* vm)
|
||||
{
|
||||
WrenConfiguration configuration;
|
||||
wrenInitConfiguration(&configuration);
|
||||
|
||||
configuration.resolveModuleFn = resolveChange;
|
||||
runTestVM(vm, &configuration, "import \"baz|bang\"");
|
||||
}
|
||||
|
||||
WrenForeignMethodFn resolutionBindMethod(const char* signature)
|
||||
{
|
||||
if (strcmp(signature, "static Resolution.noResolver()") == 0) return noResolver;
|
||||
if (strcmp(signature, "static Resolution.returnsNull()") == 0) return returnsNull;
|
||||
if (strcmp(signature, "static Resolution.changesString()") == 0) return changesString;
|
||||
if (strcmp(signature, "static Resolution.shared()") == 0) return shared;
|
||||
if (strcmp(signature, "static Resolution.importer()") == 0) return importer;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void resolutionBindClass(const char* className, WrenForeignClassMethods* methods)
|
||||
{
|
||||
// methods->allocate = foreignClassAllocate;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn resolutionBindMethod(const char* signature);
|
||||
void resolutionBindClass(const char* className, WrenForeignClassMethods* methods);
|
||||
@@ -0,0 +1,39 @@
|
||||
class Resolution {
|
||||
foreign static noResolver()
|
||||
foreign static returnsNull()
|
||||
foreign static changesString()
|
||||
foreign static shared()
|
||||
foreign static importer()
|
||||
}
|
||||
|
||||
// If no resolver function is configured, the default resolver just passes
|
||||
// along the import string unchanged.
|
||||
System.print(Resolution.noResolver())
|
||||
// expect: loading foo/bar
|
||||
// expect: ok
|
||||
// expect: success
|
||||
|
||||
// If the resolver returns NULL, it's reported as an error.
|
||||
System.print(Resolution.returnsNull())
|
||||
// expect: Could not resolve module 'foo/bar' imported from 'main'.
|
||||
// expect: error
|
||||
|
||||
// The resolver function can change the string.
|
||||
System.print(Resolution.changesString())
|
||||
// expect: loading main/foo/bar
|
||||
// expect: ok
|
||||
// expect: success
|
||||
|
||||
// Imports both "foo/bar" and "foo|bar", but only loads the module once because
|
||||
// they resolve to the same module.
|
||||
System.print(Resolution.shared())
|
||||
// expect: loading main/foo/bar
|
||||
// expect: ok
|
||||
// expect: success
|
||||
|
||||
// The string passed as importer is the resolver string of the importing module.
|
||||
System.print(Resolution.importer())
|
||||
// expect: loading main/baz/bang
|
||||
// expect: loading main/baz/bang/foo/bar
|
||||
// expect: ok
|
||||
// expect: success
|
||||
Reference in New Issue
Block a user