logo
The Falcon Programming Language
A fast, easy and powerful programming language
Location: Home page >> Falcon wiki
User ID:
Password:
 

Falcon Wiki - Embedding:The basics


Falcon is an highly customizable scripting engine. "Highly customizable" means that there's a lot to customize, but for the vast majority of the applications the default behavior and values are sufficient. Nevertheless, Falcon does not provide (and won't provide in future) a "run-the-script" call directly from the engine. A minimal setup and a some few basic steps must be taken to have a script up and running; this in light of the fact that embedding applications rarely (if ever) need "just" to "run that script".

The vast majority of embedding applications need to do something with the scripts they load. At least, they may want to inspect them, put them in a GUI list, relaunch them and so on. Still, a great part of the embedding applications will want also to continue using the scripts after they are loaded, for example by calling the script when some event occurs. So, in our opinion the "run-the-script( scriptName )" grammar is just a hoax some engines may have and sell the idea to naive developers, usually while documenting it with sentences as "it's SOOOO EASY to use our engine: see, just call run-the-script!"

On our opinion, it doesn't matter if it takes one, three, five or seven calls to run a script; what's matter is how easily the engine can be integrated in an application, without requiring changes, compromises, limitations and conditions to be taken into account by the embedding writers.

But yes, 11 lines would be a bit too much if it's "just to run a script" ;-).

Minimal binding

A minimal setup for falcon requires the following components:

  1. A module loader, that tells the falcon engine where to search for modules and included files.
  2. A "runtime", that creates a protected environment in which sub-modules can be loaded by a main module.
  3. The "core module", that is the main language-level functions and classes.
  4. And finally the virtual machine, that is needed to run the main module.

Also, the engine, which takes care of global house keeping, needs to be started before any virutal machine is created, and terminated before your program exits.

See this basic example, that we're going to comment here below.

A very basic example.

First, we need to include the engine headers. This brings in everything:

#include <falcon/engine.h>

As we must remember to turn on and off the engine, a cute way is to encapsulate our embedder in a single class. Remember that this is not necessary, and at times you'll want different solutions, but here we want to keep things simple:

class AppFalcon
{
public:
   AppFalcon();
   ~AppFalcon();
   int embed( const char *script_name );
};

The constructor will start the engine, while the destructor will terminate it.

AppFalcon::AppFalcon()
{
   Falcon::Engine::Init();
}

AppFalcon::~AppFalcon()
{
   Falcon::Engine::Shutdown();
}

Next we implement our embedding method. Let's see it in details. First, we need to provide a module loader that will load our script, and eventually other scripts or modules the main scripts may need to load:

int AppFalcon::embed( const char *script_name )
{
   Falcon::ModuleLoader theLoader(".");
   theLoader.addFalconPath();

   //...

The loader is created with "." as the search path (that is, the same directory we start the program from; also, addFalconPath() method adds the default search paths for modules, including the path burned in at compile time (the default path for the target platform) and eventually the path stored in the FALCON_LOAD_PATH environment variable. As this may not always be what you want, it's left as an optional step.

Time to create the runtime, that is, the load area where our source script and all the modules it requires will be loaded:

   //...

   Falcon::Runtime rt( &theLoader );
   rt.loadFile( script_name );
   
   //...

In case of error at compile time, or in finding one of the dependencies of the script, a newly allocated instance of class Falcon::Error is thrown; we'll see later how to catch and use it.

We can now create the virtual machine. Virtual machines register themselves with the engine, so they can't be allocated in the stack nor directly deleted, as the engine disposes of them when it's safe to do so.

However, a useful VMachineWrapper class is provided to perform the needed creation and disposal of the virtual machines. You're not require to use it, but in this simple example it provides some useful shortcuts:

   //...
   
   Falcon::VMachineWrapper vm;
   
   //...

We now need to link the runtime, filled with modules; but first, let's give the VM what every falcon scripts expects to find: the core module.

   //...

   vm->link( Falcon::core_module_init() );  // add the core module
   
   //...

Again, you're not required to use the core module as is; we'll see how to modify modules, later on. You're not even required to add the core module at all; you may wish to provide scripts with some limited functionalities.

Then, we can link our runtime, filled with the needed module, and launch our script.

   //...
   vm->link( &rt );
   vm->launch();
   return 0;
}

In the main program, we're just wrapping the embed() call in a try/catch block to catch for errors, after some minimal startup:

int main( int argc, char *argv[] )
{
   AppFalcon myApp;

   if ( argc < 2 ) {
      std::cout << "Please, provide a script name" << std::endl;
      return 0;
   }

   char *script_name = argv[0];

   try {
      return myApp.embed( script_name );
   }
   //...

Error Catching

You'll notice that we catch a Falcon::Error* around the whole Falcon embedding:


   //...
   try {
      return myApp.embed( script_name );
   }
   catch( Falcon::Error* err )
   {
       //...
   }
}

This is because the instances of that error could be shared across the script, the virtual machine and the application. The error we catch here may come from the compiler, the module loader, or the VM, and could be generated either before or during the execution of a script. This very instance of the error we catch may currently be stored deep inside the VM, somewhere in more than one script variable, as a script may have tried to catch this error too, and then failed, letting the exception to emerge up to the application level.

As such, errors are caught by pointer, and they are reference counted. They can't be directly deleted (their destructor is private); as the application catches them, they get one extra reference, so that when the application is done, they just need to be de-referenced.

Errors contain informations on the item that generated them (compiler, module loader, virtual machine, script, user module), the stack trace and the line/source file where the error was found. If they are thrown runtime, they also carry the information about the Pseudocode Counter (PC) in the virtual machine at which the error was thrown.

For our simple test, we'll just let the error describe itself and write that description on a standard stream. A complex embedding application may wish to handle the error differently (i.e. store them in a GUI list for the user to inspect).

The catch part is thus simply:

   catch( Falcon::Error* err )
   {
      Falcon::AutoCString edesc( err->toString() );
      std::cerr << edesc.c_str() << std::endl;
      err->decref();
      return 1;
   }

Falcon Strings and C strings

In the above example, we see that the Falcon::String returned by the Error::toString method is wrapped into an AutoCString class:

   Falcon::AutoCString edesc( err->toString() );

This is because Falcon strings are actually special objects which can be used directly in scripts, and are not totally adequate for an immediate representation in C/C++ programs.

The AutoCString class is a simple and efficient way to transform a Falcon string in an UTF-8 encoded C string. It uses a stack area if the string is small enough, and eventually accounts for proper allocation otherwise.

On wide-character environments (Visual Studio, wchar_t* enabled STL with gcc and so on), you'll prefer AutoWString, which transform a falcon string in a system specific wchar_t* string.

Finally, a fast debug printf() may use the String::c_ize() method. That method just adds an extra 0 at the end of the internal buffer, in appropriate character width, so that a printf can be performed directly on the inner data (which is available through the String::getRawStorage() method).

Passing data to and from the VM.

The next example shows how send application informations to the script, before it starts, and how to retrieve its output when it terminates.

In its easiest incarnation passing data to a script means, to store items in global variables declared by the modules (and scripts) you link in the VM. The core module sets up three important global entities: script_name, script_path and args variables, which are meant to be filled in any stand-alone script (i.e. when run through the Falcon command line). They are less important in an embedding context, but let's see how it works just for reference.

After linking the core module in the VM, it is possible to perform the following steps: First, get a pointer to the global variables named after the global symbols:

   vm->link( &rt );

   // ... new code:
   Falcon::Item *scriptName = vm->findGlobalItem( "scriptName" );
   Falcon::Item *scriptPath = vm->findGlobalItem( "scriptPath" );
   Falcon::Item *args = vm->findGlobalItem( "args" );

The VMachine::findGlobalItem method returns 0 if the item is not found, but we expect them to be there (as they are declared in the Core module).

The script name can be determined by reading it from the main module linked in the virtual machine, while the path is simply the full path of the script we have loaded:

   const Falcon::Module *mainMod = vm->mainModule()->module();

   *scriptName = new Falcon::CoreString( mainMod->name() );
   *scriptPath = new Falcon::CoreString( script_name );

Setting the item is just a matter of assigning a Falcon string the de-referenced item pointer. In general, the Falcon::Item constructor and the overloaded assignment operator can take good care of transforming pointers to Core*, Garbageable, and numeric data into an item; just pay attention to integers (which should be cast to Falcon::int64, or may be confused), and booleans (some compilers seems to be a bit flaky about the detection of boolean type, and tend to confuse it with integers).

Creating the args array is similar:

   Falcon::CoreArray *argsArray = new Falcon::CoreArray;
   for( int i = 0; i < argc; i ++ )
   {
      argsArray->append( new Falcon::CoreString( argv[i] ) );
   }
   *args = argsArray;

Then you can start your script with the usual VMachine::launch method.

Notice that the above standard setup can be performed through the VMachine::setupScript method.

Finally, the output of the script, or in other words, what the main script returns through an explicit return statement, is stored in the A register of the virtual machine, which can be accessed through the VMachine::regA method.

As we don't really know what the script may have return, and we don't expect a particular type, it's advisable to turn it into a string.

   vm->launch();

   // ... new code
   Falcon::String str_regA;
   vm->regA().toString( str_regA );

Remember that launch will throw an error in case of uncaught exceptions raised from within the scripts, so the code below gets executed only on a clean termination.

Then, we can show it in the same way we show an eventual error:

   Falcon::AutoCString c_regA( str_regA );
   std::cout << "VM Output: " << c_regA.c_str() << std::endl

In case the script didn't return anything, the A register holds a nil value.


Navigation
Go To Page...

Loading

Elapsed time: 0.046 secs. (VM time 0.038 secs.)