Start getting module loading working.
Right now, it uses a weird "import_" method on String which should either be replaced or at least hidden behind some syntax. But it does roughly the right thing. Still lots of corner cases to clean up and stuff to fix. In particular: - Need to handle compilation errors in imported modules. - Need to implicitly import all core and IO types into imported module. - Need to handle circular imports. (Just need to give entry module the right name for this to work.)
This commit is contained in:
+64
@@ -11,6 +11,8 @@
|
||||
// This is the source file for the standalone command line interpreter. It is
|
||||
// not needed if you are embedding Wren in an application.
|
||||
|
||||
char* rootDirectory = NULL;
|
||||
|
||||
static void failIf(bool condition, int exitCode, const char* format, ...)
|
||||
{
|
||||
if (!condition) return;
|
||||
@@ -48,8 +50,66 @@ static char* readFile(const char* path)
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static char* readModule(WrenVM* vm, const char* module)
|
||||
{
|
||||
// The module path is relative to the root directory.
|
||||
size_t rootLength = strlen(rootDirectory);
|
||||
size_t moduleLength = strlen(module);
|
||||
size_t pathLength = rootLength + moduleLength;
|
||||
char* path = malloc(pathLength + 1);
|
||||
memcpy(path, rootDirectory, rootLength);
|
||||
memcpy(path + rootLength, module, moduleLength);
|
||||
path[pathLength] = '\0';
|
||||
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (file == NULL)
|
||||
{
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find out how big the file is.
|
||||
fseek(file, 0L, SEEK_END);
|
||||
size_t fileSize = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
// Allocate a buffer for it.
|
||||
char* buffer = (char*)malloc(fileSize + 1);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
fclose(file);
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Read the entire file.
|
||||
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
||||
if (bytesRead < fileSize)
|
||||
{
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Terminate the string.
|
||||
buffer[bytesRead] = '\0';
|
||||
|
||||
fclose(file);
|
||||
free(path);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static int runFile(WrenVM* vm, const char* path)
|
||||
{
|
||||
// Use the directory where the file is as the root to resolve imports
|
||||
// relative to.
|
||||
char* lastSlash = strrchr(path, '/');
|
||||
rootDirectory = malloc(lastSlash - path + 2);
|
||||
memcpy(rootDirectory, path, lastSlash - path + 1);
|
||||
rootDirectory[lastSlash - path + 1] = '\0';
|
||||
|
||||
char* source = readFile(path);
|
||||
|
||||
int result;
|
||||
@@ -75,6 +135,8 @@ static int runRepl(WrenVM* vm)
|
||||
printf("\\\\/\"-\n");
|
||||
printf(" \\_/ wren v0.0.0\n");
|
||||
|
||||
// TODO: Set rootDirectory to current working directory so imports work.
|
||||
|
||||
char line[MAX_LINE_LENGTH];
|
||||
|
||||
for (;;)
|
||||
@@ -109,6 +171,8 @@ int main(int argc, const char* argv[])
|
||||
|
||||
WrenConfiguration config;
|
||||
|
||||
config.loadModuleFn = readModule;
|
||||
|
||||
// Since we're running in a standalone process, be generous with memory.
|
||||
config.initialHeapSize = 1024 * 1024 * 100;
|
||||
|
||||
|
||||
+62
-1
@@ -101,7 +101,18 @@ static const char* libSource =
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class String is Sequence {}\n"
|
||||
"class String is Sequence {\n"
|
||||
" import_(variable) {\n"
|
||||
" var result = loadModule_\n"
|
||||
" if (result == false) {\n"
|
||||
" Fiber.abort(\"Could not find module '\" + this + \"'.\")\n"
|
||||
" } else if (result != true) {\n"
|
||||
" result.call\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return lookUpVariable_(variable)\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class List is Sequence {\n"
|
||||
" addAll(other) {\n"
|
||||
@@ -1294,6 +1305,51 @@ DEF_NATIVE(string_subscript)
|
||||
RETURN_OBJ(result);
|
||||
}
|
||||
|
||||
DEF_NATIVE(string_loadModule)
|
||||
{
|
||||
RETURN_VAL(wrenImportModule(vm, AS_CSTRING(args[0])));
|
||||
}
|
||||
|
||||
DEF_NATIVE(string_lookUpVariable)
|
||||
{
|
||||
uint32_t moduleEntry = wrenMapFind(vm->modules, args[0]);
|
||||
ASSERT(moduleEntry != UINT32_MAX, "Should only look up loaded modules.");
|
||||
|
||||
ObjModule* module = AS_MODULE(vm->modules->entries[moduleEntry].value);
|
||||
|
||||
ObjString* variableName = AS_STRING(args[1]);
|
||||
uint32_t variable = wrenSymbolTableFind(&module->variableNames,
|
||||
variableName->value,
|
||||
variableName->length);
|
||||
|
||||
// It's a runtime error if the imported variable does not exist.
|
||||
if (variable == UINT32_MAX)
|
||||
{
|
||||
// TODO: This is pretty verbose. Do something cleaner?
|
||||
ObjString* moduleName = AS_STRING(args[0]);
|
||||
int length = 48 + variableName->length + moduleName->length;
|
||||
ObjString* error = AS_STRING(wrenNewUninitializedString(vm, length));
|
||||
|
||||
char* start = error->value;
|
||||
memcpy(start, "Could not find a variable named '", 33);
|
||||
start += 33;
|
||||
memcpy(start, variableName->value, variableName->length);
|
||||
start += variableName->length;
|
||||
memcpy(start, "' in module '", 13);
|
||||
start += 13;
|
||||
memcpy(start, moduleName->value, moduleName->length);
|
||||
start += moduleName->length;
|
||||
memcpy(start, "'.", 2);
|
||||
start += 2;
|
||||
*start = '\0';
|
||||
|
||||
args[0] = OBJ_VAL(error);
|
||||
return PRIM_ERROR;
|
||||
}
|
||||
|
||||
RETURN_VAL(module->variables.data[variable]);
|
||||
}
|
||||
|
||||
static ObjClass* defineSingleClass(WrenVM* vm, const char* name)
|
||||
{
|
||||
size_t length = strlen(name);
|
||||
@@ -1495,6 +1551,11 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
NATIVE(vm->rangeClass, "iteratorValue ", range_iteratorValue);
|
||||
NATIVE(vm->rangeClass, "toString", range_toString);
|
||||
|
||||
// TODO: Putting these on String is pretty strange. Find a better home for
|
||||
// them.
|
||||
NATIVE(vm->stringClass, "loadModule_", string_loadModule);
|
||||
NATIVE(vm->stringClass, "lookUpVariable_ ", string_lookUpVariable);
|
||||
|
||||
// While bootstrapping the core types and running the core library, a number
|
||||
// string objects have been created, many of which were instantiated before
|
||||
// stringClass was stored in the VM. Some of them *must* be created first:
|
||||
|
||||
@@ -37,6 +37,7 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
vm->reallocate = reallocate;
|
||||
vm->foreignCallSlot = NULL;
|
||||
vm->foreignCallNumArgs = 0;
|
||||
vm->loadModule = configuration->loadModuleFn;
|
||||
|
||||
wrenSymbolTableInit(vm, &vm->methodNames);
|
||||
|
||||
@@ -1123,6 +1124,57 @@ void wrenPopRoot(WrenVM* vm)
|
||||
vm->numTempRoots--;
|
||||
}
|
||||
|
||||
Value wrenImportModule(WrenVM* vm, const char* name)
|
||||
{
|
||||
Value nameValue = wrenNewString(vm, name, strlen(name));
|
||||
wrenPushRoot(vm, AS_OBJ(nameValue));
|
||||
|
||||
// If the module is already loaded, we don't need to do anything.
|
||||
uint32_t index = wrenMapFind(vm->modules, nameValue);
|
||||
if (index != UINT32_MAX)
|
||||
{
|
||||
wrenPopRoot(vm); // nameValue.
|
||||
return TRUE_VAL;
|
||||
}
|
||||
|
||||
// Load the module's source code from the embedder.
|
||||
char* source = vm->loadModule(vm, name);
|
||||
if (source == NULL)
|
||||
{
|
||||
wrenPopRoot(vm); // nameValue.
|
||||
return FALSE_VAL;
|
||||
}
|
||||
|
||||
ObjModule* module = wrenNewModule(vm);
|
||||
wrenPushRoot(vm, (Obj*)module);
|
||||
|
||||
// Implicitly import the core libraries.
|
||||
// TODO: Import all implicit names.
|
||||
// TODO: Only import IO if it's loaded.
|
||||
ObjModule* mainModule = getMainModule(vm);
|
||||
int symbol = wrenSymbolTableFind(&mainModule->variableNames, "IO", 2);
|
||||
wrenDefineVariable(vm, module, "IO", 2, mainModule->variables.data[symbol]);
|
||||
|
||||
ObjFn* fn = wrenCompile(vm, module, name, source);
|
||||
// TODO: Handle NULL fn from compilation error.
|
||||
|
||||
wrenPushRoot(vm, (Obj*)fn);
|
||||
|
||||
// Store it in the VM's module registry so we don't load the same module
|
||||
// multiple times.
|
||||
wrenMapSet(vm, vm->modules, nameValue, OBJ_VAL(module));
|
||||
|
||||
ObjFiber* moduleFiber = wrenNewFiber(vm, (Obj*)fn);
|
||||
|
||||
wrenPopRoot(vm); // fn.
|
||||
wrenPopRoot(vm); // module.
|
||||
wrenPopRoot(vm); // nameValue.
|
||||
|
||||
// Return the fiber that executes the module.
|
||||
return OBJ_VAL(moduleFiber);
|
||||
}
|
||||
|
||||
|
||||
static void defineMethod(WrenVM* vm, const char* className,
|
||||
const char* methodName, int numParams,
|
||||
WrenForeignMethodFn methodFn, bool isStatic)
|
||||
|
||||
@@ -246,6 +246,9 @@ struct WrenVM
|
||||
// to the function.
|
||||
int foreignCallNumArgs;
|
||||
|
||||
// The function used to load modules.
|
||||
WrenLoadModuleFn loadModule;
|
||||
|
||||
// Compiler and debugger data:
|
||||
|
||||
// The compiler that is currently compiling code. This is used so that heap
|
||||
@@ -308,6 +311,16 @@ void wrenPushRoot(WrenVM* vm, Obj* obj);
|
||||
// Remove the most recently pushed temporary root.
|
||||
void wrenPopRoot(WrenVM* vm);
|
||||
|
||||
// Imports the module with [name].
|
||||
//
|
||||
// If the module has already been imported (or is already in the middle of
|
||||
// being imported, in the case of a circular import), returns true. Otherwise,
|
||||
// returns a new fiber that will execute the module's code. That fiber should
|
||||
// be called before any variables are loaded from the module.
|
||||
//
|
||||
// If the module could not be found, returns false.
|
||||
Value wrenImportModule(WrenVM* vm, const char* name);
|
||||
|
||||
// Returns the class of [value].
|
||||
//
|
||||
// Defined here instead of in wren_value.h because it's critical that this be
|
||||
|
||||
Reference in New Issue
Block a user