From 8762d4fdbb311c908d8d5d2b69afb3a58fa5dc89 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Wed, 23 Jan 2013 14:31:05 +0100 Subject: [PATCH] Implement JSON.stringify Change-Id: If1d170767c61c2435fe82f7e26e1ebf8de6327bb Reviewed-by: Simon Hausmann --- qv4jsonobject.cpp | 274 +++++++++++++++++++++++++++++++++++++++++++++++-- tests/TestExpectations | 49 --------- 2 files changed, 268 insertions(+), 55 deletions(-) diff --git a/qv4jsonobject.cpp b/qv4jsonobject.cpp index 6c0b02f..719cb56 100644 --- a/qv4jsonobject.cpp +++ b/qv4jsonobject.cpp @@ -40,10 +40,14 @@ ****************************************************************************/ #include #include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include + namespace QQmlJS { namespace VM { @@ -633,6 +637,230 @@ bool Parser::parseString(QString *string) } +struct Stringify +{ + ExecutionContext *ctx; + FunctionObject *replacerFunction; + QVector propertyList; + QString gap; + QString indent; + + QStack stack; + + Stringify(ExecutionContext *ctx) : ctx(ctx), replacerFunction(0) {} + + QString Str(const QString &key, Value value); + QString JA(ArrayObject *a); + QString JO(Object *o); + + QString makeMember(const QString &key, Value v); +}; + +static QString quote(const QString &str) +{ + QString product = "\""; + for (int i = 0; i < str.length(); ++i) { + QChar c = str.at(i); + switch (c.unicode()) { + case '"': + product += "\\\""; + break; + case '\\': + product += "\\\\"; + break; + case '\b': + product += "\\b"; + break; + case '\f': + product += "\\f"; + break; + case '\n': + product += "\\n"; + break; + case '\r': + product += "\\r"; + break; + case '\t': + product += "\\t"; + break; + default: + if (c.unicode() <= 0x1f) { + product += "\\u00"; + product += c.unicode() > 0xf ? '1' : '0'; + product += "0123456789abcdef"[c.unicode() & 0xf]; + } else { + product += c; + } + } + } + product += '"'; + return product; +} + +QString Stringify::Str(const QString &key, Value value) +{ + QString result; + + if (Object *o = value.asObject()) { + FunctionObject *toJSON = o->__get__(ctx, ctx->engine->identifier(QStringLiteral("toJSON"))).asFunctionObject(); + if (toJSON) { + Value arg = Value::fromString(ctx, key); + value = toJSON->call(ctx, value, &arg, 1); + } + } + + if (replacerFunction) { + Object *holder = ctx->engine->newObject(); + Value holderValue = Value::fromObject(holder); + holder->__put__(ctx, QString(), value); + Value args[2]; + args[0] = Value::fromString(ctx, key); + args[1] = value; + value = replacerFunction->call(ctx, holderValue, args, 2); + } + + if (Object *o = value.asObject()) { + if (NumberObject *n = o->asNumberObject()) + value = n->value; + else if (StringObject *so = o->asStringObject()) + value = so->value; + else if (BooleanObject *b =o->asBooleanObject()) + value = b->value; + } + + if (value.isNull()) + return QStringLiteral("null"); + if (value.isBoolean()) + return value.booleanValue() ? QStringLiteral("true") : QStringLiteral("false"); + if (value.isString()) + return quote(value.stringValue()->toQString()); + + if (value.isNumber()) { + double d = value.toNumber(ctx); + return std::isfinite(d) ? value.toString(ctx)->toQString() : QStringLiteral("null"); + } + + if (Object *o = value.asObject()) { + if (!o->asFunctionObject()) { + if (o->asArrayObject()) + return JA(static_cast(o)); + else + return JO(o); + } + } + + return QString(); +} + +QString Stringify::makeMember(const QString &key, Value v) +{ + QString strP = Str(key, v); + if (!strP.isEmpty()) { + QString member = quote(key) + ':'; + if (!gap.isEmpty()) + member += ' '; + member += strP; + return member; + } + return QString(); +} + +QString Stringify::JO(Object *o) +{ + if (stack.contains(o)) + ctx->throwTypeError(); + + QString result; + stack.push(o); + QString stepback = indent; + indent += gap; + + QStringList partial; + if (propertyList.isEmpty()) { + ObjectIterator it(ctx, o, ObjectIterator::EnumberableOnly); + + while (1) { + String *name; + uint index; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + Value v = o->getValueChecked(ctx, pd); + QString key; + if (name) + key = name->toQString(); + else + key = QString::number(index); + QString member = makeMember(key, v); + if (!member.isEmpty()) + partial += member; + } + } else { + for (int i = 0; i < propertyList.size(); ++i) { + bool exists; + Value v = o->__get__(ctx, propertyList.at(i), &exists); + if (!exists) + continue; + QString member = makeMember(propertyList.at(i)->toQString(), v); + if (!member.isEmpty()) + partial += member; + } + } + + if (partial.isEmpty()) { + result = QStringLiteral("{}"); + } else if (gap.isEmpty()) { + result = "{" + partial.join(",") + "}"; + } else { + QString separator = ",\n" + indent; + result = "{\n" + indent + partial.join(separator) + "\n" + stepback + "}"; + } + + indent = stepback; + stack.pop(); + return result; +} + +QString Stringify::JA(ArrayObject *a) +{ + if (stack.contains(a)) + ctx->throwTypeError(); + + QString result; + stack.push(a); + QString stepback = indent; + indent += gap; + + QStringList partial; + uint len = a->array.length(); + for (uint i = 0; i < len; ++i) { + bool exists; + Value v = a->__get__(ctx, i, &exists); + if (!exists) { + partial += QStringLiteral("null"); + continue; + } + QString strP = Str(QString::number(i), v); + if (!strP.isEmpty()) + partial += strP; + else + partial += QStringLiteral("null"); + } + + if (partial.isEmpty()) { + result = QStringLiteral("[]"); + } else if (gap.isEmpty()) { + result = "[" + partial.join(",") + "]"; + } else { + QString separator = ",\n" + indent; + result = "[\n" + indent + partial.join(separator) + "\n" + stepback + "]"; + } + + indent = stepback; + stack.pop(); + return result; +} + JsonObject::JsonObject(ExecutionContext *context) : Object() @@ -640,7 +868,7 @@ JsonObject::JsonObject(ExecutionContext *context) prototype = context->engine->objectPrototype; defineDefaultProperty(context, QStringLiteral("parse"), method_parse, 2); - defineDefaultProperty(context, QStringLiteral("stringify"), method_stringify); + defineDefaultProperty(context, QStringLiteral("stringify"), method_stringify, 3); } @@ -662,8 +890,42 @@ Value JsonObject::method_parse(ExecutionContext *ctx) Value JsonObject::method_stringify(ExecutionContext *ctx) { - Q_UNUSED(ctx); - assert(!"Not implemented"); + Stringify stringify(ctx); + + Object *o = ctx->argument(1).asObject(); + if (o) { + stringify.replacerFunction = o->asFunctionObject(); + if (o->isArray) { + for (uint i = 0; i < o->array.length(); ++i) { + Value v = o->__get__(ctx, i); + if (v.asNumberObject() || v.asStringObject() || v.isNumber()) + v = __qmljs_to_string(v, ctx); + if (v.isString()) { + String *s = v.stringValue(); + if (!stringify.propertyList.contains(s)) + stringify.propertyList.append(s); + } + } + } + } + + Value s = ctx->argument(2); + if (NumberObject *n = s.asNumberObject()) + s = n->value; + else if (StringObject *so = s.asStringObject()) + s = so->value; + + if (s.isNumber()) { + stringify.gap = QString(qMin(10, (int)s.toInteger(ctx)), ' '); + } else if (s.isString()) { + stringify.gap = s.stringValue()->toQString().left(10); + } + + + QString result = stringify.Str(QString(), ctx->argument(0)); + if (result.isEmpty()) + return Value::undefinedValue(); + return Value::fromString(ctx, result); } diff --git a/tests/TestExpectations b/tests/TestExpectations index 4740928..ad05f24 100644 --- a/tests/TestExpectations +++ b/tests/TestExpectations @@ -270,45 +270,6 @@ S12.8_A4_T1 failing S12.8_A4_T2 failing S12.8_A4_T3 failing S15.12.2_A1 failing -15.12.3-0-2 failing -15.12.3-11-1 failing -15.12.3-11-10 failing -15.12.3-11-11 failing -15.12.3-11-12 failing -15.12.3-11-13 failing -15.12.3-11-14 failing -15.12.3-11-15 failing -15.12.3-11-2 failing -15.12.3-11-26 failing -15.12.3-11-3 failing -15.12.3-11-4 failing -15.12.3-11-5 failing -15.12.3-11-6 failing -15.12.3-11-7 failing -15.12.3-11-8 failing -15.12.3-11-9 failing -15.12.3-4-1 failing -15.12.3-5-a-i-1 failing -15.12.3-5-b-i-1 failing -15.12.3-6-a-1 failing -15.12.3-6-a-2 failing -15.12.3-6-b-1 failing -15.12.3-6-b-2 failing -15.12.3-6-b-3 failing -15.12.3-6-b-4 failing -15.12.3-7-a-1 failing -15.12.3-8-a-1 failing -15.12.3-8-a-2 failing -15.12.3-8-a-3 failing -15.12.3-8-a-4 failing -15.12.3-8-a-5 failing -15.12.3_2-2-b-i-1 failing -15.12.3_2-2-b-i-2 failing -15.12.3_2-2-b-i-3 failing -15.12.3_2-3-a-1 failing -15.12.3_2-3-a-2 failing -15.12.3_2-3-a-3 failing -15.12.3_4-1-2 failing 15.2.3.6-4-360-3 failing 15.2.3.6-4-360-7 failing 15.4.4.14-9-a-10 failing @@ -487,16 +448,6 @@ S15.2.4.4_A14 failing S15.4.4.4_A1_T2 failing # Regressions due to Object/property refactoring -15.12.3-11-16 failing -15.12.3-11-17 failing -15.12.3-11-18 failing -15.12.3-11-19 failing -15.12.3-11-20 failing -15.12.3-11-21 failing -15.12.3-11-22 failing -15.12.3-11-23 failing -15.12.3-11-24 failing -15.12.3-11-25 failing 15.4.4.18-7-c-i-6 failing 15.4.4.19-8-c-i-6 failing 15.4.4.20-9-c-i-6 failing -- 2.7.4