Qt 4.8: Moving from QSA to Qt Script

Qt Reference Documentation

# Moving from QSA to Qt Script

The purpose of this document is to map the differences between Qt Script for Applications (QSA) and Qt Script, the ECMAScript compatible engine supplied with Qt 4.3. This document is not supposed to be a complete function by function porting guide, but will cover the most obvious aspects.

First of all it is important to realize that Qt Script is only an interpreter, it does not provide an editor, completion or script project management, like QSA does. Qt Script however does provides almost full compliance with the ECMAScript standard and performs significantly better than the script engine provided by QSA.

## The Scripting Language

The scripting language used in QSA, from here on referred to as QSA, was derived from ECMAScript 3.0 and 4.0 and is a hybrid of these standards. Most of the run-time logic, such as classes and scoping rules, is based on the ECMAScript 4.0 proposal, while the library implementation is based on the ECMAScript 3.0 standard. Qt Script on the other hand is solely based on the ECMAScript 3.0 standard. Though the languages look identical at first glance, there are a few differences that we'll cover in the sections below.

### Classes vs. Objects and Properties

QSA implements classes and inheritance much in a familiar way to users of other object oriented languages, like C++ and Java. However, the ECMAScript 3.0 standard defines that everything is an object, and objects can have named properties. For instance to create an point object with the properties x and y one would write the following Qt Script code:

     point = new Object();
     point.x = 12;
     point.y = 35;

The object `point` in this case is constructed as a plain object and we assign two properties, `x` and `y`, to it with the values 12 and 35. The `point` object is assigned to the "Global Object" as the named property `point`. The global object can be considered the global namespace of the script engine. Similarly, global functions are named properties of the global object; for example:

     function manhattanLength(point) {
         return point.x + point.y;
     }

An equivalent construction that illustrates that the function is a property of the global object is the following assignment:

     manhattanLength = function(point) {
        return point.x + point.y;
     }

Since functions are objects, they can be assigned to objects as properties, becoming member functions:

     point.manhattanLength = function() {
         return this.x + this.y;
     }
     print(point.manhattanLength()); // prints 47

In the code above, we see the first subtle difference between QSA and Qt Script. In QSA one would write the point class like this:

     class Point() {
         var x;
         var y;
         function manhattanLength() { return x + y; }
     }

where in the `manhattanLength()` function we access `x` and `y` directly because, when the function is called, the `this` object is implicitly part of the current scope, as in C++. In Qt Script, however, this is not the case, and we need to explicitly access the `x` and `y` values via `this`.

All the code above runs with QSA except the assignment of a function to `point.manhattanLength`, which we repeat here for clarity:

     point.manhattanLength = function() {
         return this.x + this.y;
     }
     print(point.manhattanLength()); // prints 47

This is because, in QSA, the value of `this` is decided based on the location of the declaration of the function it is used in. In the code above, the function is assigned to an object, but it is declared in the global scope, hence there will be no valid `this` value. In Qt Script, the value of `this` is decided at run-time, hence you could have assigned the `manhattanLength()` function to any object that had `x` and `y` values.

### Constructors

In the code above, we use a rather awkward method for constructing the objects, by first instantiating them, then manually assigning properties to them. In QSA, the proper way to solve this is to implement a constructor in the class:

     class Car {
         var regNumber;
         function Car(regnr) {
             regNumber = regnr;
         }
     }
     var car = new Car("ABC 123");

The equivalent in Qt Script is to create a constructor function:

     function Car(regnr) {
         this.regNumber = regnr;
     }
     var car = new Car("ABC 123");

As we can see, the constructor is just a normal function. What is special with is how we call it, namely prefixed with the `new` keyword. This will create a new object and call the `Car()` function with the newly created object as the `this` pointer. So, in a sense, it is equivalent to:

     var car = new Object();
     car.constructor = function(regnr) {
         // ...
     }
     car.constructor();

This is similar to the manhattenLength() example above. Again, the main difference between QSA and Qt Script is that one has to explicitly use the keyword `this` to access the members and that instead of declaring the variable, `regNumber`, we just extend the `this` object with the property.

### Member Functions and Prototypes

As we saw above, one way of creating member functions of a Qt Script object is to assign the member function to the object as a property and use the `this` object inside the functions. So, if we add a `toString` function to the `Car` class

     class Car {
         var regNumber;
         function Car(regnr) {
             regNumber = regnr;
         }
         function toString() {
             return regNumber;
         }
     }

one could write this in Qt Script as:

     function Car(regnr) {
         this.regNumber = regnr;
         this.toString = function() { return this.regNumber; }
     }

In QSA, the member functions were part of the class declaration, and were therefore shared between all instances of a given class. In Qt Script, each instance has a instance member for each function. This means that more memory is used when multiple instances are used. Qt Script uses prototypes to remedy this.

The basic prototype-based inheritance mechanism works as follows. Each Qt Script object has an internal link to another object, its prototype. When a property is looked up in an object, and the object itself does not have the property, the interpreter searches for the property in the prototype object instead; if the prototype has the property then that property is returned. If the prototype object does not have the property, the interpreter searches for the property in the prototype of the prototype object, and so on.

This chain of objects constitutes a prototype chain. The chain of prototype objects is followed until the property is found or the end of the chain is reached.

To make the `toString()` function part of the prototype, we write code like this:

     function Car(regnr) {
         this.regNumber = regnr;
     }
     Car.prototype.toString = function() { return this.regNumber; }

Here, we made the `toString()` function part of the prototype so that, when we call `car.toString()` it will be resolved via the internal prototype object of the car object. Note, however, that the `this` object is still the original object that the function was called on, namely `car`.

### Inheritance

Now that we've seen how to use prototypes to create a "class" members in Qt Script, let's see how we can use prototypes to create polymorphism. In QSA you would write

     class GasolineCar extends Car {
         function GasolineCar(regnr) {
             Car(regnr);
         }
         function toString() {
             return "GasolineCar(" + regNumber + ")";
         }
     }

With Qt Script, we acheive the same effect by creating a prototype chain. The default prototype of an object is a plain `Object` without any special members, but it is possible to replace this object with another prototype object.

     function GasolineCar(regnr) {
         Car(regnr);
     }
     GasolineCar.prototype = new Car();
     GasolineCar.prototype.toString = function() {
         return "GasolineCar(" + this.regNumber + ")";
     }

In the code above, we have a constructor, `GasolineCar`, which calls the "base class" implementation of the constructor to initialize the `this` object with the property `regNumber`, based on the values passed in the constructor. The interesting line in this case is the line after the constructor where we change the default prototype for `GasolineCar` to be an instance of type `Car`. This means that all members available in a `Car` object are now available in all `GasolineCar` objects. In the last line, we replace the `toString()` function in the prototype with our own, thus overriding the `toString()` for all instances of `GasolineCar`.

### Static Members

QSA allowed users to declare static members in classes, and these could be accessed both through instances of the class and through the class itself. For example, the following variable is accessed through the `Car` class:

     class Car {
         static var globalCount = 0;
     }
     print(Car.globalCount);

The equivalent in Qt Script is to assign variables that should appear as static members as properties of the constructor function. For example:

     Car.globalCount = 0;
     print(Car.globalCount);

Note that in QSA, static member variables were also accessible in instances of the given class. In Qt Script, with the approach illustrated above, the variable is a member of the constructor object only, and thus only accessible through `Car.globalCount`.

## The Built-in Functions and Library

The built-in functions in QSA are based on those defined in the ECMAScript 3.0 standard, the same standard used for Qt Script, but QSA adds some extensions to this, specifically for the `String` and `RegExp` types. QSA also lacked some functions from the standard, most notably the `Date` type. Below we list all the differences. All changes made to Qt Script are to increase compliance with ECMAScript 3.0.

QSA Function | Notes about Equivalent Qt Script Functions
--- | ---
eval() | The eval function in QSA opened a new scope for code being executed in the eval function, so locally declared variables were not accessible outside. In Qt Script, the eval() function shares the current scope, making locally declared variables accessible outside the eval() call.
debug() | This function is not available in Qt Script. Use print() instead.
connect() | QSA had closures, meaning that a member function reference implicitly contained its `this` object. Qt Script does not support this. See the Qt Script documentation for details on using the connect function.
String.arg() | This function is not available in Qt Script. Use replace() or concat() instead.
String.argDec() | This function is not available in Qt Script. Use replace() or concat() instead.
String.argInt() | This function is not available in Qt Script. Use replace() or concat() instead.
String.argStr() | This function is not available in Qt Script. Use replace() or concat() instead.
String.endsWith() | This function is not available in Qt Script. Use lastIndexOf() instead.
String.find() | This function is not available in Qt Script. Use indexOf() instead.
String.findRev() | This function is not available in Qt Script. Use lastIndexOf() and length instead.
String.isEmpty() | This function is not available in Qt Script. Use length == 0 instead.
String.left() | This function is not available in Qt Script. Use substring() instead.
String.lower() | This function is not available in Qt Script. Use toLowerCase() instead.
String.mid() | This function is not available in Qt Script. Use substring() instead.
String.right() | This function is not available in Qt Script. Use substring() instead.
String.searchRev() | This function is not available in Qt Script. Use search() / match() instead.
String.startsWith() | This function is not available in Qt Script. Use indexOf() == 0 instead.
String.upper() | This function is not available in Qt Script. Use toUpperCase() instead.
RegExp.valid | This property is not available in Qt Script because it is not required; a `SyntaxError` exception is thrown for bad `RegExp` objects.
RegExp.empty | This property is not available in Qt Script. Use `toString().length == 0` instead.
RegExp.matchedLength | This property is not available in Qt Script. RegExp.exec() returns an array whose size is the matched length.
RegExp.capturedTexts | This property is not available in Qt Script. RegExp.exec() returns an array of captured texts.
RegExp.search() | This function is not available in Qt Script. Use RegExp.exec() instead.
RegExp.searchRev() | This function is not available in Qt Script. Use RegExp.exec() or String.search()/match() instead.
RegExp.exactMatch() | This function is not available in Qt Script. Use RegExp.exec() instead.
RegExp.pos() | This function is not available in Qt Script. Use String.match() instead.
RegExp.cap() | This function is not available in Qt Script. RegExp.exec() returns an array of captured texts.

QSA also defined some internal Qt API which is not present in Qt Script. The types provided by QSA which are not provided by Qt Script are:

- Rect
- Point
- Size
- Color
- Palette
- ColorGroup
- Font
- Pixmap
- ByteArray

## The C++ API of QSA vs Qt Script

QSA is more than just a scripting engine. It provides project management, an editor with completion and a minimalistic IDE to edit scriptable projects. Qt Script on the other hand is just a scripting engine. This means that equivalents to the classes `QSEditor`, `QSScript`, `QSProject` and `QSWorkbench` do not exist in Qt Script. QSA also provides some extension APIs through the `QSUtilFactory` and `QSInputDialogFactory`. There is also no equivalent to these classes in the Qt Script API.

### Making QObjects Accessible from Scripts

There are two different ways of making QObjects accessible from scripts in QSA. The first method is via the `QSInterpreter::addTransientObject()` and `QSProject::addObject()` functions. In this case objects are added to the global namespace of the interpreter using their object names as the names of the variables.

     QPushButton *button = new QPushButton();
     button->setObjectName("button");
     interpreter->addTransientObject(button);

The code above adds the button to the global namespace under the name "button". One obvious limitation here is that there is potential for either unnamed QObjects or objects whose names conflict. Qt Script provides a more flexible way of adding QObjects to the scripting environment.

     QPushButton *button = new QPushButton();
     QScriptValue scriptButton = engine.newQObject(button);
     engine.globalObject().setProperty("button", scriptButton);

In the code above we create a QPushButton and wrap it in a script value using the function, QScriptEngine::newQObject(). This gives us a script value that we put into the global object using the name "button". The concept of objects and properties discussed above is quite visible here in the public C++ API as well. We have no dependency on the object's name and we can also resolve name conflicts more gracefully. Here, we operate directly on QScriptValue objects. This is the actual object that is being passed around inside the script engine, so we actually have low-level access to the internal script data structures, far beyond that which is possible in QSA. Properties, signals and slots of the QObject are accessible to the scripter in Qt Script, just like in QSA.

The other way to expose QObjects in QSA was to create a `QSObjectFactory` that made it possible to instantiate QObjects from scripts.

Below is listed some code from the filter example in the QSA package.

     ModuleFactory::ModuleFactory()
     {
         registerClass( "ImageSource", &ImgSource::staticMetaObject);
         ...
     }

     QObject *ModuleFactory::create( const QString &type,
                                     const QVariantList &,
                                     QObject * )
     {
         if ( type == "ImageSource" )
             return new ImgSource();
         ...
     }

     ...

     interpreter.addObjectFactory(new ModuleFactory());

The equivalent in Qt Script is written in much the same way as constructors are written in scripts. We register a callback C++ function under the name "ImageSource" in the global namespace and return the QObject from this function:

     QScriptValue construct_QPushButton(QScriptContext *, QScriptEngine *engine) {
         return engine->newQObject(new QPushButton());
     }

     ...

     QScriptValue constructor = engine.newFunction(construct_QPushButton);
     QScriptValue value =
         engine.newQMetaObject(&QPushButton::staticMetaObject,
                               constructor);
     engine.globalObject().setProperty("QPushButton", value);

In the Qt Script case we use the same approach that we use to expose a QObject, namely via QScriptEngine::newQObject(). This function also has the benefit that it is possible to specify if the QObject should expose properties and slots of its base class. It is also possible to specify custom ownership rules.

The reader might question why we don't add the constructor function directly into the namespace, but create a meta-object script value for it in addition. The plain function would certainly be good enough, but by creating a QMetaObject based constructor we get the enums on QPushButton for free in the QPushButton function object. Exposing enums in QSA is rather painful in comparison.

If we want to add more "static" data to the QPushButton type in Qt Script, we're free to add properties, similar to how we did for the script. It is also possible to add custom functions to a Qt Script QPushButton instance by setting more properties on it, such as making the setText() C++ function available. It is also possible to acheive this by installing a custom prototype, and be memory efficient, as discussed in the script example above.

### Accessing Non-QObjects

In QSA, it was possible to expose non-QObjects to QSA by wrapping them in a QObject and using either `QSWrapperFactory` or `QSObjectFactory` to expose them. Deciding when to use each of these classes could be confusing, as one was used for script based construction and the other for wrapping function parameters and return values, but in essence they did exactly the same thing.

In Qt Script, providing access to QObjects and non-QObjects is done in the same way as shown above, by creating a constructor function, and by adding properties or a custom prototype to the constructed object.

### Data Mapping

QSA supported a hardcoded set of type mappings which covered most of the QVariant types, QObjects and primitives. For more complex type signatures, such as the template-based tool classes, it had rather limited support. Qt Script is significantly better at type mapping and will convert lists of template types into arrays of the appropriate types, given that all the types are declared to the meta-type system.