简体   繁体   中英

What's the point of returning an “Undefined Value” when re-defining “print()” function for QScriptEngine?

[Background]

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, eg 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());

[Compilable Code]

(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() { } 


[Example]

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


[Question]

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 seems the returning of an "Undefined Value" is NECESSARY for the print function. But why???

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM