Kent
Qt
QSA
Posted by Kent
 in Qt, QSA
 on Monday, August 25, 2008 @ 14:45

At Akademy (which was a great event by the way!) I had an interesting talk with Pino (the man with Okular vision 8-) ) about an almost equally interesting feature of the Adobe Acrobat JavaScript API. Quoting the reference:

Many of the JavaScript methods provided by Acrobat accept either a list of arguments, as is customary in JavaScript, or a single object argument with properties that contain the arguments. For example, these two calls are equivalent:

app.alert("Acrobat Multimedia", 3);

app.alert( { cMsg: "Acrobat Multimedia", nIcon: 3 } );

The second form uses JavaScript’s object literal syntax to provide arguments in a declarative style, which can make for more readable code, and relieves the script author of having to worry about the order of arguments. However, it does present a new challenge to the implementer of the API: Does he have to implement every function twice now, once per argument-passing style?

Short answer: No (and that’s the honest truth). Long answer: Read on.

What we’ll do is wrap a proxy function around the function that implements the API; the job of the proxy is to detect which particular argument-passing style is used in an invocation, and call the real function using a single argument-passing style regardless (i.e. convert the object literal to a list or vice versa). Essentially we are building our own little type system on top of barebones JS. Sure, this means that some additional glue code has to be written for initializing the JS bindings, and there will be a slight overhead involved with calling a public API function, but I believe in most cases that’s a small price to pay, considering the alternative. The solution presented works on a per-function basis anyway, so you don’t have to use it for everything.

I’ve written a small JavaScript function, called argumentative(), that does the work; you can download it here. To use it, you first prepare your own “private” implementations of the API in whatever call-style you wish, for example list-style:

function __alert(msg, icon, type, title, doc, checkbox)
{
    // For now we just dump the arguments, a real implementation
    // would display a message box.
    print("msg:", msg, "icon:", icon, "type:", type);
    print("title:", title, "doc:", doc, "checkbox:", checkbox);
}

Then you call argumentative(), passing it your function, and an array of argument descriptors. argumentative() will return a proxy function, which is the function you actually want to expose to script authors (i.e. your public API).

alert = argumentative(__alert,
    [ { name: 'cMsg', type: String },
      { name: 'nIcon', type: Number, defaultValue: 0 },
      { name: 'nType', type: Number, defaultValue: 0 },
      { name: 'cTitle', type: String, defaultValue: 'Adobe Acrobat' },
      { name: 'oDoc', type: Object, optional: true },
      { name: 'oCheckbox', type: Object, optional: true,
        properties: [ { name: 'cMsg', type: String, defaultValue: 'Do not show this message again' },
                          { name: 'bInitialValue', type: Boolean, defaultValue: false } ]
      }
    ]);

The purpose of the name property of an argument descriptor should be obvious. The type property is optional; if it is specified, the proxy function will check that the actual argument is of the specified type before invoking your function; this means you can potentially get rid of a lot of type checks in your own code. (The type-checking approach is inspired by John Resig’s strict() function, found in his book “Pro JavaScript Techniques”.) The defaultValue property is optional; if it is specified, its value will be used if the argument is missing in a call. Descriptors for object-based types can additionally specify a properties property, which is just another array of argument descriptors; the proxy function will recursively validate the properties of the object, and substitute in default values if appropriate, before calling your function.

Anyway, now the script author can either do:

alert("my message", 1, 2, "my title", null, { cMsg: "check", bInitialValue: false } );

or

alert( { cMsg: "my message", nIcon: 1, nType: 2, cTitle: "my title",
          oDoc: null, oCheckbox: { cMsg: "check", bInitialValue: false } } );

To your implementation, the two calls will appear identical. (By the way, the argumentative() function lets you control which style you want to receive the arguments in; if you pass 1 as the third argument, your function will receive arguments single-argument-object style, instead of as a list.) If the script author does this:

alert();
alert( { } );

in both cases he will get an error saying that the cMsg argument is missing, as expected. Similarly, if he does this:

alert( { cMsg: "my message", oCheckbox: { cMsg: "check", bInitialValue: "ciao" } } );

he will get an error saying that the argument oCheckbox.bInitialValue has the wrong type.

In the case of the Adobe JS bindings, the argumentative() function could also easily be augmented to support the special acrohelp argument (in which case the function should return a list of its own arguments, rather than call the real function); the function proxy already has all the information it needs.

OK, now for the Qt Script-related part (I almost forgot this is a Qt blog). Most, if not all, JS API functions like those for Acrobat (including alert()) have to be implemented as native functions. So how can you take advantage of the argumentative() functionality in this case? It’s actually pretty simple, as demonstrated by the following C++ snippet:

QScriptEngine engine;
/* evaluate argumentative.js
 ... ... */

QScriptValue descriptors = engine.evaluate(/* the same array of descriptors defined in an earlier snippet */);
QScriptValue fun = engine.newFunction(alert); // alert is a function pointer
QScriptValue proxy = eng.evaluate("argumentative")
                    .call(QScriptValue(), QScriptValueList() << fun << descriptors);
engine.globalObject().setProperty("alert", proxy); // install the public API function

The full example can be downloaded here; it’s a partial implemention of the alert() function using a proper Qt message box, and shows that the native function works the same regardless of which argument-passing style the script uses.

4 Responses to “Unifying JavaScript Argument-passing Styles”

» Posted by Ian Monroe
 on Monday, August 25, 2008 @ 16:15

Named arguments are a good idea. There’s a place in the Amarok QtScript API where using it would be a really good idea…

But what about unifying setters and getters and properties in the QtScript binding API? :P I guess ideally all the Qt’s object “properities” (in the OOP academic sense) could be modified via “properities” (in the JavaScript language sense).

» Posted by Norman Rasmussen
 on Monday, August 25, 2008 @ 17:42

If you swapped the order of parameters to argumentative, then you could use a closure and have the arguments defined before the function:

alert = argumentative(
[ { name: ‘cMsg’, type: String },
{ name: ‘nIcon’, type: Number, defaultValue: 0 },
{ name: ‘nType’, type: Number, defaultValue: 0 },
{ name: ‘cTitle’, type: String, defaultValue: ‘Adobe Acrobat’ },
{ name: ‘oDoc’, type: Object, optional: true },
{ name: ‘oCheckbox’, type: Object, optional: true,
properties: [ { name: ‘cMsg’, type: String, defaultValue: ‘Do not show this message again’ },
{ name: ‘bInitialValue’, type: Boolean, defaultValue: false } ]
}
], function (msg, icon, type, title, doc, checkbox)
{
// For now we just dump the arguments, a real implementation
// would display a message box.
print(”msg:”, msg, “icon:”, icon, “type:”, type);
print(”title:”, title, “doc:”, doc, “checkbox:”, checkbox);
});

The only problem with this is that the callStyle parameter either gets lots at the end, or isn’t optional.

It’s a pity that you can’t decorate JavaScript functions (with syntax) like you can in Java and python.

» Reply from Kent
 on Tuesday, August 26, 2008 @ 12:33
Kent

Ian: You mean, so you could do things like “p1 = new QPoint(); p1.x = 123;” and it would actually do what you expect? Yes, that’d be nice. We just need to annotate Qt’s classes and create a moc-like tool that extracts the information and generates something that compiles into something that can be used at runtime to query and access the properties.

» Posted by Francois Savard
 on Saturday, August 30, 2008 @ 17:34

Nice idea. Keyword arguments are also found in Python, and I’ve been using and abusing them ever since I discovered that.

@Norman Rasmussen
Kind of a shameless plug here, but it so happens (ah, coincidences!) that in the last few days I have developped a little JS lib that checks types for parameters/return values by decorating the function (it also brings some sort of interface concept, btw):

http://www.fsavard.com/code/fsignatures/

It’s alpha, but I hope you find it interesting. Comments very welcome :P



© 2008 Nokia Corporation and/or its subsidiaries. Nokia, Qt and their respective logos are trademarks of Nokia Corporation in Finland and/or other countries worldwide.
All other trademarks are property of their respective owners.