Closed IngwiePhoenix closed 5 years ago
The core module is loaded from a wren file, which you can see here, the c part just hooks up the bindings to the functions and types as needed. Some have bindings, some are only wren.
Note that the wren file is used as a reference, and then generated into this c file that gets compiled in. So if you just change the wren source, it won't show up. You'd have to make sure the inc is updated, which i think is in the python somewhere (I've haven't needed to change that yet).
Another (maybe less ideal) approach is to pre-concatenate the loaded module source, at import
time. Since you have the control there, you can just do return "import "file" for File" + originalSource
in your loadModuleFn callback.
So if I changed (and then re-generated) that wren file, I would be able to add to the core module, which is loaded every time? Or can I also just use the fact that the module ID is NULL
and put my stuff there?
I guessed as far with that Wren file, I just finished reading it actually. There are way more classes in there than I had actually known...
There's a distinction between how the core module is handled vs regular foreign functions though, the PRIMITIVE vs FOREIGN distinction among other things. I'd be wary of changing it that way first. If you just want an automatic import, there's simpler ways, like the string splicing, or defining variables in the module.
If you're willing to change the core module, then you can also just define variables inside the load function by giving yourself a second callback in the config, and calling out to yourself with the module from inside the vm code. From there you can manually import whatever you want after the fact too, using the api only.
I see. Well I guess all that's left for me is to figure out a good way to do so.
Though, there really should be - in my opinion - a way for users to define "always present" classes and variables without having to go as far as to modifying the core's actual files.
Though, would you may elaborate on what exactly you meant with:
If you just want an automatic import, there's simpler ways, like the string splicing, or defining variables in the module.
If I use wrenInterpret
on the main module - as in, the first one that I will be loading - it will not affect any other modules in the variables and classes they have. At least, that is how I had understood it so far. And, do you mean that I prepend some source before any other source that I load with what you called "string splicing"? I am not a native speaker - so "splicing" is confusing me a little.
No problem, so let's look at what happens when you load a module:
import string
Value wrenImportModule(WrenVM* vm, Value name)
inside wren_vm.c
source = vm->config.loadModuleFn(vm, AS_CSTRING(name));
ObjFiber* moduleFiber = compileInModule(vm, name, source, false, true);
Now, we can see that loadModuleFn
can modify the source (in your own code) before returning it to wren. This loadModuleFn
is set on the config when configuring the VM, before creating it. The code isn't really functional below, it's just to illustrate.
static char* loadModule(WrenVM* vm, const char* module) {
//embedder specific
const char* module_source = load_file(module);
const char* default_imports = "import \"file\" for File\n"
...
const char* result = strcat(default_imports, module_source);
return result;
}
Here, we just joined the two strings together, before giving it to the virtual machine to compile. Now every module ever, has the File
variable already defined. No vm/core changes.
The second method is to change the vm. I would start with the easier path above.
Minor issue with that solution, it will mess line numbering.
It appears that this is pretty much the "safest" option out there, as I am not fond of messing with the core. Yes, it may end up messing around with the line numbers, but I guess there is no other option - yet.
Thank you very much for taking the time to help! :)
Yea the lines would be off, good to note.
One step up to changing the vm, can add compileInModule
inside the wrenImportModule
function with your own imports, which wouldn't change lines.
My earlier suggestion was to add an additional callback. There is loadModuleFn, you could add postLoadModuleFn and do any extern stuff in your own code, with minimal vm change. Either way should work for now.
I have accidentially encountered an even less issue-y thing: Calling wrenInterpret
two consecutive times does not mess up the lines AND runs in the same context, when module
matches. See #631 - I edited the code some more, and actually found that out. I dont know if that is intended, but it works - without breaking line numbers! :)
This one will correctly throw an error on internal:main:3
- and if you remove the false statement on that line, it will print the proper value of x
.
#include <stdio.h>
#include "wren.h"
void printOut(WrenVM* vm, const char* text) {
printf("%s", text);
}
void printErr(
WrenVM* vm, WrenErrorType type,
const char* module, int line,
const char* message
) {
switch(type) {
case WREN_ERROR_RUNTIME:
printf("[wren] ERR: Runtime reported: \"%s\"\n", message);
return;
case WREN_ERROR_COMPILE:
printf("[wren] ERR: (%s:%i) %s\n", module, line, message);
return;
case WREN_ERROR_STACK_TRACE:
printf("[wren] STACK: %s:%i = %s", module, line, message);
return;
}
}
const char* rootCode =
"class A {\n"
" construct new() { System.print(\"Constructed\") }\n"
"}\n"
"var x = 123";
const char* script =
"var a = A.new()\n"
"System.print(x)\n"
"nonExistantFunction()";
const char* rootModule = "internal:main";
int main(int argc, const char** argv) {
WrenVM* vm;
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = printOut;
config.errorFn = printErr;
vm = wrenNewVM(&config);
WrenInterpretResult rt;
printf("Root: \n");
rt = wrenInterpret(vm, rootModule, rootCode);
if(rt != WREN_RESULT_SUCCESS) exit(1);
printf("Main: \n");
rt = wrenInterpret(vm, rootModule, script);
if(rt != WREN_RESULT_SUCCESS) exit(1);
wrenFreeVM(vm);
exit(0);
}
And with that...all I need is to sneak a wrenInterpret
call to before the actual source is being executed. Probably in loadModuleFn
?
const char* loadModuleFn(/* ... */) {
// Find source, store it into src.
const char* src;
// Call wrenInterpret before returning, maybe?
wrenInterpret(vm, module, preSrc);
return src;
}
You think this would work?
I went ahead and tried it - it works.
// Dummy method. Only ever returns one, plain module.
char* loadModuleFn(WrenVM* vm, const char* module) {
printf("> Dummy loaded: %s\n", module);
char* dumbSrc = "System.print(\"Pre module code\")";
char* dumbSrc_ptr = (char*)malloc(strlen(dumbSrc)+1);
strcpy(dumbSrc_ptr, dumbSrc_ptr);
wrenInterpret(vm, module, dumbSrc);
char* src = "var MyName = \"Ingwie\"";
char* src_ptr = (char*)malloc(strlen(src)+1);
strcpy(src_ptr, src);
return src_ptr;
}
Yes... my memory management is lacky - but I was just trying to hack together a solution that'll work for the meantime of the test. :)
A little bit hacky way would be to create a preamble callback and a function to declare objects. This is how core object does, could be done in ~15 lines of code I think, if one could get Wren handle to inject.
I would like to make a few things from my application available to my users immediately, in every file, without having to
import "the module" for vars
. :)Now, jokes aside, I looked up how
System
is registered, and came across this line: https://github.com/munificent/wren/blob/master/src/vm/wren_core.c#L1146-L1147How would I go about, possibly without having to directly include the related headers and directly interact with the stack - so Wren doesn't suddenly act awkward due to a mistake I made - adding a "root" object?
For instance, I want to make a
File
class available immediately without having to import it each and every time that I use it.