Initial work on the String object.
authorRoberto Raggi <roberto.raggi@nokia.com>
Mon, 14 May 2012 14:03:10 +0000 (16:03 +0200)
committerRoberto Raggi <roberto.raggi@nokia.com>
Mon, 14 May 2012 14:03:10 +0000 (16:03 +0200)
main.cpp
qmljs_objects.cpp
qmljs_objects.h
qmljs_runtime.cpp
qmljs_runtime.h
qv4ecmaobjects.cpp [new file with mode: 0644]
qv4ecmaobjects_p.h [new file with mode: 0644]
tests/string.1.js
v4.pro

index 1bf71b4..a3c0b73 100644 (file)
--- a/main.cpp
+++ b/main.cpp
@@ -3,6 +3,7 @@
 #include "qv4codegen_p.h"
 #include "qv4isel_p.h"
 #include "qv4syntaxchecker_p.h"
+#include "qv4ecmaobjects_p.h"
 
 #include <QtCore>
 #include <private/qqmljsengine_p.h>
@@ -37,72 +38,12 @@ struct Print: FunctionObject
             __qmljs_to_string(ctx, &v, &ctx->arguments[i]);
             if (i)
                 std::cout << ' ';
-            std::cout << qPrintable(v.stringValue->text());
+            std::cout << qPrintable(v.stringValue->toQString());
         }
         std::cout << std::endl;
     }
 };
 
-struct ObjectCtor: FunctionObject
-{
-    ObjectCtor(Context *scope): FunctionObject(scope) {}
-
-    virtual void construct(Context *ctx)
-    {
-        __qmljs_init_object(ctx, &ctx->thisObject, new Object());
-    }
-
-    virtual void call(Context *)
-    {
-        assert(!"not here");
-    }
-};
-
-struct StringCtor: FunctionObject
-{
-    StringCtor(Context *scope): FunctionObject(scope) {}
-
-    virtual void construct(Context *ctx)
-    {
-        Value arg = ctx->argument(0);
-        __qmljs_to_string(ctx, &arg, &arg);
-        __qmljs_init_object(ctx, &ctx->thisObject, new StringObject(arg));
-    }
-
-    virtual void call(Context *ctx)
-    {
-        const Value arg = ctx->argument(0);
-        if (arg.is(UNDEFINED_TYPE))
-            __qmljs_init_string(ctx, &ctx->result, String::get(ctx, QString()));
-        else
-            __qmljs_to_string(ctx, &ctx->result, &arg);
-    }
-};
-
-struct StringPrototype: Object
-{
-    StringPrototype(Context *ctx, FunctionObject *ctor)
-    {
-        setProperty(ctx, QLatin1String("constructor"), Value::object(ctx, ctor));
-        setProperty(ctx, QLatin1String("toString"), toString);
-    }
-
-    void setProperty(Context *ctx, const QString &name, const Value &value)
-    {
-        put(String::get(ctx, name), value);
-    }
-
-    void setProperty(Context *ctx, const QString &name, void (*code)(Context *))
-    {
-        setProperty(ctx, name, Value::object(ctx, new NativeFunction(ctx, code)));
-    }
-
-    static void toString(Context *ctx)
-    {
-        __qmljs_to_string(ctx, &ctx->result, &ctx->thisObject);
-    }
-};
-
 } // builtins
 
 
@@ -153,10 +94,10 @@ void evaluate(QQmlJS::Engine *engine, const QString &fileName, const QString &co
         __qmljs_init_object(ctx, &ctx->activation, globalObject);
 
         globalObject->put(VM::String::get(ctx, QLatin1String("print")),
-                                         VM::Value::object(ctx, new builtins::Print(ctx)));
+                          VM::Value::object(ctx, new builtins::Print(ctx)));
 
         globalObject->put(VM::String::get(ctx, QLatin1String("Object")),
-                                         VM::Value::object(ctx, new builtins::ObjectCtor(ctx)));
+                          VM::Value::object(ctx, new builtins::ObjectCtor(ctx)));
 
         VM::FunctionObject *stringCtor = new builtins::StringCtor(ctx);
         stringCtor->put(prototype, VM::Value::object(ctx, new builtins::StringPrototype(ctx, stringCtor)));
@@ -165,7 +106,7 @@ void evaluate(QQmlJS::Engine *engine, const QString &fileName, const QString &co
         foreach (IR::Function *function, module.functions) {
             if (function->name && ! function->name->isEmpty()) {
                 globalObject->put(VM::String::get(ctx, *function->name),
-                                                 VM::Value::object(ctx, new VM::ScriptFunction(ctx, function)));
+                                  VM::Value::object(ctx, new VM::ScriptFunction(ctx, function)));
             }
         }
         codeByName.value(QLatin1String("%entry"))->code(ctx);
index a1a97e5..bcc08d3 100644 (file)
@@ -11,6 +11,16 @@ Object::~Object()
     delete members;
 }
 
+void Object::setProperty(Context *ctx, const QString &name, const Value &value)
+{
+    put(String::get(ctx, name), value);
+}
+
+void Object::setProperty(Context *ctx, const QString &name, void (*code)(Context *))
+{
+    setProperty(ctx, name, Value::object(ctx, new NativeFunction(ctx, code)));
+}
+
 bool Object::get(String *name, Value *result)
 {
     if (Value *prop = getProperty(name)) {
index 8658178..2dd4b09 100644 (file)
@@ -28,7 +28,7 @@ struct String {
     String(const QString &text)
         : _text(text), _hashValue(0) {}
 
-    inline const QString &text() const {
+    inline const QString &toQString() const {
         return _text;
     }
 
@@ -66,7 +66,7 @@ struct Property {
     inline bool hasName(String *n) const {
         if (name == n) {
             return true;
-        } else if (name->hashValue() == n->hashValue() && name->text() == n->text()) {
+        } else if (name->hashValue() == n->hashValue() && name->toQString() == n->toQString()) {
             return true;
         }
         return false;
@@ -209,6 +209,12 @@ struct Object {
     virtual bool deleteProperty(String *name, bool flag);
     virtual void defaultValue(Value *result, int typeHint);
     // ### TODO: defineOwnProperty(name, descriptor, boolean) -> boolean
+
+    //
+    // helpers
+    //
+    void setProperty(Context *ctx, const QString &name, const Value &value);
+    void setProperty(Context *ctx, const QString &name, void (*code)(Context *));
 };
 
 struct BooleanObject: Object {
index 3bacfa3..6fdf179 100644 (file)
@@ -14,6 +14,62 @@ Value Value::string(Context *ctx, const QString &s)
     return string(ctx, String::get(ctx, s));
 }
 
+int Value::toInt32(double number)
+{
+    if (! number || isnan(number) || isinf(number))
+        return +0;
+    return (int) trunc(number); // ###
+}
+
+int Value::toInteger(double number)
+{
+    if (isnan(number))
+        return +0;
+    else if (! number || isinf(number))
+        return number;
+    const double v = floor(fabs(number));
+    return signbit(number) ? -v : v;
+}
+
+int Value::toUInt16(Context *ctx)
+{
+    return __qmljs_to_uint16(ctx, this);
+}
+
+int Value::toInt32(Context *ctx)
+{
+    return __qmljs_to_int32(ctx, this);
+}
+
+uint Value::toUInt32(Context *ctx)
+{
+    return __qmljs_to_int32(ctx, this);
+}
+
+bool Value::toBoolean(Context *ctx) const
+{
+    return __qmljs_to_boolean(ctx, this);
+}
+
+double Value::toInteger(Context *ctx) const
+{
+    return __qmljs_to_integer(ctx, this);
+}
+
+double Value::toNumber(Context *ctx) const
+{
+    return __qmljs_to_number(ctx, this);
+}
+
+String *Value::toString(Context *ctx) const
+{
+    Value v;
+    __qmljs_to_string(ctx, &v, this);
+    assert(v.is(STRING_TYPE));
+    return v.stringValue;
+}
+
+
 extern "C" {
 
 void __qmljs_init_closure(Context *ctx, Value *result, IR::Function *clos)
@@ -102,13 +158,13 @@ void __qmljs_in(Context *ctx, Value *result, const Value *left, const Value *rig
 
 int __qmljs_string_length(Context *, String *string)
 {
-    return string->text().length();
+    return string->toQString().length();
 }
 
 double __qmljs_string_to_number(Context *, String *string)
 {
     bool ok;
-    return string->text().toDouble(&ok); // ### TODO
+    return string->toQString().toDouble(&ok); // ### TODO
 }
 
 void __qmljs_string_from_number(Context *ctx, Value *result, double number)
@@ -119,19 +175,19 @@ void __qmljs_string_from_number(Context *ctx, Value *result, double number)
 
 bool __qmljs_string_compare(Context *, String *left, String *right)
 {
-    return left->text() < right->text();
+    return left->toQString() < right->toQString();
 }
 
 bool __qmljs_string_equal(Context *, String *left, String *right)
 {
     return left == right ||
             (left->hashValue() == right->hashValue() &&
-             left->text() == right->text());
+             left->toQString() == right->toQString());
 }
 
 String *__qmljs_string_concat(Context *ctx, String *first, String *second)
 {
-    return String::get(ctx, first->text() + second->text());
+    return String::get(ctx, first->toQString() + second->toQString());
 }
 
 bool __qmljs_is_function(Context *, const Value *value)
index 143319e..2a0f42d 100644 (file)
@@ -199,6 +199,23 @@ struct Value {
     }
 
     static Value string(Context *ctx, const QString &string);
+
+    static int toInteger(double number);
+    static int toInt32(double value);
+    int toUInt16(Context *ctx);
+    int toInt32(Context *ctx);
+    uint toUInt32(Context *ctx);
+    bool toBoolean(Context *ctx) const;
+    double toInteger(Context *ctx) const;
+    double toNumber(Context *ctx) const;
+    String *toString(Context *ctx) const;
+
+    inline bool isUndefined() const { return is(UNDEFINED_TYPE); }
+    inline bool isNull() const { return is(NULL_TYPE); }
+    inline bool isString() const { return is(STRING_TYPE); }
+    inline bool isBoolean() const { return is(BOOLEAN_TYPE); }
+    inline bool isNumber() const { return is(NUMBER_TYPE); }
+    inline bool isObject() const { return is(OBJECT_TYPE); }
 };
 
 extern "C" {
@@ -311,7 +328,8 @@ inline double __qmljs_to_integer(Context *ctx, const Value *value)
         return +0;
     else if (! number || isinf(number))
         return number;
-    return signbit(number) * floor(fabs(number));
+    const double v = floor(fabs(number));
+    return signbit(number) ? -v : v;
 }
 
 inline int __qmljs_to_int32(Context *ctx, const Value *value)
diff --git a/qv4ecmaobjects.cpp b/qv4ecmaobjects.cpp
new file mode 100644 (file)
index 0000000..455a454
--- /dev/null
@@ -0,0 +1,333 @@
+
+#include "qv4ecmaobjects_p.h"
+#include <qmath.h>
+#include <qnumeric.h>
+#include <cassert>
+
+using namespace QQmlJS::VM;
+
+//
+// Object
+//
+ObjectCtor::ObjectCtor(Context *scope)
+    : FunctionObject(scope)
+{
+}
+
+void ObjectCtor::construct(Context *ctx)
+{
+    __qmljs_init_object(ctx, &ctx->thisObject, new Object());
+}
+
+void ObjectCtor::call(Context *)
+{
+    assert(!"not here");
+}
+
+
+ObjectPrototype::ObjectPrototype(Context *ctx, FunctionObject *ctor)
+{
+    setProperty(ctx, QLatin1String("constructor"), Value::object(ctx, ctor));
+}
+
+//
+// String
+//
+StringCtor::StringCtor(Context *scope)
+    : FunctionObject(scope)
+{
+}
+
+void StringCtor::construct(Context *ctx)
+{
+    Value arg = ctx->argument(0);
+    __qmljs_to_string(ctx, &arg, &arg);
+    __qmljs_init_object(ctx, &ctx->thisObject, new StringObject(arg));
+}
+
+void StringCtor::call(Context *ctx)
+{
+    const Value arg = ctx->argument(0);
+    if (arg.is(UNDEFINED_TYPE))
+        __qmljs_init_string(ctx, &ctx->result, String::get(ctx, QString()));
+    else
+        __qmljs_to_string(ctx, &ctx->result, &arg);
+}
+
+StringPrototype::StringPrototype(Context *ctx, FunctionObject *ctor)
+{
+    setProperty(ctx, QLatin1String("constructor"), Value::object(ctx, ctor));
+    setProperty(ctx, QLatin1String("toString"), method_toString);
+    setProperty(ctx, QLatin1String("valueOf"), method_valueOf);
+    setProperty(ctx, QLatin1String("charAt"), method_charAt);
+    setProperty(ctx, QLatin1String("charCodeAt"), method_charCodeAt);
+    setProperty(ctx, QLatin1String("concat"), method_concat);
+    setProperty(ctx, QLatin1String("indexOf"), method_indexOf);
+    setProperty(ctx, QLatin1String("lastIndexOf"), method_lastIndexOf);
+    setProperty(ctx, QLatin1String("localeCompare"), method_localeCompare);
+    setProperty(ctx, QLatin1String("match"), method_match);
+    setProperty(ctx, QLatin1String("replace"), method_replace);
+    setProperty(ctx, QLatin1String("search"), method_search);
+    setProperty(ctx, QLatin1String("slice"), method_slice);
+    setProperty(ctx, QLatin1String("split"), method_split);
+    setProperty(ctx, QLatin1String("substr"), method_substr);
+    setProperty(ctx, QLatin1String("substring"), method_substring);
+    setProperty(ctx, QLatin1String("toLowerCase"), method_toLowerCase);
+    setProperty(ctx, QLatin1String("toLocaleLowerCase"), method_toLocaleLowerCase);
+    setProperty(ctx, QLatin1String("toUpperCase"), method_toUpperCase);
+    setProperty(ctx, QLatin1String("toLocaleUpperCase"), method_toLocaleUpperCase);
+    setProperty(ctx, QLatin1String("fromCharCode"), method_fromCharCode);
+}
+
+QString StringPrototype::getThisString(Context *ctx)
+{
+    Value v;
+    __qmljs_to_string(ctx, &v, &ctx->thisObject);
+    assert(v.is(STRING_TYPE));
+    return v.stringValue->toQString();
+}
+
+void StringPrototype::method_toString(Context *ctx)
+{
+    __qmljs_to_string(ctx, &ctx->result, &ctx->thisObject);
+}
+
+void StringPrototype::method_valueOf(Context *ctx)
+{
+    ctx->thisObject.objectValue->defaultValue(&ctx->result, STRING_HINT);
+}
+
+void StringPrototype::method_charAt(Context *ctx)
+{
+    const QString str = getThisString(ctx);
+
+    int pos = 0;
+    if (ctx->argumentCount > 0)
+        pos = (int) ctx->argument(0).toInteger(ctx);
+
+    QString result;
+    if (pos >= 0 && pos < str.length())
+        result += str.at(pos);
+
+    ctx->result = Value::string(ctx, result);
+}
+
+void StringPrototype::method_charCodeAt(Context *ctx)
+{
+    const QString str = getThisString(ctx);
+
+    int pos = 0;
+    if (ctx->argumentCount > 0)
+        pos = (int) ctx->argument(0).toInteger(ctx);
+
+    double result = qSNaN();
+
+    if (pos >= 0 && pos < str.length())
+        result = str.at(pos).unicode();
+
+    __qmljs_init_number(ctx, &ctx->result, result);
+}
+
+void StringPrototype::method_concat(Context *ctx)
+{
+    QString value = getThisString(ctx);
+
+    for (unsigned i = 0; i < ctx->argumentCount; ++i) {
+        Value v;
+        __qmljs_to_string(ctx, &v, &ctx->arguments[i]);
+        assert(v.is(STRING_TYPE));
+        value += v.stringValue->toQString();
+    }
+
+    ctx->result = Value::string(ctx, value);
+}
+
+void StringPrototype::method_indexOf(Context *ctx)
+{
+    QString value = getThisString(ctx);
+
+    QString searchString;
+    if (ctx->argumentCount)
+        searchString = ctx->argument(0).toString(ctx)->toQString();
+
+    int pos = 0;
+    if (ctx->argumentCount > 1)
+        pos = (int) ctx->argument(1).toInteger(ctx);
+
+    int index = -1;
+    if (! value.isEmpty())
+        index = value.indexOf(searchString, qMin(qMax(pos, 0), value.length()));
+
+    __qmljs_init_number(ctx, &ctx->result, index);
+}
+
+void StringPrototype::method_lastIndexOf(Context *ctx)
+{
+    const QString value = getThisString(ctx);
+
+    QString searchString;
+    if (ctx->argumentCount) {
+        Value v;
+        __qmljs_to_string(ctx, &v, &ctx->arguments[0]);
+        searchString = v.stringValue->toQString();
+    }
+
+    Value posArg = ctx->argument(1);
+    double position = __qmljs_to_number(ctx, &posArg);
+    if (qIsNaN(position))
+        position = +qInf();
+    else
+        position = trunc(position);
+
+    int pos = trunc(qMin(qMax(position, 0.0), double(value.length())));
+    if (!searchString.isEmpty() && pos == value.length())
+        --pos;
+    int index = value.lastIndexOf(searchString, pos);
+    __qmljs_init_number(ctx, &ctx->result, index);
+}
+
+void StringPrototype::method_localeCompare(Context *ctx)
+{
+    const QString value = getThisString(ctx);
+    const QString that = ctx->argument(0).toString(ctx)->toQString();
+    __qmljs_init_number(ctx, &ctx->result, QString::localeAwareCompare(value, that));
+}
+
+void StringPrototype::method_match(Context *)
+{
+    // requires Regexp
+    Q_UNIMPLEMENTED();
+}
+
+void StringPrototype::method_replace(Context *)
+{
+    // requires Regexp
+    Q_UNIMPLEMENTED();
+}
+
+void StringPrototype::method_search(Context *)
+{
+    // requires Regexp
+    Q_UNIMPLEMENTED();
+}
+
+void StringPrototype::method_slice(Context *ctx)
+{
+    const QString text = getThisString(ctx);
+    const int length = text.length();
+
+    int start = int (ctx->argument(0).toInteger(ctx));
+    int end = ctx->argument(1).isUndefined()
+              ? length : int (ctx->argument(1).toInteger(ctx));
+
+    if (start < 0)
+        start = qMax(length + start, 0);
+    else
+        start = qMin(start, length);
+
+    if (end < 0)
+        end = qMax(length + end, 0);
+    else
+        end = qMin(end, length);
+
+    int count = qMax(0, end - start);
+    ctx->result = Value::string(ctx, text.mid(start, count));
+}
+
+void StringPrototype::method_split(Context *)
+{
+    // requires Array
+    Q_UNIMPLEMENTED();
+}
+
+void StringPrototype::method_substr(Context *ctx)
+{
+    const QString value = getThisString(ctx);
+
+    double start = 0;
+    if (ctx->argumentCount > 0)
+        start = ctx->argument(0).toInteger(ctx);
+
+    double length = +qInf();
+    if (ctx->argumentCount > 1)
+        length = ctx->argument(1).toInteger(ctx);
+
+    double count = value.length();
+    if (start < 0)
+        start = qMax(count + start, 0.0);
+
+    length = qMin(qMax(length, 0.0), count - start);
+
+    qint32 x = Value::toInt32(start);
+    qint32 y = Value::toInt32(length);
+    ctx->result = Value::string(ctx, value.mid(x, y));
+}
+
+void StringPrototype::method_substring(Context *ctx)
+{
+    QString value = getThisString(ctx);
+    int length = value.length();
+
+    double start = 0;
+    double end = length;
+
+    if (ctx->argumentCount > 0)
+        start = ctx->argument(0).toInteger(ctx);
+
+    if (ctx->argumentCount > 1)
+        end = ctx->argument(1).toInteger(ctx);
+
+    if (qIsNaN(start) || start < 0)
+        start = 0;
+
+    if (qIsNaN(end) || end < 0)
+        end = 0;
+
+    if (start > length)
+        start = length;
+
+    if (end > length)
+        end = length;
+
+    if (start > end) {
+        double was = start;
+        start = end;
+        end = was;
+    }
+
+    qint32 x = Value::toInt32(start);
+    qint32 y = Value::toInt32(end - start);
+    ctx->result = Value::string(ctx, value.mid(x, y));
+}
+
+void StringPrototype::method_toLowerCase(Context *ctx)
+{
+    QString value = getThisString(ctx);
+    ctx->result = Value::string(ctx, value.toLower());
+}
+
+void StringPrototype::method_toLocaleLowerCase(Context *ctx)
+{
+    method_toLowerCase(ctx);
+}
+
+void StringPrototype::method_toUpperCase(Context *ctx)
+{
+    QString value = getThisString(ctx);
+    ctx->result = Value::string(ctx, value.toUpper());
+}
+
+void StringPrototype::method_toLocaleUpperCase(Context *ctx)
+{
+    method_toUpperCase(ctx);
+}
+
+void StringPrototype::method_fromCharCode(Context *ctx)
+{
+    QString str;
+    for (unsigned i = 0; i < ctx->argumentCount; ++i) {
+        QChar c(ctx->argument(i).toUInt16(ctx));
+        str += c;
+    }
+    ctx->result = Value::string(ctx, str);
+}
diff --git a/qv4ecmaobjects_p.h b/qv4ecmaobjects_p.h
new file mode 100644 (file)
index 0000000..1b5d630
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef QV4ECMAOBJECTS_P_H
+#define QV4ECMAOBJECTS_P_H
+
+#include "qmljs_objects.h"
+
+namespace QQmlJS {
+namespace VM {
+
+struct ObjectCtor: FunctionObject
+{
+    ObjectCtor(Context *scope);
+
+    virtual void construct(Context *ctx);
+    virtual void call(Context *);
+};
+
+struct ObjectPrototype: Object
+{
+    ObjectPrototype(Context *ctx, FunctionObject *ctor);
+};
+
+struct StringCtor: FunctionObject
+{
+    StringCtor(Context *scope);
+
+    virtual void construct(Context *ctx);
+    virtual void call(Context *ctx);
+};
+
+struct StringPrototype: Object
+{
+    StringPrototype(Context *ctx, FunctionObject *ctor);
+
+protected:
+    static QString getThisString(Context *ctx);
+
+    static void method_toString(Context *ctx);
+    static void method_valueOf(Context *ctx);
+    static void method_charAt(Context *ctx);
+    static void method_charCodeAt(Context *ctx);
+    static void method_concat(Context *ctx);
+    static void method_indexOf(Context *ctx);
+    static void method_lastIndexOf(Context *ctx);
+    static void method_localeCompare(Context *ctx);
+    static void method_match(Context *ctx);
+    static void method_replace(Context *ctx);
+    static void method_search(Context *ctx);
+    static void method_slice(Context *ctx);
+    static void method_split(Context *ctx);
+    static void method_substr(Context *ctx);
+    static void method_substring(Context *ctx);
+    static void method_toLowerCase(Context *ctx);
+    static void method_toLocaleLowerCase(Context *ctx);
+    static void method_toUpperCase(Context *ctx);
+    static void method_toLocaleUpperCase(Context *ctx);
+    static void method_fromCharCode(Context *ctx);
+};
+
+} // end of namespace VM
+} // end of namespace QQmlJS
+
+#endif // QV4ECMAOBJECTS_P_H
index 5ae5535..8cc76bb 100644 (file)
@@ -11,3 +11,10 @@ print(s3)
 var s4 = new String.prototype.constructor(321)
 print(s4)
 
+print(s4.charAt(0), s4.charAt(1), s4.charCodeAt(0))
+
+print(s4.concat("ciao", "cool"))
+print(s4.indexOf('1'))
+print(s4.lastIndexOf('1', 1))
+
+print("ss", s4.slice(0, 2))
diff --git a/v4.pro b/v4.pro
index c9d9b1a..fc26789 100644 (file)
--- a/v4.pro
+++ b/v4.pro
@@ -13,7 +13,8 @@ SOURCES += main.cpp \
     qmljs_runtime.cpp \
     qmljs_objects.cpp \
     qv4isel.cpp \
-    qv4syntaxchecker.cpp
+    qv4syntaxchecker.cpp \
+    qv4ecmaobjects.cpp
 
 HEADERS += \
     qv4codegen_p.h \
@@ -23,7 +24,9 @@ HEADERS += \
     qv4isel_p.h \
     x86-codegen.h \
     amd64-codegen.h \
-    qv4syntaxchecker_p.h 
+    qv4syntaxchecker_p.h \
+    qv4ecmaobjects_p.h
+