Split out the core and main modules.
- Implicitly import everything in core into every imported module. - Test cyclic imports.
This commit is contained in:
parent
2005e1e0a2
commit
bc5a793c41
@ -109,8 +109,13 @@ void wrenFreeVM(WrenVM* vm);
|
|||||||
// Runs [source], a string of Wren source code in a new fiber in [vm].
|
// Runs [source], a string of Wren source code in a new fiber in [vm].
|
||||||
//
|
//
|
||||||
// [sourcePath] is a string describing where [source] was located, for use in
|
// [sourcePath] is a string describing where [source] was located, for use in
|
||||||
// debugging stack traces. It must not be `null`. If an empty string, it will
|
// debugging stack traces. It must not be `null`.
|
||||||
// not be shown in a stack trace.
|
//
|
||||||
|
// If it's an empty string, then [source] is considered part of the "core"
|
||||||
|
// module. Any module-level names defined in it will be implicitly imported by
|
||||||
|
// another other modules. This also means runtime errors in its code will be
|
||||||
|
// omitted from stack traces (to avoid confusing users with core library
|
||||||
|
// implementation details).
|
||||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
||||||
const char* source);
|
const char* source);
|
||||||
|
|
||||||
|
|||||||
184
src/wren_vm.c
184
src/wren_vm.c
@ -1029,47 +1029,133 @@ static bool runInterpreter(WrenVM* vm)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks up the main module in the module map.
|
// Looks up the core module in the module map.
|
||||||
static ObjModule* getMainModule(WrenVM* vm)
|
static ObjModule* getCoreModule(WrenVM* vm)
|
||||||
{
|
{
|
||||||
uint32_t entry = wrenMapFind(vm->modules, NULL_VAL);
|
uint32_t entry = wrenMapFind(vm->modules, NULL_VAL);
|
||||||
ASSERT(entry != UINT32_MAX, "Could not find main module.");
|
ASSERT(entry != UINT32_MAX, "Could not find core module.");
|
||||||
return AS_MODULE(vm->modules->entries[entry].value);
|
return AS_MODULE(vm->modules->entries[entry].value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ObjFiber* loadModule(WrenVM* vm, Value name, const char* source)
|
||||||
|
{
|
||||||
|
ObjModule* module = wrenNewModule(vm);
|
||||||
|
wrenPushRoot(vm, (Obj*)module);
|
||||||
|
|
||||||
|
// Implicitly import the core module.
|
||||||
|
ObjModule* coreModule = getCoreModule(vm);
|
||||||
|
for (int i = 0; i < coreModule->variables.count; i++)
|
||||||
|
{
|
||||||
|
wrenDefineVariable(vm, module,
|
||||||
|
coreModule->variableNames.data[i].buffer,
|
||||||
|
coreModule->variableNames.data[i].length,
|
||||||
|
coreModule->variables.data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjFn* fn = wrenCompile(vm, module, AS_CSTRING(name), source);
|
||||||
|
if (fn == NULL)
|
||||||
|
{
|
||||||
|
// TODO: Should we still store the module even if it didn't compile?
|
||||||
|
wrenPopRoot(vm); // module.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, name, OBJ_VAL(module));
|
||||||
|
|
||||||
|
ObjFiber* moduleFiber = wrenNewFiber(vm, (Obj*)fn);
|
||||||
|
|
||||||
|
wrenPopRoot(vm); // fn.
|
||||||
|
wrenPopRoot(vm); // module.
|
||||||
|
|
||||||
|
// Return the fiber that executes the module.
|
||||||
|
return moduleFiber;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute [source] in the context of the core module.
|
||||||
|
WrenInterpretResult static loadIntoCore(WrenVM* vm, const char* source)
|
||||||
|
{
|
||||||
|
ObjModule* coreModule = getCoreModule(vm);
|
||||||
|
|
||||||
|
ObjFn* fn = wrenCompile(vm, coreModule, "", source);
|
||||||
|
if (fn == NULL) return WREN_RESULT_COMPILE_ERROR;
|
||||||
|
|
||||||
|
wrenPushRoot(vm, (Obj*)fn);
|
||||||
|
vm->fiber = wrenNewFiber(vm, (Obj*)fn);
|
||||||
|
wrenPopRoot(vm); // fn.
|
||||||
|
|
||||||
|
return runInterpreter(vm) ? WREN_RESULT_SUCCESS : WREN_RESULT_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
|
||||||
const char* source)
|
const char* source)
|
||||||
{
|
{
|
||||||
// TODO: Check for freed VM.
|
if (strlen(sourcePath) == 0) return loadIntoCore(vm, source);
|
||||||
ObjFn* fn = wrenCompile(vm, getMainModule(vm), sourcePath, source);
|
|
||||||
if (fn == NULL) return WREN_RESULT_COMPILE_ERROR;
|
// TODO: Better module name.
|
||||||
|
Value name = wrenNewString(vm, "main", 4);
|
||||||
|
wrenPushRoot(vm, AS_OBJ(name));
|
||||||
|
|
||||||
wrenPushRoot(vm, (Obj*)fn);
|
ObjFiber* fiber = loadModule(vm, name, source);
|
||||||
vm->fiber = wrenNewFiber(vm, (Obj*)fn);
|
if (fiber == NULL)
|
||||||
wrenPopRoot(vm);
|
{
|
||||||
|
wrenPopRoot(vm);
|
||||||
|
return WREN_RESULT_COMPILE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
if (runInterpreter(vm))
|
vm->fiber = fiber;
|
||||||
|
|
||||||
|
bool succeeded = runInterpreter(vm);
|
||||||
|
|
||||||
|
wrenPopRoot(vm); // name.
|
||||||
|
|
||||||
|
return succeeded ? WREN_RESULT_SUCCESS : WREN_RESULT_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
return WREN_RESULT_SUCCESS;
|
wrenPopRoot(vm); // nameValue.
|
||||||
|
return TRUE_VAL;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// Load the module's source code from the embedder.
|
||||||
|
char* source = vm->loadModule(vm, name);
|
||||||
|
if (source == NULL)
|
||||||
{
|
{
|
||||||
return WREN_RESULT_RUNTIME_ERROR;
|
wrenPopRoot(vm); // nameValue.
|
||||||
|
return FALSE_VAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjFiber* moduleFiber = loadModule(vm, nameValue, source);
|
||||||
|
|
||||||
|
wrenPopRoot(vm); // nameValue.
|
||||||
|
|
||||||
|
// Return the fiber that executes the module.
|
||||||
|
return OBJ_VAL(moduleFiber);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value wrenFindVariable(WrenVM* vm, const char* name)
|
Value wrenFindVariable(WrenVM* vm, const char* name)
|
||||||
{
|
{
|
||||||
ObjModule* mainModule = getMainModule(vm);
|
ObjModule* coreModule = getCoreModule(vm);
|
||||||
int symbol = wrenSymbolTableFind(&mainModule->variableNames,
|
int symbol = wrenSymbolTableFind(&coreModule->variableNames,
|
||||||
name, strlen(name));
|
name, strlen(name));
|
||||||
return mainModule->variables.data[symbol];
|
return coreModule->variables.data[symbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||||
size_t length)
|
size_t length)
|
||||||
{
|
{
|
||||||
if (module == NULL) module = getMainModule(vm);
|
if (module == NULL) module = getCoreModule(vm);
|
||||||
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
||||||
|
|
||||||
wrenValueBufferWrite(vm, &module->variables, UNDEFINED_VAL);
|
wrenValueBufferWrite(vm, &module->variables, UNDEFINED_VAL);
|
||||||
@ -1079,7 +1165,7 @@ int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
|||||||
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||||
size_t length, Value value)
|
size_t length, Value value)
|
||||||
{
|
{
|
||||||
if (module == NULL) module = getMainModule(vm);
|
if (module == NULL) module = getCoreModule(vm);
|
||||||
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
||||||
|
|
||||||
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
|
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
|
||||||
@ -1124,56 +1210,6 @@ void wrenPopRoot(WrenVM* vm)
|
|||||||
vm->numTempRoots--;
|
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,
|
static void defineMethod(WrenVM* vm, const char* className,
|
||||||
const char* methodName, int numParams,
|
const char* methodName, int numParams,
|
||||||
WrenForeignMethodFn methodFn, bool isStatic)
|
WrenForeignMethodFn methodFn, bool isStatic)
|
||||||
@ -1189,17 +1225,19 @@ static void defineMethod(WrenVM* vm, const char* className,
|
|||||||
|
|
||||||
ASSERT(methodFn != NULL, "Must provide method function.");
|
ASSERT(methodFn != NULL, "Must provide method function.");
|
||||||
|
|
||||||
// TODO: Which module do these go in?
|
// TODO: Need to be able to define methods in classes outside the core
|
||||||
|
// module.
|
||||||
|
|
||||||
// Find or create the class to bind the method to.
|
// Find or create the class to bind the method to.
|
||||||
ObjModule* mainModule = getMainModule(vm);
|
ObjModule* coreModule = getCoreModule(vm);
|
||||||
int classSymbol = wrenSymbolTableFind(&mainModule->variableNames,
|
int classSymbol = wrenSymbolTableFind(&coreModule->variableNames,
|
||||||
className, strlen(className));
|
className, strlen(className));
|
||||||
ObjClass* classObj;
|
ObjClass* classObj;
|
||||||
|
|
||||||
if (classSymbol != -1)
|
if (classSymbol != -1)
|
||||||
{
|
{
|
||||||
// TODO: Handle name is not class.
|
// TODO: Handle name is not class.
|
||||||
classObj = AS_CLASS(mainModule->variables.data[classSymbol]);
|
classObj = AS_CLASS(coreModule->variables.data[classSymbol]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1211,7 +1249,7 @@ static void defineMethod(WrenVM* vm, const char* className,
|
|||||||
|
|
||||||
// TODO: Allow passing in name for superclass?
|
// TODO: Allow passing in name for superclass?
|
||||||
classObj = wrenNewClass(vm, vm->objectClass, 0, nameString);
|
classObj = wrenNewClass(vm, vm->objectClass, 0, nameString);
|
||||||
wrenDefineVariable(vm, mainModule, className, length, OBJ_VAL(classObj));
|
wrenDefineVariable(vm, coreModule, className, length, OBJ_VAL(classObj));
|
||||||
|
|
||||||
wrenPopRoot(vm);
|
wrenPopRoot(vm);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
// The maximum number of temporary objects that can be made visible to the GC
|
// The maximum number of temporary objects that can be made visible to the GC
|
||||||
// at one time.
|
// at one time.
|
||||||
#define WREN_MAX_TEMP_ROOTS 4
|
#define WREN_MAX_TEMP_ROOTS 5
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
@ -279,6 +279,16 @@ struct WrenVM
|
|||||||
// [oldSize] will be zero. It should return NULL.
|
// [oldSize] will be zero. It should return NULL.
|
||||||
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
|
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
|
||||||
|
|
||||||
|
// 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 value of the module-level variable named [name] in the main
|
// Returns the value of the module-level variable named [name] in the main
|
||||||
// module.
|
// module.
|
||||||
Value wrenFindVariable(WrenVM* vm, const char* name);
|
Value wrenFindVariable(WrenVM* vm, const char* name);
|
||||||
@ -311,16 +321,6 @@ void wrenPushRoot(WrenVM* vm, Obj* obj);
|
|||||||
// Remove the most recently pushed temporary root.
|
// Remove the most recently pushed temporary root.
|
||||||
void wrenPopRoot(WrenVM* vm);
|
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].
|
// Returns the class of [value].
|
||||||
//
|
//
|
||||||
// Defined here instead of in wren_value.h because it's critical that this be
|
// Defined here instead of in wren_value.h because it's critical that this be
|
||||||
|
|||||||
9
test/module/cyclic_import/a.wren
Normal file
9
test/module/cyclic_import/a.wren
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// nontest
|
||||||
|
IO.print("start a")
|
||||||
|
|
||||||
|
var A = "a value"
|
||||||
|
IO.print("a defined ", A)
|
||||||
|
var B = "b".import_("B")
|
||||||
|
IO.print("a imported ", B)
|
||||||
|
|
||||||
|
IO.print("end a")
|
||||||
9
test/module/cyclic_import/b.wren
Normal file
9
test/module/cyclic_import/b.wren
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// nontest
|
||||||
|
IO.print("start b")
|
||||||
|
|
||||||
|
var B = "b value"
|
||||||
|
IO.print("b defined ", B)
|
||||||
|
var A = "a".import_("A")
|
||||||
|
IO.print("b imported ", A)
|
||||||
|
|
||||||
|
IO.print("end b")
|
||||||
11
test/module/cyclic_import/cyclic_import.wren
Normal file
11
test/module/cyclic_import/cyclic_import.wren
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
var A = "a".import_("A")
|
||||||
|
|
||||||
|
// Shared module should only run once:
|
||||||
|
// expect: start a
|
||||||
|
// expect: a defined a value
|
||||||
|
// expect: start b
|
||||||
|
// expect: b defined b value
|
||||||
|
// expect: b imported a value
|
||||||
|
// expect: end b
|
||||||
|
// expect: a imported b value
|
||||||
|
// expect: end a
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
var Module = "module".import_("Module")
|
||||||
|
// expect: Bool
|
||||||
|
// expect: Class
|
||||||
|
// expect: Fiber
|
||||||
|
// expect: Fn
|
||||||
|
// expect: List
|
||||||
|
// expect: Map
|
||||||
|
// expect: MapKeySequence
|
||||||
|
// expect: MapValueSequence
|
||||||
|
// expect: Null
|
||||||
|
// expect: Num
|
||||||
|
// expect: Object
|
||||||
|
// expect: Range
|
||||||
|
// expect: Sequence
|
||||||
|
// expect: String
|
||||||
17
test/module/implicitly_imports_core/module.wren
Normal file
17
test/module/implicitly_imports_core/module.wren
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// nontest
|
||||||
|
var Module = "from module"
|
||||||
|
|
||||||
|
IO.print(Bool)
|
||||||
|
IO.print(Class)
|
||||||
|
IO.print(Fiber)
|
||||||
|
IO.print(Fn)
|
||||||
|
IO.print(List)
|
||||||
|
IO.print(Map)
|
||||||
|
IO.print(MapKeySequence)
|
||||||
|
IO.print(MapValueSequence)
|
||||||
|
IO.print(Null)
|
||||||
|
IO.print(Num)
|
||||||
|
IO.print(Object)
|
||||||
|
IO.print(Range)
|
||||||
|
IO.print(Sequence)
|
||||||
|
IO.print(String)
|
||||||
Loading…
Reference in New Issue
Block a user