The default print()
function of QScriptEngine
prints the result to the terminal of Qt Creator IDE for debugging purpose. As a result, the output must be redirected to our texteditor if we are going to make a ECMA script interpreter ourselves.
This part of the document "Making Applications Scriptable" remains untouched since Qt 4.3.
Section "Redefining print()":
Qt Script provides a built-in print() function that can be useful for simple debugging purposes. The built-in print() function writes to standard output. You can redefine the print() function (or add your own function, e.g. debug() or log()) that redirects the text to somewhere else. The following code shows a custom print() that adds text to a QPlainTextEdit.
So here is the suggested re-definition of print()
:
QScriptValue QtPrintFunction(QScriptContext *context, QScriptEngine *engine)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0)
result.append(" ");
result.append(context->argument(i).toString());
}
QScriptValue calleeData = context->callee().data();
QPlainTextEdit *edit = qobject_cast<QPlainTextEdit*>(calleeData.toQObject());
edit->appendPlainText(result);
return engine->undefinedValue();
}
At first, I doubted the need of returning an "Undefined Value" by return engine->undefinedValue();
, and it looks like the role of the argument *engine
is just to return this void value.
So here is what I've done to change the function:
QScriptValue myPrintFunction(QScriptContext *context, QScriptEngine *engine)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0)
result.append(" ");
result.append(context->argument(i).toString());
}
/*
QScriptValue calleeData = context->callee().data();
QPlainTextEdit *edit = qobject_cast<QPlainTextEdit*>(calleeData.toQObject());
edit->appendPlainText(result);
return engine->undefinedValue();
*/
return engine->toScriptValue(result); // ---> return the result directly
}
which I think is more reasonable to me: returning an evaluated QScriptValue
from script engine, and the value can later be translated to QString
for output. This bypass the need of dynamic type cast, which could become messy especially for customized QObjects.
For both kinds of print function, here is the exposition to the script engine:
QScriptEngine *engine = new QScriptEngine(this);
QTextEdit *input = new QTextEdit(this);
QTextEdit *output = new QTextEdit(this);
// Use documented print function :
QScriptValue fun = engine->newFunction(QtPrintFunction);
// Use my revised print function :
// QScriptValue fun = engine->newFunction(myPrintFunction);
fun.setData(engine->newQObject(output));
engine->globalObject().setProperty("print", fun);
Evaluation and output:
QString command = input->toPlainText();
QScriptValue result = engine->evaluate(command);
output->append(result.toString());
(Qt version > 4 is needed)
test.pro
QT += core gui widgets script
TARGET = Test
TEMPLATE = app
SOURCES += main.cpp\
console.cpp
HEADERS += console.h
main.cpp
#include <QApplication>
#include "console.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Console w;
w.show();
return app.exec();
}
console.h
#ifndef CONSOLE_H
#define CONSOLE_H
#include <QWidget>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QScriptEngine>
class Console : public QWidget
{
Q_OBJECT
public:
Console();
~Console();
public slots:
void runScript();
private:
QScriptEngine *engine;
QVBoxLayout *layout;
QPushButton *run;
QTextEdit *input, *output;
};
QScriptValue QtPrintFunction(QScriptContext *context, QScriptEngine *engine);
QScriptValue myPrintFunction(QScriptContext *context, QScriptEngine *engine);
#endif // CONSOLE_H
console.cpp
#include "console.h"
Console::Console()
{
engine = new QScriptEngine(this);
layout = new QVBoxLayout(this);
run = new QPushButton("Run",this);
input = new QTextEdit(this);
output = new QTextEdit(this);
layout->addWidget(input);
layout->addWidget(run);
layout->addWidget(output);
//QScriptValue fun = engine->newFunction(QtPrintFunction);
QScriptValue fun = engine->newFunction(myPrintFunction);
fun.setData(engine->newQObject(output));
engine->globalObject().setProperty("print", fun);
connect(run, SIGNAL(clicked()), this, SLOT(runScript()));
}
void Console::runScript()
{
QString command = input->toPlainText();
QScriptValue result = engine->evaluate(command);
output->append(result.toString());
}
QScriptValue QtPrintFunction(QScriptContext *context, QScriptEngine *engine)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0)
result.append(" ");
result.append(context->argument(i).toString());
}
QScriptValue calleeData = context->callee().data();
QTextEdit *edit = qobject_cast<QTextEdit*>(calleeData.toQObject());
edit->append(result);
return engine->undefinedValue();
}
QScriptValue myPrintFunction(QScriptContext *context, QScriptEngine *engine)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0)
result.append(" ");
result.append(context->argument(i).toString());
}
return engine->toScriptValue(result);
}
Console::~Console()
{
}
Input 1:
print(123);
Output (Qt Document QtPrintFunction()
):
123
undefined
Output (My version myPrintFunction()
):
123
Input 2:
for (i = 0; i < 3; i++)
print(i);
Output (Qt Document QtPrintFunction()
):
0
1
2
undefined
Output (myPrintFunction()
):
2
Input 3:
print("Stack");
print("Overflow");
Output (Qt Document QtPrintFunction()
):
Stack
Overflow
undefined
Output (My version myPrintFunction()
):
Overflow
Although myPrintFunction
seems to work fine at first, it didn't work when there are more than two print
called in a script, where only the last print
will be executed.
It's not that it is NECESSARY to return undefinedValue()
, but when you do, it's the same as not returning anything. Or essentially, as if you declared the function as void print(...)
, so to speak.
That's what the QtPrintFunction
does -- it returns "nothing". But it does have a side effect of appending its argument to the internal data object, whenever you call it. That's why you get all of the values passed to print
in the output
object.
Now, when you call engine->evaluate()
it returns the value of the last evaluated expression. So, with myPrintFunction
you get the last value only.
So, if you were to enter the following:
print("Stack");
print("Overflow");
"garbage";
you will only get garbage
back (pun intended), as this was the last evaluated expression.
But, if you were to enter this:
print("Stack") + '\n' +
print("Overflow");
you will get both values, as you expected.
Additionally, if you enter:
result = "";
for (i = 0; i < 3; i++)
result += print(i) + '\n';
you will also get what you expected.
Hopefully this explains why you functions behave the way they are.
However, I don't think this is what you are trying to achieve. So... moving right along.
One thing you can do is to define the myPrintFunction
as follows:
QScriptValue myPrintFunction(QScriptContext *context, QScriptEngine *engine)
{
static QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0)
result.append(" ");
result.append(context->argument(i).toString());
}
result.append('\n');
return engine->toScriptValue(result);
}
This will "work" the way you expected it to work. The only problem is that you can't clear the value of result
. If that works for you, then that will be that.
There is a more betterer way to do this, which is probably to define a class, eg:
class QTrace: public QObject
{
...
void clear();
void append(const QString& value);
const QString& get();
}
and pass an object of that class to fun.setData(engine->newQObject(trace))
and define your function as:
QScriptValue myPrintFunction(QScriptContext *context, QScriptEngine *engine)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0)
result.append(" ");
result.append(context->argument(i).toString());
}
result.append('\n');
QScriptValue calleeData = context->callee().data();
QTrace *trace = qobject_cast<QTrace*>(calleeData.toQObject());
trace->append(result);
return engine->undefinedValue();
}
Lastly, you would change your runScript
function to something like:
trace->clear();
QScriptValue result = engine->evaluate(command);
if(result.isError())
output->append(result.toString());
else
output->append(trace->get());
Or there are probably other ways, but hopefully will help you get the ball rolling in the right direction.
The quick answer: you don't need to return undefinedValue. You can return anything you want. However, engine->evaluate()
can only return a single value, which I think is the source of the confusion.
Take a look at the evaluation code:
QString command = input->toPlainText();
QScriptValue result = engine->evaluate(command);
output->append(result.toString());
This takes a script string and evaluates it, assigning the resulting value into result
, where it is then appended to the QTextEdit
control. The return value of evaluate()
is going to be the last value of the script. For example:
QString command = "var a=1, b=2; a; b;";
QScriptValue result = engine->evaluate(command);
output->append(result.toString());
result
would contain 2, which would then get logged to the QTextEdit
control.
So, what's happening? Take this input for example:
print("Stack");
print("Overflow");
When using the QtPrintFunction
, "Stack" and "Overflow" are added to the output
control as part of the QtPrintFunction
implementation. When the evaluate()
finishes, the last statement was print("Overflow")
which returns undefined. The evaluation code then takes that return value and adds it to the output
, resulting in:
Stack
Overflow
undefined
When using the myPrintFunction
, that implementation doesn't log anything to the output
, but returns the value. The result is that the evaluate()
function only returns the last value ("Overflow"), resulting in this output:
Overflow
Which is what you are seeing.
Since you want to redirect output to your own custom text editor, you need to change this block of code:
QScriptEngine *engine = new QScriptEngine(this);
QTextEdit *input = new QTextEdit(this);
//QTextEdit *output = new QTextEdit(this);
YourCustomEditor *output = getYourCustomEditor();
// Use my revised print function :
QScriptValue fun = engine->newFunction(myPrintFunction);
fun.setData(engine->newQObject(output)); // pass your editor in
engine->globalObject().setProperty("print", fun);
Then in myPrintFunction
you need to send the output to YourCustomEditor
in a way that's similar to the QtPrintFunction
. Then you no longer need to output the result from evaluate()
:
QString command = input->toPlainText();
QScriptValue result = engine->evaluate(command);
// output->append(result.toString()); <- not needed anymore
I've worked with embedded interpreters many times before and things can get confusing quickly. Hopefully this is clear enough to help.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With