New content are available at http://tis-method.org/. All articles from this site will be moved to the new location.

вторник, 18 января 2011 г.

Scripting subsystem based on CInt interpreter


To be compiled or to be interpreted
ru

The software system itself is a heterogeneous and multifaceted entity. It can be examined from different points pursuing different objectives. Any research like this is called to describe a new side or maybe already studied side of a particular problem, of interconnection. And received results help to deeply understand the applied problem and methods for its solution.

Below you will find the description of a mechanism of application functionality extension using CInt interpreter (interpreter for C/C++) as well as the method of unified scripts execution both in interpreted environment and in form of compiled code.


Structural application model

Starting the study of any system, first of all you need to define its structure boundaries. On the one hand it will help to avoid the consideration of undue generalized structures; on the other hand it helps not to get bogged down in details and low-level nuances, not significant at the current level. For each system there are three main elements. The first one is an environment in which the system operates, the second – is the system under consideration itself and the third is a set of nested structures responsible for the behavior of considered entity.
This is one of the equally possible partitions, but this presentation will be based exactly on this variant.

If we apply such partition to information systems, then the main connecting link of all the components will be information channels, which transmit signals/events/data in both directions. Operation system<->application<->user commands; browser<->HTML page<->JavaScript code: these are the examples of such organizational linkages.

The next step will be specifying the considered variety to the level of software systems subset. This variety is limited from below by the operation environment (operaton system), which interacts directly with the hardware. Upper variety is not limited, as the nesting of software systems into each other can be infinite (countable).
Completing the detailing stage we define the following scheme of a software system, see Figure 1.

On the one hand any system functions in some environment (Environment, Application), embeds in it (embedding). On the other hand there is a possibility to set up its behavior by means of external (Application, Configurations) mechanisms (extending).

At this level we do not make any assumptions about technologies of components creation and operation methods. These can be linkages: operation system<–>application<–>database, web server<–>php script<–>configuration file, application<–>dynamic module<–>registery parameters.


Applied application model

After specifying the structural parts, we can analyze the peculiarities appearing at the boundaries of its parts. It is logical that the operation environment must provide a broad, but a standardized interface, since it is designed for embedding of a large number of different applications. Consequently, each application created for the same environment must execute the standard contract of interaction with it. Using of rule leads to appearance of the so-called frameworks, increasing code reuse as well as development speed (here and after the framework, frame and engine are used as synonyms).

On the other hand the process of adjustment/setup/configuration can be done in many different ways: by command line parameters using configuration files, with help of interactive user input, plug-ins, scripts, etc. It is also necessary to execute an interaction contract, and in addition each of the extension mechanisms has different characteristics of generalization, versatility, extensibility etc. These are the criteria while choosing a particular tool.
Based on these conclusions we can single out the following application elements, see Figure 2.

Thus, this scheme also imposes no limitations on the implementation of each part. Framework can be written on Python, Application can be implemented as DLL on C++, and Configurations customize the system behavior using the xml file.


Scripting subsystem

As shown above, the specialization of application behavior can be carried out in many ways. Here we consider one of the options - the use of scripts as a mechanism of application functionality extension.
What is the main purpose of scripting subsystem? It is a provision of an orthogonal run-time interface to the functional system core. Interface, which deeper "penetrates" into the system compared to any operations, available through standard mechanisms of application behavior. Most packages of 2D/3D modeling, mathematical packages and financial tools contain similar functionality.
First of all we define the requirements for the subsystem to be developed
Creating of effective systems requires the opportunities for code reuse, which leads us to the following scheme, see Figure 3.

Thesis 1: Scripting code must be maximally adapted for reuse.

Different applied problems might require from us different forms of final assembly of the source codes into executable/library modules, see Figure 4.

Let us have a look at each of them separately.
First variant a) is typical for cases, when scripts run-time is critical and they are built in one module together with basic application. In such cases often the programming language of application is used for scripts too.
For variant b) the notion «application» and «framework» are not separated. These can be specialized developments for which generalization and reuse are sacrificed for the sake of speed and efficiency.
Variant c) – is an example of a monolithic system «all inclusive».
And variant d) – is a little «idealized» situation. Such structure is somewhat a standard, as it reflects a balanced view of a software product. And as usual – such result is very difficult to achieve.

Thesis 2: The implementation of scripting subsystem must enable to effectively implement four described integration schemes.

Not all scripting languages and systems can be available on all target platforms. Separate scripting language (subsystem) can be less effective compared to other language (subsystem) for specific tasks while all other conditions are equal. Figure 5 shows a schematic table of platforms covering. Using a similar table on the stage of operating environment analysis, we can select a scripting language with maximal covering and effective operation on the target subset of platforms and tasks.

Thesis 3: Scripting subsystem must provide the opportunity of parameterization by scripting language.

Now we can define the final requirement for the subsystem: developed design must be of high generality level, suitable for reuse, easily integrable, flexible, able to be parameterized by a scripting language.


C++ + CInt

This subsection is devoted to the description of really developed and used scripting subsystem, which is a part of the framework (engine) z3d.

The system is implemented in the following basis: framework – C++, application – C++, configurations – CInt.
Use of interpretation has many advantages: no need to change the binary code, broad algorithmic capabilities, usability, flexibility and unstrict typification, etc. But the speed of script execution is a disadvantage which limits the scope of its application.
Different systems "soften" this factor in different ways: the Python interpreter creates .pyc files,. NET platform performs the byte-code, Facebook «runs» php-scripts on C++.

A key property of developed system is availability of a general scheme, which enables both to use scripts interpretation and to connect them at compilation and compile them into binary module assembly.

Scripting language
CInt was selected as a scripting language. This is an interpreter for C/C++ To include CInt in project you can use compiled library from the official site. However, it is recommended to compile it from source codes using installed compiler with possibility to control the parameters of compilation and assembly.
The sequence of actions for library compilation under MS Windows with help of cygwin and msvc9 (msvc8, msvc7) is described here.

Subsystem architecture
Let us consider the design of the scripting subsystem and its component parts.
First of all, the entire scripting subsystem is implemented as a set of header files. Library user can enable them both in the executable module, and as a part of dynamic or static libraries.
Figure 6 reflects the diagram of key components.

Class z3d::script::cint::language encapsulates the presentation and behavior of a particular scripting language CInt (initialization, deinitialization, handling of context creation, etc.). Class z3d::script::cint::context - reflects a logically closed area "sandbox" within which the interpreter runs (function calls, expressions calculation). Template classes z3d::script::language and z3d::script::context realize common functionality to all scripting subsystems (context aggregation, lifetime management).
This organization allows us to meet Thesis 3 and parts b) and d) of Thesis 2.

Functionality realized in framework can be of two types:
  • interpreted code (prototypes and implementation of scripting functions of general purpose)
  • prototypes of gateway-functions for a call from scripting code. In other words, the entry points into compiled framework code from application scripts
Figure 7 shows the diagram of the source script code location.

Application: stdafx.h, stdafx.cpp
Application scripts: scr.h – a file containing general definitions for scripts and application; scr.inl – a file including all application scripts for scripts compiling; main.inl, config.inl – scripting functionality of application.
Framework scripts: engine.h – prototypes of gateway-functions for framework functionality access; common.h, vfs.h, pref.h, ui.h – frame scripting functionality; common.inl, ui.inl – implementation of framework scripting functionality.

This scheme enables to store and use scripts both on framework level and on application level and use them together in application context meeting Thesis 1.

And the last item in the list but the most important one is a preprocessor constant Z3D_SCRIPT_CINT_INTERPRETER.
If we define this constant in program code, then after module compilation all method calls of object z3d:: script:: cint:: context will be sent to CInt interpreter.
If this constant is not defined, the scripting code will be compiled and all scripting calls will be within the boundaries of compiled module. In this case the program code written on C++ and scripting code C/C++ will be compiled and assembled together.

This behavior is possible, firstly, due to certain mechanism of scripts inclusion in the project, see Fig.7, secondly, thanks to the capability of export section PE (Portable Executable).
Herein a small explanation is required. Script calls are implemented through string expressions of application time, which are delivered to the interpreter entry. In the case of "compiled" scripts the string semantics is lost after compilation. Here comes the opportunity to put the function in export section. Such mechanism works not only for the DLL, but for the EXE modules too. Thus, after module assembly you can easily recover the string semantics of a function using GetProcAddress

Here is a small illustration:
template< typename R, typename P1 >
R call( std::string const& func, P1 p1 )
{
#ifdef Z3D_SCRIPT_CINT_INTERPRETER
        std::stringstream ss;
        ss << func << '(' << z3d::type_name::param_( p1 ) << ");";
        return ( result< boost::is_float< R >::value >::convert< R >( G__calc( ss.str().c_str() ) ) );
#else
        return ( dict.get( func )( p1 ) );
#endif
}

where the function CInt – G_calc sends a string to the interpreter, and the method dict:: get returns a function pointer with name specified as func parameter, implementing the search of function by name in export section of a module.
This enables to cover parts a) and c) of the Thesis 2.

Results
This design enabled us to combine the flexibility of interpreted system and efficiency of compiled assembly. As before, the script interpreter can be included in the final program version, it can be used at the stage of design and prototyping as well as for test automation, you can consider the “scripts compiling” as their verifier, etc.
See a small example of a subsystem application below.
// create CInt language objects
z3d::script::cint::language::language_ptr_t l = z3d::script::cint::language::create( env.get_log() );

// create context (sandbox)
z3d::script::cint::context::context_ptr_t c = l->make_context();

// load script file into context
c->load( "main.inl" );

// call scripts
c->call< void >( "func" );
c->call< void >( "func1", const_cast("abcdef") );
c->call< void >( "func2", 1, 2.f );
c->call< void >( "func10", 0, 'A', 1.f, 1, 'B', 2.f, 3, 'C', 3.f, true );

Defining or removing the constant Z3D_SCRIPT_CINT_INTERPRETER you can get the script execution both using an interpreter and from a binary module.

It is also necessary to single out a number of compromises which were made while implementing the subsystem:
  • calculation of expressions will work in the interpreted assembly, but it will be unavailable in the compiled basis (calculation of expression type "2+2*2")
  • to implement the scripting function call a system was developed which compares the function call signatures and a corresponding string representation, increasing the compiling time using metaprogramming methods. f (1, 1.f) -> "f ((int) 1, (float) 1.f); "(for implementation details see here)
Among other plans of a scripting subsystem development we can single out the integration as scripting languages Ch, Python, Lua. The last two languages will not allow compiling scripts in code, but not all tasks require similar behavior. Another problem is the implementation of a “transparent” transmission of objects created within scripting code on the application level and vice versa.

Mentioned herein source codes can be obtained by downloading z3d sdk.
After installation you can find a project that demonstrates the application of scripting subsystem (msvc9) in the folder %z3d-root%/sam/. To work with the source code the libraries Boost, Loki are required.