/*
Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef qscriptvalue_p_h
#define qscriptvalue_p_h
#include "qscriptconverter_p.h"
#include "qscriptengine_p.h"
#include "qscriptvalue.h"
#include <JavaScriptCore/JavaScript.h>
#include <QtCore/qmath.h>
#include <QtCore/qnumeric.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qvarlengtharray.h>
class QScriptEngine;
class QScriptValue;
/*
\internal
\class QScriptValuePrivate
Implementation of QScriptValue.
The implementation is based on a state machine. The states names are included in
QScriptValuePrivate::States. Each method should check for the current state and then perform a
correct action.
States:
Invalid -> QSVP is invalid, no assumptions should be made about class members (apart from m_value).
CString -> QSVP is created from QString or const char* and no JSC engine has been associated yet.
Current value is kept in m_string,
CNumber -> QSVP is created from int, uint, double... and no JSC engine has been bind yet. Current
value is kept in m_number
CBool -> QSVP is created from bool and no JSC engine has been associated yet. Current value is kept
in m_number
CSpecial -> QSVP is Undefined or Null, but a JSC engine hasn't been associated yet, current value
is kept in m_number (cast of QScriptValue::SpecialValue)
JSValue -> QSVP is associated with engine, but there is no information about real type, the state
have really short live cycle. Normally it is created as a function call result.
JSNative -> QSVP is associated with engine, and it is sure that it isn't a JavaScript object.
JSObject -> QSVP is associated with engine, and it is sure that it is a JavaScript object.
Each state keep all necessary information to invoke all methods, if not it should be changed to
a proper state. Changed state shouldn't be reverted.
*/
class QScriptValuePrivate : public QSharedData {
public:
inline static QScriptValuePrivate* get(const QScriptValue& q);
inline static QScriptValue get(const QScriptValuePrivate* d);
inline static QScriptValue get(QScriptValuePrivate* d);
inline ~QScriptValuePrivate();
inline QScriptValuePrivate();
inline QScriptValuePrivate(const QString& string);
inline QScriptValuePrivate(bool value);
inline QScriptValuePrivate(int number);
inline QScriptValuePrivate(uint number);
inline QScriptValuePrivate(qsreal number);
inline QScriptValuePrivate(QScriptValue::SpecialValue value);
inline QScriptValuePrivate(const QScriptEngine* engine, bool value);
inline QScriptValuePrivate(const QScriptEngine* engine, int value);
inline QScriptValuePrivate(const QScriptEngine* engine, uint value);
inline QScriptValuePrivate(const QScriptEngine* engine, qsreal value);
inline QScriptValuePrivate(const QScriptEngine* engine, const QString& value);
inline QScriptValuePrivate(const QScriptEngine* engine, QScriptValue::SpecialValue value);
inline QScriptValuePrivate(const QScriptEnginePrivate* engine, JSValueRef value);
inline QScriptValuePrivate(const QScriptEnginePrivate* engine, JSValueRef value, JSObjectRef object);
inline bool isValid() const;
inline bool isBool();
inline bool isNumber();
inline bool isNull();
inline bool isString();
inline bool isUndefined();
inline bool isError();
inline bool isObject();
inline bool isFunction();
inline QString toString() const;
inline qsreal toNumber() const;
inline bool toBool() const;
inline qsreal toInteger() const;
inline qint32 toInt32() const;
inline quint32 toUInt32() const;
inline quint16 toUInt16() const;
inline bool equals(QScriptValuePrivate* other);
inline bool strictlyEquals(const QScriptValuePrivate* other) const;
inline bool assignEngine(QScriptEnginePrivate* engine);
inline QScriptValuePrivate* call(const QScriptValuePrivate* , const QScriptValueList& args);
inline JSGlobalContextRef context() const;
inline JSValueRef value() const;
inline JSObjectRef object() const;
inline QScriptEnginePrivate* engine() const;
private:
// Please, update class documentation when you change the enum.
enum States {
Invalid = 0,
CString = 0x1000,
CNumber,
CBool,
CSpecial,
JSValue = 0x2000, // JS values are equal or higher then this value.
JSNative,
JSObject
} m_state;
QScriptEnginePtr m_engine;
QString m_string;
qsreal m_number;
JSValueRef m_value;
JSObjectRef m_object;
inline void setValue(JSValueRef);
inline bool inherits(const char*);
inline bool isJSBased() const;
inline bool isNumberBased() const;
inline bool isStringBased() const;
};
QScriptValuePrivate* QScriptValuePrivate::get(const QScriptValue& q) { return q.d_ptr.data(); }
QScriptValue QScriptValuePrivate::get(const QScriptValuePrivate* d)
{
return QScriptValue(const_cast<QScriptValuePrivate*>(d));
}
QScriptValue QScriptValuePrivate::get(QScriptValuePrivate* d)
{
return QScriptValue(d);
}
QScriptValuePrivate::~QScriptValuePrivate()
{
if (m_value)
JSValueUnprotect(context(), m_value);
}
QScriptValuePrivate::QScriptValuePrivate()
: m_state(Invalid)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(const QString& string)
: m_state(CString)
, m_string(string)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(bool value)
: m_state(CBool)
, m_number(value)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(int number)
: m_state(CNumber)
, m_number(number)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(uint number)
: m_state(CNumber)
, m_number(number)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(qsreal number)
: m_state(CNumber)
, m_number(number)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(QScriptValue::SpecialValue value)
: m_state(CSpecial)
, m_number(value)
, m_value(0)
{
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEngine* engine, bool value)
: m_state(JSNative)
{
if (!engine) {
// slower path reinitialization
m_state = CBool;
m_number = value;
m_value = 0;
} else {
m_engine = QScriptEnginePrivate::get(engine);
m_value = m_engine->makeJSValue(value);
JSValueProtect(context(), m_value);
}
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEngine* engine, int value)
: m_state(JSNative)
{
if (!engine) {
// slower path reinitialization
m_state = CNumber;
m_number = value;
m_value = 0;
} else {
m_engine = QScriptEnginePrivate::get(engine);
m_value = m_engine->makeJSValue(value);
JSValueProtect(context(), m_value);
}
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEngine* engine, uint value)
: m_state(JSNative)
{
if (!engine) {
// slower path reinitialization
m_state = CNumber;
m_number = value;
m_value = 0;
} else {
m_engine = QScriptEnginePrivate::get(engine);
m_value = m_engine->makeJSValue(value);
JSValueProtect(context(), m_value);
}
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEngine* engine, qsreal value)
: m_state(JSNative)
{
if (!engine) {
// slower path reinitialization
m_state = CNumber;
m_number = value;
m_value = 0;
} else {
m_engine = QScriptEnginePrivate::get(engine);
m_value = m_engine->makeJSValue(value);
JSValueProtect(context(), m_value);
}
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEngine* engine, const QString& value)
: m_state(JSNative)
{
if (!engine) {
// slower path reinitialization
m_state = CString;
m_string = value;
m_value = 0;
} else {
m_engine = QScriptEnginePrivate::get(engine);
m_value = m_engine->makeJSValue(value);
JSValueProtect(context(), m_value);
}
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEngine* engine, QScriptValue::SpecialValue value)
: m_state(JSNative)
{
if (!engine) {
// slower path reinitialization
m_state = CSpecial;
m_number = value;
m_value = 0;
} else {
m_engine = QScriptEnginePrivate::get(engine);
m_value = m_engine->makeJSValue(value);
JSValueProtect(context(), m_value);
}
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEnginePrivate* engine, JSValueRef value)
: m_state(JSValue)
, m_engine(const_cast<QScriptEnginePrivate*>(engine))
, m_value(value)
{
Q_ASSERT(engine);
JSValueProtect(context(), m_value);
}
QScriptValuePrivate::QScriptValuePrivate(const QScriptEnginePrivate* engine, JSValueRef value, JSObjectRef object)
: m_state(JSObject)
, m_engine(const_cast<QScriptEnginePrivate*>(engine))
, m_value(value)
, m_object(object)
{
Q_ASSERT(engine);
JSValueProtect(context(), m_value);
}
bool QScriptValuePrivate::isValid() const { return m_state != Invalid; }
bool QScriptValuePrivate::isBool()
{
switch (m_state) {
case CBool:
return true;
case JSValue:
if (isObject())
return false;
// Fall-through.
case JSNative:
return JSValueIsBoolean(context(), value());
default:
return false;
}
}
bool QScriptValuePrivate::isNumber()
{
switch (m_state) {
case CNumber:
return true;
case JSValue:
if (isObject())
return false;
// Fall-through.
case JSNative:
return JSValueIsNumber(context(), value());
default:
return false;
}
}
bool QScriptValuePrivate::isNull()
{
switch (m_state) {
case CSpecial:
return m_number == static_cast<int>(QScriptValue::NullValue);
case JSValue:
if (isObject())
return false;
// Fall-through.
case JSNative:
return JSValueIsNull(context(), value());
default:
return false;
}
}
bool QScriptValuePrivate::isString()
{
switch (m_state) {
case CString:
return true;
case JSValue:
if (isObject())
return false;
// Fall-through.
case JSNative:
return JSValueIsString(context(), value());
default:
return false;
}
}
bool QScriptValuePrivate::isUndefined()
{
switch (m_state) {
case CSpecial:
return m_number == static_cast<int>(QScriptValue::UndefinedValue);
case JSValue:
if (isObject())
return false;
// Fall-through.
case JSNative:
return JSValueIsUndefined(context(), value());
default:
return false;
}
}
bool QScriptValuePrivate::isError()
{
switch (m_state) {
case JSValue:
if (!isObject())
return false;
// Fall-through.
case JSObject:
return inherits("Error");
default:
return false;
}
}
bool QScriptValuePrivate::isObject()
{
switch (m_state) {
case JSObject:
return true;
case JSValue:
m_object = JSValueToObject(context(), value(), /* exception */ 0);
if (!m_object)
return false;
m_state = JSObject;
return true;
default:
return false;
}
}
bool QScriptValuePrivate::isFunction()
{
switch (m_state) {
case JSValue:
m_object = JSValueToObject(context(), value(), /* exception */ 0);
if (!m_object)
return false;
m_state = JSObject;
// Fall-through.
case JSObject:
return JSObjectIsFunction(context(), object());
default:
return false;
}
}
QString QScriptValuePrivate::toString() const
{
switch (m_state) {
case Invalid:
return QString();
case CBool:
return m_number ? QString::fromLatin1("true") : QString::fromLatin1("false");
case CString:
return m_string;
case CNumber:
return QString::number(m_number);
case CSpecial:
return m_number == QScriptValue::NullValue ? QString::fromLatin1("null") : QString::fromLatin1("undefined");
case JSValue:
case JSNative:
case JSObject:
return QScriptConverter::toString(JSValueToStringCopy(context(), value(), /* exception */ 0));
}
Q_ASSERT_X(false, "toString()", "Not all states are included in the previous switch statement.");
return QString(); // Avoid compiler warning.
}
qsreal QScriptValuePrivate::toNumber() const
{
switch (m_state) {
case JSValue:
case JSNative:
case JSObject:
return JSValueToNumber(context(), value(), /* exception */ 0);
case CNumber:
return m_number;
case CBool:
return m_number ? 1 : 0;
case Invalid:
return 0;
case CSpecial:
return m_number == QScriptValue::NullValue ? 0 : qQNaN();
case CString:
bool ok;
qsreal result = m_string.toDouble(&ok);
if (ok)
return result;
result = m_string.toInt(&ok, 0); // Try other bases.
if (ok)
return result;
if (m_string == "Infinity" || m_string == "-Infinity")
return qInf();
return m_string.length() ? qQNaN() : 0;
}
Q_ASSERT_X(false, "toNumber()", "Not all states are included in the previous switch statement.");
return 0; // Avoid compiler warning.
}
bool QScriptValuePrivate::toBool() const
{
switch (m_state) {
case JSValue:
case JSNative:
return JSValueToBoolean(context(), value());
case JSObject:
return true;
case CNumber:
return !(qIsNaN(m_number) || !m_number);
case CBool:
return m_number;
case Invalid:
case CSpecial:
return false;
case CString:
return m_string.length();
}
Q_ASSERT_X(false, "toBool()", "Not all states are included in the previous switch statement.");
return false; // Avoid compiler warning.
}
qsreal QScriptValuePrivate::toInteger() const
{
// TODO it is not true implementation!
return toNumber();
}
qint32 QScriptValuePrivate::toInt32() const
{
// TODO it is not true implementation!
return toNumber();
}
quint32 QScriptValuePrivate::toUInt32() const
{
// TODO it is not true implementation!
return toNumber();
}
quint16 QScriptValuePrivate::toUInt16() const
{
// TODO it is not true implementation!
return toNumber();
}
bool QScriptValuePrivate::equals(QScriptValuePrivate* other)
{
if (!isValid() || !other->isValid())
return false;
if ((m_state == other->m_state) && !isJSBased()) {
if (isNumberBased())
return m_number == other->m_number;
return m_string == other->m_string;
}
if (isJSBased() && !other->isJSBased()) {
if (!other->assignEngine(engine())) {
qWarning("equals(): Cannot compare to a value created in a different engine");
return false;
}
} else if (!isJSBased() && other->isJSBased()) {
if (!other->assignEngine(other->engine())) {
qWarning("equals(): Cannot compare to a value created in a different engine");
return false;
}
}
return JSValueIsEqual(context(), value(), other->value(), /* exception */ 0);
}
bool QScriptValuePrivate::strictlyEquals(const QScriptValuePrivate* other) const
{
if (m_state != other->m_state)
return false;
if (isJSBased()) {
if (other->engine() != engine()) {
qWarning("strictlyEquals(): Cannot compare to a value created in a different engine");
return false;
}
return JSValueIsStrictEqual(context(), value(), other->value());
}
if (isStringBased())
return m_string == other->m_string;
if (isNumberBased())
return m_number == other->m_number;
return false; // Invalid state.
}
/*!
Tries to assign \a engine to this value. Returns true on success; otherwise returns false.
*/
bool QScriptValuePrivate::assignEngine(QScriptEnginePrivate* engine)
{
JSValueRef value;
switch (m_state) {
case CBool:
value = engine->makeJSValue(static_cast<bool>(m_number));
break;
case CString:
value = engine->makeJSValue(m_string);
break;
case CNumber:
value = engine->makeJSValue(m_number);
break;
case CSpecial:
value = engine->makeJSValue(static_cast<QScriptValue::SpecialValue>(m_number));
break;
default:
if (!isJSBased())
Q_ASSERT_X(!isJSBased(), "assignEngine()", "Not all states are included in the previous switch statement.");
else
qWarning("JSValue can't be rassigned to an another engine.");
return false;
}
m_engine = engine;
m_state = JSNative;
setValue(value);
return true;
}
QScriptValuePrivate* QScriptValuePrivate::call(const QScriptValuePrivate*, const QScriptValueList& args)
{
switch (m_state) {
case JSValue:
m_object = JSValueToObject(context(), value(), /* exception */ 0);
if (!object()) {
m_state = JSValue;
return new QScriptValuePrivate;
}
m_state = JSObject;
// Fall-through.
case JSObject:
{
// Convert all arguments and bind to the engine.
int argc = args.size();
QVarLengthArray<JSValueRef, 8> argv(argc);
QScriptValueList::const_iterator i = args.constBegin();
for (int j = 0; i != args.constEnd(); j++, i++) {
QScriptValuePrivate* value = QScriptValuePrivate::get(*i);
if (!value->assignEngine(engine())) {
qWarning("QScriptValue::call() failed: cannot call function with values created in a different engine");
return new QScriptValuePrivate;
}
argv[j] = value->value();
}
// Make the call
JSValueRef exception = 0;
JSValueRef result = JSObjectCallAsFunction(context(), object(), /* thisObject */ 0, argc, argv.constData(), &exception);
if (!result && exception)
return new QScriptValuePrivate(engine(), exception);
if (result && !exception)
return new QScriptValuePrivate(engine(), result);
}
// this QSV is not a function <-- !result && !exception. Fall-through.
default:
return new QScriptValuePrivate;
}
}
QScriptEnginePrivate* QScriptValuePrivate::engine() const
{
// As long as m_engine is an autoinitializated pointer we can safely return it without
// checking current state.
return m_engine.data();
}
JSGlobalContextRef QScriptValuePrivate::context() const
{
Q_ASSERT(isJSBased());
return m_engine->context();
}
JSValueRef QScriptValuePrivate::value() const
{
Q_ASSERT(isJSBased());
return m_value;
}
JSObjectRef QScriptValuePrivate::object() const
{
Q_ASSERT(m_state == JSObject);
return m_object;
}
void QScriptValuePrivate::setValue(JSValueRef value)
{
if (m_value)
JSValueUnprotect(context(), m_value);
if (value)
JSValueProtect(context(), value);
m_value = value;
}
/*!
\internal
Returns true if QSV is created from constructor with the given \a name, it has to be a
built-in type.
*/
bool QScriptValuePrivate::inherits(const char* name)
{
Q_ASSERT(isJSBased());
JSObjectRef globalObject = JSContextGetGlobalObject(context());
JSValueRef error = JSObjectGetProperty(context(), globalObject, QScriptConverter::toString(name), 0);
return JSValueIsInstanceOfConstructor(context(), value(), JSValueToObject(context(), error, /* exception */ 0), /* exception */ 0);
}
/*!
\internal
Returns true if QSV have an engine associated.
*/
bool QScriptValuePrivate::isJSBased() const { return m_state >= JSValue; }
/*!
\internal
Returns true if current value of QSV is placed in m_number.
*/
bool QScriptValuePrivate::isNumberBased() const { return !isJSBased() && !isStringBased() && m_state != Invalid; }
/*!
\internal
Returns true if current value of QSV is placed in m_string.
*/
bool QScriptValuePrivate::isStringBased() const { return m_state == CString; }
#endif // qscriptvalue_p_h