Falcon provides a reflexive compiler and module loader that allows scripts to load other falcon sources, pre-compiled modules or binary modules. The reflexive compiler is actually an interface to the internal compilation and link engine, so anything that can be done by the falcon command line utility can be done also through this facility.
Falcon code structure is fixed; modules are read-only from a Virtual Machine stand point. This means that Falcon cannot alter the structure of scripts being currently executed. In example, it is not possible to compile a single code line and have it executed in the current script context.
However, the reflexive compiler is so flexible that this limitation is hardly felt. It is possible to load a module, or to compile it on the fly, and then get or set any of the global symbols in the target module. By extracting items from the loaded module, or injecting known items into that, it is possible to configure, alter, and execute arbitrary parts of the loaded module as if it were coded internally to the loader script.
The following script shows how a source module may be compiled and executed on the fly.
load compiler // create the compiler c = Compiler() // try to compile a source string str = ' function func( a ) > "Hello from compiled source: ", a end > "The main part of the module" ' try // First param is the module logical name. sourceMod = c.compile( "aString", str ) // in case of compilation error, we had an error and we bailed out // load the symbol func from our module... func = sourceMod.get( "func" ) // and execute it... func( "test param" ) // execute directly the main module, notice the () at eol sourceMod.get( "__main__" )() catch CodeError in e > "Had an error: ", e end
We may also put symbols in target scripts, forcing them to execute some part of the loader code. In example, suppose this is a falcon source called "module1.fal":
// this global variable will be mangled by the loader fromLoader = nil function test() > "Test from module 1" // trustfully call the readied global fromLoader() end The loader will look like this: load compiler c = Compiler() function saySomething() > "Something said from the main module." end // Load a module by its path. mod = c.loadModule( "./module1.fal" ) // change target symbol mod.set( "fromLoader", saySomething ) // call loaded function mod.get( "test" )()
Of course, scripts can exchange objects, classes, methods, functions, lambdas and in general any value.
It is not possible to set directly a global object method in the target module in this way. In example, if the target module defines an object Obj, with a property called prop, it is not possible to call
mod.set( "Obj.prop", someValue )
However, the object can be imported in the local script, and any change to its structure will be reflected in both the original owner and the loader. In example, if alphadef.fal defines the object:
object alpha prop = nil function callProp() self.prop( "Called internally from self." ) end end Then the loader may do: load compiler c = Compiler() function saySomething( param ) > "Parameter is: ", param end // Load a module by its name. // It means, search a suitable .fal, .fam or binary module. mod = c.loadByName( "alphadef" ) // get the object obj = mod.get( "alpha" ) // and change the property to something in our script obj.prop = saySomething // then call it obj.callProp()
As expected, the result is:
$ ./falcon alphaload.fal Parameter is: Called internally from self.