From 5debabc410ff0b971225fea14a8845db63391904 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 7 Nov 2012 16:38:01 +0100 Subject: [PATCH] QmlJS: adding SimpleReader SimpleReader is a !simple! parser for json like qml files. SimpleReader only parses literal properties for e.g. configuration files. SimpleAbstractStreamReader allows event based parsing and SimpleReader stores the parsed data in a reference counted tree structure. Change-Id: I0f6422a97f5c356149c516f227f8bbd7b736a6d0 Reviewed-by: Joerg Bornemann --- src/libs/qmljs/qmljs-lib.pri | 6 +- src/libs/qmljs/qmljssimplereader.cpp | 327 ++++++++++++++++++ src/libs/qmljs/qmljssimplereader.h | 133 +++++++ .../qmljssimplereader/qmljssimplereader.pro | 12 + .../tst_qmljssimplereader.cpp | 180 ++++++++++ 5 files changed, 656 insertions(+), 2 deletions(-) create mode 100644 src/libs/qmljs/qmljssimplereader.cpp create mode 100644 src/libs/qmljs/qmljssimplereader.h create mode 100644 tests/auto/qml/qmljssimplereader/qmljssimplereader.pro create mode 100644 tests/auto/qml/qmljssimplereader/tst_qmljssimplereader.cpp diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index 6f4f72db853..3d335f62f41 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -36,7 +36,8 @@ HEADERS += \ $$PWD/jsoncheck.h \ $$PWD/consolemanagerinterface.h \ $$PWD/consoleitem.h \ - $$PWD/iscriptevaluator.h + $$PWD/iscriptevaluator.h \ + $$PWD/qmljssimplereader.h SOURCES += \ $$PWD/qmljsbind.cpp \ @@ -63,7 +64,8 @@ SOURCES += \ $$PWD/qmljsstaticanalysismessage.cpp \ $$PWD/jsoncheck.cpp \ $$PWD/consolemanagerinterface.cpp \ - $$PWD/consoleitem.cpp + $$PWD/consoleitem.cpp \ + $$PWD/qmljssimplereader.cpp RESOURCES += \ $$PWD/qmljs.qrc diff --git a/src/libs/qmljs/qmljssimplereader.cpp b/src/libs/qmljs/qmljssimplereader.cpp new file mode 100644 index 00000000000..b87b078e351 --- /dev/null +++ b/src/libs/qmljs/qmljssimplereader.cpp @@ -0,0 +1,327 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmljssimplereader.h" + +#include "parser/qmljsparser_p.h" +#include "parser/qmljslexer_p.h" +#include "parser/qmljsengine_p.h" +#include "parser/qmljsast_p.h" +#include "parser/qmljsastvisitor_p.h" +#include + +#include "qmljsbind.h" +#include "qmljsinterpreter.h" +#include "qmljsutils.h" + +#include + +enum { + debug = false +}; + + +using namespace QmlJS; + +QVariant SimpleReaderNode::property(const QString &name) const +{ + return m_properties.value(name); +} + +QStringList SimpleReaderNode::propertyNames() const +{ + return m_properties.keys(); +} + +SimpleReaderNode::PropertyHash SimpleReaderNode::properties() const +{ + return m_properties; +} + +bool SimpleReaderNode::isRoot() const +{ + return m_parentNode.isNull(); +} + +bool SimpleReaderNode::isValid() const +{ + return !m_name.isEmpty(); +} + +SimpleReaderNode::Ptr SimpleReaderNode::invalidNode() +{ + return Ptr(new SimpleReaderNode); +} + +SimpleReaderNode::WeakPtr SimpleReaderNode::parent() const +{ + return m_parentNode; +} + +QString SimpleReaderNode::name() const +{ + return m_name; +} + +SimpleReaderNode::SimpleReaderNode() +{ +} + +SimpleReaderNode::SimpleReaderNode(const QString &name, WeakPtr parent) + : m_name(name), m_parentNode(parent) +{ +} + +SimpleReaderNode::Ptr SimpleReaderNode::create(const QString &name, WeakPtr parent) +{ + Ptr newNode(new SimpleReaderNode(name, parent)); + newNode->m_weakThis = newNode; + if (parent) + parent.data()->m_children.append(newNode); + return newNode; +} + +const SimpleReaderNode::List SimpleReaderNode::children() const +{ + return m_children; +} + +void SimpleReaderNode::setProperty(const QString &name, const QVariant &value) +{ + m_properties.insert(name, value); +} + +SimpleAbstractStreamReader::SimpleAbstractStreamReader() +{ +} + +bool SimpleAbstractStreamReader::readFile(const QString &fileName) +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + QByteArray source = file.readAll(); + file.close(); + return readFromSource(source); + } + addError(tr("Cannot find file %1").arg(fileName)); + return false; +} + +bool SimpleAbstractStreamReader::readFromSource(const QString &source) +{ + Engine engine; + Lexer lexer(&engine); + Parser parser(&engine); + + lexer.setCode(source, /*line = */ 1, /*qmlMode = */true); + + if (!parser.parse()) { + QString errorMessage = QString("%1:%2: %3").arg( + QString::number(parser.errorLineNumber()), + QString::number(parser.errorColumnNumber()), + parser.errorMessage()); + addError(errorMessage); + return false; + } + return readDocument(parser.ast()); +} + +QStringList SimpleAbstractStreamReader::errors() const +{ + return m_errors; +} + +void SimpleAbstractStreamReader::addError(const QString &error, const AST::SourceLocation &sourceLocation) +{ + m_errors << QString("%1:%2: %3\n").arg( + QString::number(sourceLocation.startLine), + QString::number(sourceLocation.startColumn), + error); +} + +AST::SourceLocation SimpleAbstractStreamReader::currentSourceLocation() const +{ + return m_currentSourceLocation; +} + +bool SimpleAbstractStreamReader::readDocument(AST::UiProgram *ast) +{ + if (!ast) { + addError(tr("Could not parse document")); + false; + } + + AST::UiObjectDefinition *uiObjectDefinition = AST::cast(ast->members->member); + if (!uiObjectDefinition) { + addError(tr("Expected document to contain a single object definition")); + false; + } + readChild(uiObjectDefinition); + + return errors().isEmpty(); +} + +void SimpleAbstractStreamReader::readChildren(AST::UiObjectDefinition *uiObjectDefinition) +{ + Q_ASSERT(uiObjectDefinition); + + for (AST::UiObjectMemberList *it = uiObjectDefinition->initializer->members; it; it = it->next) { + AST::UiObjectMember *member = it->member; + AST::UiObjectDefinition *uiObjectDefinition = AST::cast(member); + if (uiObjectDefinition) + readChild(uiObjectDefinition); + } +} + +void SimpleAbstractStreamReader::readChild(AST::UiObjectDefinition *uiObjectDefinition) +{ + Q_ASSERT(uiObjectDefinition); + + setSourceLocation(uiObjectDefinition->firstSourceLocation()); + + elementStart(toString(uiObjectDefinition->qualifiedTypeNameId)); + + readProperties(uiObjectDefinition); + readChildren(uiObjectDefinition); + + elementEnd(); +} + +void SimpleAbstractStreamReader::readProperties(AST::UiObjectDefinition *uiObjectDefinition) +{ + Q_ASSERT(uiObjectDefinition); + + for (AST::UiObjectMemberList *it = uiObjectDefinition->initializer->members; it; it = it->next) { + AST::UiObjectMember *member = it->member; + AST::UiScriptBinding *scriptBinding = AST::cast(member); + if (scriptBinding) + readProperty(scriptBinding); + } +} + +void SimpleAbstractStreamReader::readProperty(AST::UiScriptBinding *uiScriptBinding) +{ + Q_ASSERT(uiScriptBinding); + + setSourceLocation(uiScriptBinding->firstSourceLocation()); + + const QString name = toString(uiScriptBinding->qualifiedId); + const QVariant value = parseProperty(uiScriptBinding); + + propertyDefinition(name, value); +} + +QVariant SimpleAbstractStreamReader::parseProperty(AST::UiScriptBinding *uiScriptBinding) +{ + Q_ASSERT(uiScriptBinding); + + AST::ExpressionStatement *expStmt = AST::cast(uiScriptBinding->statement); + if (!expStmt) { + addError(tr("Expected expression statement after colon"), uiScriptBinding->statement->firstSourceLocation()); + return QVariant(); + } + + AST::StringLiteral *stringLiteral = AST::cast(expStmt->expression); + if (stringLiteral) + return stringLiteral->value.toString(); + + AST::TrueLiteral *trueLiteral = AST::cast(expStmt->expression); + if (trueLiteral) + return true; + + AST::FalseLiteral *falseLiteral = AST::cast(expStmt->expression); + if (falseLiteral) + return false; + + AST::NumericLiteral *numericLiteral = AST::cast(expStmt->expression); + if (numericLiteral) + return numericLiteral->value; + + addError(tr("Expected expression statement to be a literal"), uiScriptBinding->statement->firstSourceLocation()); + return QVariant(); +} + +void SimpleAbstractStreamReader::setSourceLocation(const AST::SourceLocation &sourceLocation) +{ + m_currentSourceLocation = sourceLocation; +} + +SimpleReader::SimpleReader() +{ +} + +SimpleReaderNode::Ptr SimpleReader::readFile(const QString &fileName) +{ + SimpleAbstractStreamReader::readFile(fileName); + return m_rootNode; +} + +SimpleReaderNode::Ptr SimpleReader::readFromSource(const QString &source) +{ + SimpleAbstractStreamReader::readFromSource(source); + return m_rootNode; +} + +void SimpleReader::elementStart(const QString &name) +{ + if (debug) + qDebug() << "SimpleReader::elementStart()" << name; + + SimpleReaderNode::Ptr newNode = SimpleReaderNode::create(name, m_currentNode); + + if (newNode->isRoot()) + m_rootNode = newNode; + + Q_ASSERT(newNode->isValid()); + + m_currentNode = newNode; +} + +void SimpleReader::elementEnd() +{ + Q_ASSERT(m_currentNode); + + if (debug) + qDebug() << "SimpleReader::elementEnd()" << m_currentNode.data()->name(); + + m_currentNode = m_currentNode.data()->parent(); +} + +void SimpleReader::propertyDefinition(const QString &name, const QVariant &value) +{ + Q_ASSERT(m_currentNode); + + if (debug) + qDebug() << "SimpleReader::propertyDefinition()" << m_currentNode.data()->name() << name << value; + + if (m_currentNode.data()->propertyNames().contains(name)) + addError(tr("Property is defined twice"), currentSourceLocation()); + + m_currentNode.data()->setProperty(name, value); +} diff --git a/src/libs/qmljs/qmljssimplereader.h b/src/libs/qmljs/qmljssimplereader.h new file mode 100644 index 00000000000..dd543354815 --- /dev/null +++ b/src/libs/qmljs/qmljssimplereader.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLJS_SIMPLEREADER_H +#define QMLJS_SIMPLEREADER_H + +#include +#include + +#include +#include +#include +#include +#include + +// for Q_DECLARE_TR_FUNCTIONS +#include + +namespace QmlJS { + +class QMLJS_EXPORT SimpleReaderNode +{ +public: + typedef QSharedPointer Ptr; + typedef QWeakPointer WeakPtr; + typedef QHash PropertyHash; + typedef QList List; + + QVariant property(const QString &name) const; + QStringList propertyNames() const; + PropertyHash properties() const; + bool isRoot() const; + bool isValid() const; + static Ptr invalidNode(); + WeakPtr parent() const; + QString name() const; + const List children() const; + +protected: + SimpleReaderNode(); + SimpleReaderNode(const QString &name, WeakPtr parent); + static Ptr create(const QString &name, WeakPtr parent); + void setProperty(const QString &name, const QVariant &value); + +private: + const QString m_name; + PropertyHash m_properties; + const WeakPtr m_parentNode; + List m_children; + WeakPtr m_weakThis; + + friend class SimpleReader; +}; + +class QMLJS_EXPORT SimpleAbstractStreamReader +{ + Q_DECLARE_TR_FUNCTIONS(QmlJS::SimpleAbstractStreamReader) + +public: + SimpleAbstractStreamReader(); + bool readFile(const QString &fileName); + bool readFromSource(const QString &source); + QStringList errors() const; + +protected: + void addError(const QString &error, const AST::SourceLocation &sourceLocation = AST::SourceLocation()); + AST::SourceLocation currentSourceLocation() const; + + virtual void elementStart(const QString &name) = 0; + virtual void elementEnd() = 0; + virtual void propertyDefinition(const QString &name, const QVariant &value) = 0; + +private: + bool readDocument(AST::UiProgram *ast); + void readChildren(AST::UiObjectDefinition *ast); + void readChild(AST::UiObjectDefinition *uiObjectDefinition); + void readProperties(AST::UiObjectDefinition *ast); + void readProperty(AST::UiScriptBinding *uiScriptBinding); + QVariant parseProperty(AST::UiScriptBinding *ast); + void setSourceLocation(const AST::SourceLocation &sourceLocation); + + QStringList m_errors; + AST::SourceLocation m_currentSourceLocation; +}; + +class QMLJS_EXPORT SimpleReader: public SimpleAbstractStreamReader +{ + Q_DECLARE_TR_FUNCTIONS(QmlJS::SimpleReader) + +public: + SimpleReader(); + SimpleReaderNode::Ptr readFile(const QString &fileName); + SimpleReaderNode::Ptr readFromSource(const QString &source); + +protected: + virtual void elementStart(const QString &name); + virtual void elementEnd(); + virtual void propertyDefinition(const QString &name, const QVariant &value); + +private: + SimpleReaderNode::Ptr m_rootNode; + SimpleReaderNode::WeakPtr m_currentNode; +}; + +} // namespace QmlJS + +#endif // QMLJS_SIMPLEREADER_H diff --git a/tests/auto/qml/qmljssimplereader/qmljssimplereader.pro b/tests/auto/qml/qmljssimplereader/qmljssimplereader.pro new file mode 100644 index 00000000000..20722acddf4 --- /dev/null +++ b/tests/auto/qml/qmljssimplereader/qmljssimplereader.pro @@ -0,0 +1,12 @@ +include(../../qttest.pri) + +DEFINES+=QTCREATORDIR=\\\"$$IDE_SOURCE_TREE\\\" +DEFINES+=TESTSRCDIR=\\\"$$PWD\\\" + +include($$IDE_SOURCE_TREE/src/libs/utils/utils.pri) +include($$IDE_SOURCE_TREE/src/libs/qmljs/qmljs.pri) + +TARGET = tst_qmljssimplereader + +SOURCES += \ + tst_qmljssimplereader.cpp diff --git a/tests/auto/qml/qmljssimplereader/tst_qmljssimplereader.cpp b/tests/auto/qml/qmljssimplereader/tst_qmljssimplereader.cpp new file mode 100644 index 00000000000..13030e0d7c4 --- /dev/null +++ b/tests/auto/qml/qmljssimplereader/tst_qmljssimplereader.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include + +#include +#include + +using namespace QmlJS; + +class tst_SimpleReader : public QObject +{ + Q_OBJECT +public: + tst_SimpleReader(); + +private slots: + void testWellFormed(); + void testIllFormed01(); + void testIllFormed02(); +}; + +tst_SimpleReader::tst_SimpleReader() +{ +} + +void tst_SimpleReader::testWellFormed() +{ + char source[] = "RootNode {\n" + " ChildNode {\n" + " property01: 10\n" + " }\n" + " ChildNode {\n" + " propertyString: \"str\"\n" + " InnerChild {\n" + " test: \"test\"\n" + " }\n" + " }\n" + " propertyBlah: false\n" + "}\n"; + + QmlJS::SimpleReaderNode::WeakPtr weak01; + QmlJS::SimpleReaderNode::WeakPtr weak02; + { + QmlJS::SimpleReader reader; + QmlJS::SimpleReaderNode::Ptr rootNode = reader.readFromSource(source); + QVERIFY(reader.errors().isEmpty()); + QVERIFY(rootNode); + QVERIFY(rootNode->isValid()); + QCOMPARE(rootNode->name(), QLatin1String("RootNode")); + + QCOMPARE(rootNode->children().count(), 2); + QCOMPARE(rootNode->properties().count(), 1); + + QVERIFY(rootNode->properties().contains("propertyBlah")); + QCOMPARE(rootNode->property("property01").toBool(), false); + + QVERIFY(rootNode->children().first()->isValid()); + QVERIFY(!rootNode->children().first()->isRoot()); + + QVERIFY(rootNode->children().first()->properties().contains("property01")); + QCOMPARE(rootNode->children().first()->property("property01").toInt(), 10); + + QmlJS::SimpleReaderNode::Ptr secondChild = rootNode->children().at(1); + + QVERIFY(secondChild); + QVERIFY(secondChild->isValid()); + QVERIFY(!secondChild->isRoot()); + QCOMPARE(secondChild->name(), QLatin1String("ChildNode")); + + QVERIFY(secondChild->properties().contains("propertyString")); + QCOMPARE(secondChild->property("propertyString").toString(), QLatin1String("str")); + + QCOMPARE(secondChild->children().count(), 1); + + QmlJS::SimpleReaderNode::Ptr innerChild = secondChild->children().first(); + + QVERIFY(innerChild); + QVERIFY(innerChild->isValid()); + QVERIFY(!innerChild->isRoot()); + QCOMPARE(innerChild->name(), QLatin1String("InnerChild")); + + QVERIFY(innerChild->properties().contains("test")); + QCOMPARE(innerChild->property("test").toString(), QLatin1String("test")); + + weak01 = rootNode; + weak02 = secondChild; + } + + QVERIFY(!weak01); + QVERIFY(!weak02); +} + +void tst_SimpleReader::testIllFormed01() +{ + char source[] = "RootNode {\n" + " ChildNode {\n" + " property01: 10\n" + " }\n" + " ChildNode {\n" + " propertyString: \"str\"\n" + " InnerChild \n" //missing { + " test: \"test\"\n" + " }\n" + " }\n" + " propertyBlah: false\n" + "}\n"; + QmlJS::SimpleReader reader; + QmlJS::SimpleReaderNode::Ptr rootNode = reader.readFromSource(source); + + QVERIFY(!rootNode); + QVERIFY(!reader.errors().empty()); +} + +void tst_SimpleReader::testIllFormed02() +{ + char source[] = "RootNode {\n" + " ChildNode {\n" + " property01: 10\n" + " property01: 20\n" + " }\n" + " ChildNode {\n" + " propertyString: \"str\"\n" + " InnerChild {\n" + " test: \"test\"\n" + " test: \"test2\"\n" + " }\n" + " }\n" + "}\n"; + + QmlJS::SimpleReader reader; + QmlJS::SimpleReaderNode::Ptr rootNode = reader.readFromSource(source); + + QVERIFY(rootNode); + QVERIFY(rootNode->isValid()); + QVERIFY(rootNode->isRoot()); + + QVERIFY(!reader.errors().empty()); + QCOMPARE(reader.errors().count(), 2); + + QmlJS::SimpleReaderNode::Ptr firstChild = rootNode->children().at(0); + + QVERIFY(firstChild); + QVERIFY(firstChild->isValid()); + QVERIFY(!firstChild->isRoot()); + + QCOMPARE(firstChild->properties().count(), 1); + QVERIFY(firstChild->properties().contains("property01")); + QCOMPARE(firstChild->property("property01").toString(), QLatin1String("20")); +} + +QTEST_MAIN(tst_SimpleReader); + +#include "tst_qmljssimplereader.moc"