6.0.0-rc4Source

Using Nt Qml

How to run JavaScript in an application, and let scripts use its QObjects.

Nt Qml runs JavaScript. QJSEngine evaluates programs, QJSValue holds what they make, and QObjects take part in scripts with their properties, methods and signals. It is the base of QML, which follows.

find_package(Nt6 REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE Nt::Qml)

Running Code

evaluate() runs a program and returns the value of its last statement. Functions come back as values that C++ can call:

QJSEngine engine;
engine.installExtensions(QJSEngine::ConsoleExtension);

QJSValue result = engine.evaluate("[1, 2, 3].map(x => x * x).join(' + ')");
qDebug() << result.toString(); // "1 + 4 + 9"

QJSValue square = engine.evaluate("(function(x) { return x * x; })");
qDebug() << square.call({12}).toInt(); // 144

QJSValue error = engine.evaluate("undefinedFunction()", "inline.js");
if (error.isError())
    qDebug() << "line" << error.property("lineNumber").toInt() << error.toString();

Errors do not escape as C++ exceptions: evaluate() and QJSValue::call() return the error, an object with name, message, fileName, lineNumber and stack.

QObjects in Scripts

newQObject() gives scripts a QObject. They read and write its Q_PROPERTYs, call its slots and Q_INVOKABLE methods, emit its signals by calling them, and connect them to functions with signal.connect(f) or signal.connect(thisObject, f):

Counter counter;
engine.globalObject().setProperty("counter", engine.newQObject(&counter));
engine.evaluate(R"(
    counter.valueChanged.connect(v => console.log("counter is now", v));
    counter.value = 10;
    counter.add(5);
)");
qDebug() << counter.value(); // 15

Enumeration properties take their keys as strings, "Heating", or their values. Overloaded methods are chosen by the number and types of the arguments. Properties that scripts add to a QObject are kept on its script value. A script calling a method of a deleted object gets a TypeError.

The Language

Nt implements a large part of ECMAScript 2017, interpreted:

Not supported: classes, generators, async functions and promises, destructuring, symbols and iterators (for-of works on arrays, strings and array-likes), Map and Set, Proxy and Reflect, typed arrays, getters on arrays, tagged templates, with, and making functions from strings. Arrays have no holes: writing past the end fills the gap with undefined. Regular expressions run on QRegularExpression, whose syntax is that of JavaScript for the common cases.

Untrusted Scripts

Source code is treated as untrusted input. Nesting in the source, recursion at run time and the size of arrays and strings are limited, so a hostile script fails with an error instead of exhausting the stack or memory. QJSEngine::setInterrupted() stops a script that runs too long; call it from a timer or another thread. A script can do only what the objects given to it allow: there is no file or network access of its own.

The js_scripting example runs the snippets above, or a file given on its command line.