From 82a8e9bb86815ba3ad3665082ac6e55d41721187 Mon Sep 17 00:00:00 2001 From: Fawzi Mohamed Date: Mon, 3 Dec 2012 19:17:09 +0100 Subject: [PATCH] qmljs: added persistent trie Add a presistent trie to store imports, and provide a better (IMHO) completion algorithm. The trie is quite generic and it might be worth while to move it to utils. Change-Id: I4081346af6215b1ee8ff14bd063c2a021d7c8218 Reviewed-by: Thomas Hartmann --- src/libs/qmljs/persistenttrie.cpp | 631 ++++++++++++++++++ src/libs/qmljs/persistenttrie.h | 127 ++++ src/libs/qmljs/qmljs-lib.pri | 6 +- src/libs/qmljs/qmljs.qbs | 2 + tests/auto/qml/persistenttrie/completion.data | 10 + tests/auto/qml/persistenttrie/intersect.data | 81 +++ tests/auto/qml/persistenttrie/listAll.data | 26 + tests/auto/qml/persistenttrie/merge.data | 81 +++ .../qml/persistenttrie/persistenttrie.pro | 25 + .../auto/qml/persistenttrie/tst_testtrie.cpp | 374 +++++++++++ tests/auto/qml/persistenttrie/tst_testtrie.h | 56 ++ 11 files changed, 1417 insertions(+), 2 deletions(-) create mode 100644 src/libs/qmljs/persistenttrie.cpp create mode 100644 src/libs/qmljs/persistenttrie.h create mode 100644 tests/auto/qml/persistenttrie/completion.data create mode 100644 tests/auto/qml/persistenttrie/intersect.data create mode 100644 tests/auto/qml/persistenttrie/listAll.data create mode 100644 tests/auto/qml/persistenttrie/merge.data create mode 100644 tests/auto/qml/persistenttrie/persistenttrie.pro create mode 100644 tests/auto/qml/persistenttrie/tst_testtrie.cpp create mode 100644 tests/auto/qml/persistenttrie/tst_testtrie.h diff --git a/src/libs/qmljs/persistenttrie.cpp b/src/libs/qmljs/persistenttrie.cpp new file mode 100644 index 00000000000..271fa69c785 --- /dev/null +++ b/src/libs/qmljs/persistenttrie.cpp @@ -0,0 +1,631 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +/*! + \class QmlJS::PersistentTrie::Trie + + \brief Implements a trie that is persistent (not on disk but in memory). + + This means that several versions can coexist, as adding an element + is non destructive, and as much as possible is shared. + + The trie is only *partially* ordered, it preserves the order + of what was inserted as much as possible. + This makes some operations a bit slower, but is considered + a feature. + This means the order in which you insert the elements matters. + + An important use case for this is completions, and several + strategies are available. + Results order can be improved using the matching strength + + All const operations are threadsafe, and copy is cheap (only a + QSharedPointer copy). + + Assigning a shared pointer is *not* threadsafe, so updating the + head is *not* thread safe, and should be done only on a local + instance (shared pointer used only from a single thread), or + protected with locks. + + This is a two level implementation, based on a fully functional + implementation (PersistentTrie::Trie), which could be private + but was left public because deemed useful. + + Would gain from some memory optimization, or direct string implementation. + */ + +#include "persistenttrie.h" + +#include +#include +#include +#include + +#include +#include + +namespace QmlJS { +namespace PersistentTrie { + +TrieNode::TrieNode(const QString &pre, QList post) : + prefix(pre), postfixes(post) {} + +TrieNode::TrieNode(const TrieNode &o) : prefix(o.prefix), + postfixes(o.postfixes) {} + +TrieNode::Ptr TrieNode::create(const QString &pre, QList post) +{ + return TrieNode::Ptr(new TrieNode(pre,post)); +} + +void TrieNode::complete(QStringList &res, const TrieNode::Ptr &trie, + const QString &value, const QString &base, LookupFlags flags) +{ + // one could also modify this and make it return a TrieNode, instead of a QStringList + if (trie.isNull()) + return; + QString::const_iterator i = trie->prefix.constBegin(), iEnd = trie->prefix.constEnd(); + QString::const_iterator j = value.constBegin(), jEnd = value.constEnd(); + while (i != iEnd && j != jEnd) { + if (i->isSpace()) { + if (! j->isSpace() && (flags & SkipSpaces) == 0) + return; + while (j != jEnd && j->isSpace()) { + ++j; + } ; + do { + ++i; + } while (i != iEnd && i->isSpace()); + } else { + if (*i != *j && ((flags & CaseInsensitive) == 0 || i->toLower() != j->toLower())) { + if ((flags & SkipChars) != 0) + --j; + else + return; + } + ++i; + ++j; + } + } + QString base2 = base + trie->prefix; + if (j == jEnd) { + if (trie->postfixes.isEmpty()) + res.append(base2); + if (trie->postfixes.size() == 1) { + complete(res, trie->postfixes[0],QString(), base2, flags); + return; + } + foreach (TrieNode::Ptr t, trie->postfixes) { + if ((flags & Partial) != 0) + res.append(base2 + t->prefix); + else + complete(res, t, QString(), base2, flags); + } + return; + } + foreach (const TrieNode::Ptr v, trie->postfixes) { + QString::const_iterator vi = v->prefix.constBegin(), vEnd = v->prefix.constEnd(); + if (vi != vEnd && (*vi == *j || ((flags & CaseInsensitive) != 0 + && vi->toLower() == j->toLower()) || ((flags & SkipChars) != 0))) + complete(res, v, value.right(jEnd-j), base2, flags); + } +} + +TrieNode::Ptr TrieNode::insertF(const TrieNode::Ptr &trie, + const QString &value) +{ + if (trie.isNull()) { + if (value.isEmpty()) + return trie; + return TrieNode::create(value); + } + typedef TrieNode T; + QString::const_iterator i = trie->prefix.constBegin(), iEnd = trie->prefix.constEnd(); + QString::const_iterator j = value.constBegin(), jEnd = value.constEnd(); + while (i != iEnd && j != jEnd) { + if (i->isSpace()) { + if (! j->isSpace()) + break; + do { + ++j; + } while (j != jEnd && j->isSpace()); + do { + ++i; + } while (i != iEnd && i->isSpace()); + } else { + if (*i != *j) + break; + ++i; + ++j; + } + } + if (i == iEnd) { + if (j == jEnd) + return trie; + int tSize = trie->postfixes.size(); + for (int i=0; i < tSize; ++i) { + const T::Ptr &v=trie->postfixes[i]; + QString::const_iterator vi = v->prefix.constBegin(), vEnd = v->prefix.constEnd(); + if (vi != vEnd && *vi == *j) { + T::Ptr res = insertF(v, value.right(jEnd-j)); + if (res != v) { + QList post = trie->postfixes; + post.replace(i, res); + return T::create(trie->prefix,post); + } else { + return trie; + } + } + } + QList post = trie->postfixes; + if (post.isEmpty()) + post.append(T::create()); + post.append(T::create(value.right(jEnd - j))); + return T::create(trie->prefix, post); + } else { + T::Ptr newTrie1 = T::create(trie->prefix.right(iEnd - i), trie->postfixes); + T::Ptr newTrie2 = T::create(value.right(jEnd - j)); + return T::create(trie->prefix.left(i - trie->prefix.constBegin()), + QList() << newTrie1 << newTrie2); + } +} + +bool TrieNode::contains(const TrieNode::Ptr &trie, + const QString &value, LookupFlags flags) +{ + if (trie.isNull()) + return false; + QString::const_iterator i = trie->prefix.constBegin(), iEnd = trie->prefix.constEnd(); + QString::const_iterator j = value.constBegin(), jEnd = value.constEnd(); + while (i != iEnd && j != jEnd) { + if (i->isSpace()) { + if (! j->isSpace()) + return false; + do { + ++j; + } while (j != jEnd && j->isSpace()); + do { + ++i; + } while (i != iEnd && i->isSpace()); + } else { + if (*i != *j && ((flags & CaseInsensitive) == 0 || i->toLower() != j->toLower())) + return false; + ++i; + ++j; + } + } + if (j == jEnd) { + if ((flags & Partial) != 0) + return true; + if (i == iEnd) { + foreach (const TrieNode::Ptr t, trie->postfixes) + if (t->prefix.isEmpty()) + return true; + return trie->postfixes.isEmpty(); + } + return false; + } + if (i != iEnd) + return false; + bool res = false; + foreach (const TrieNode::Ptr v, trie->postfixes) { + QString::const_iterator vi = v->prefix.constBegin(), vEnd = v->prefix.constEnd(); + if (vi != vEnd && (*vi == *j || ((flags & CaseInsensitive) != 0 + && vi->toLower() == j->toLower()))) + res = res || contains(v, value.right(jEnd-j), flags); + } + return res; +} + +namespace { +class Appender { +public: + Appender() {} + QStringList res; + void operator()(const QString &s) { + res.append(s); + } +}; +} + +QStringList TrieNode::stringList(const TrieNode::Ptr &trie) +{ + Appender a; + enumerateTrieNode(trie, a, QString()); + return a.res; +} + +std::pair TrieNode::intersectF( + const TrieNode::Ptr &v1, const TrieNode::Ptr &v2, int index1) +{ + typedef TrieNode::Ptr P; + typedef QMap::const_iterator MapIterator; + if (v1.isNull() || v2.isNull()) + return std::make_pair(P(0), ((v1.isNull()) ? 1 : 0) | ((v2.isNull()) ? 2 : 0)); + QString::const_iterator i = v1->prefix.constBegin()+index1, iEnd = v1->prefix.constEnd(); + QString::const_iterator j = v2->prefix.constBegin(), jEnd = v2->prefix.constEnd(); + while (i != iEnd && j != jEnd) { + if (i->isSpace()) { + if (! j->isSpace()) + break; + do { + ++j; + } while (j != jEnd && j->isSpace()); + do { + ++i; + } while (i != iEnd && i->isSpace()); + } else { + if (*i != *j) + break; + ++i; + ++j; + } + } + if (i == iEnd) { + if (j == jEnd) { + if (v1->postfixes.isEmpty() || v2->postfixes.isEmpty()) { + if (v1->postfixes.isEmpty() && v2->postfixes.isEmpty()) + return std::make_pair(v1, 3); + foreach (P t1, v1->postfixes) + if (t1->prefix.isEmpty()) { + if (index1 == 0) + return std::make_pair(v2, 2); + else + return std::make_pair(TrieNode::create( + v1->prefix.left(index1).append(v2->prefix), v2->postfixes),0); + } + foreach (P t2, v2->postfixes) + if (t2->prefix.isEmpty()) + return std::make_pair(v1,1); + return std::make_pair(P(0), 0); + } + QMap p1, p2; + QList

p3; + int ii = 0; + foreach (P t1, v1->postfixes) + p1[t1->prefix] = ii++; + ii = 0; + foreach (P t2, v2->postfixes) + p2[t2->prefix] = ii++; + MapIterator p1Ptr = p1.constBegin(), p2Ptr = p2.constBegin(), + p1End = p1.constEnd(), p2End = p2.constEnd(); + int sameV1V2 = 3; + while (p1Ptr != p1End && p2Ptr != p2End) { + if (p1Ptr.key().isEmpty()) { + if (p2Ptr.key().isEmpty()) { + if (sameV1V2 == 0) + p3.append(v1->postfixes.at(p1Ptr.value())); + ++p1Ptr; + ++p2Ptr; + } else { + if (sameV1V2 == 1) + for (MapIterator p1I = p1.constBegin();p1I != p1Ptr; ++p1I) + p3.append(v1->postfixes.at(p1I.value())); + ++p1Ptr; + sameV1V2 &= 2; + } + } else if (p2Ptr.key().isEmpty()) { + if (sameV1V2 == 2) + for (MapIterator p2I = p2.constBegin(); p2I != p2Ptr; ++p2I) + p3.append(v2->postfixes.at(p2I.value())); + ++p2Ptr; + sameV1V2 &= 1; + } else { + QChar c1 = p1Ptr.key().at(0); + QChar c2 = p2Ptr.key().at(0); + if (c1 < c2) { + if (sameV1V2 == 1) + for (MapIterator p1I = p1.constBegin(); p1I != p1Ptr; ++p1I) + p3.append(v1->postfixes.at(p1I.value())); + ++p1Ptr; + sameV1V2 &= 2; + } else if (c1 > c2) { + if (sameV1V2 == 2) + for (MapIterator p2I = p2.constBegin(); p2I != p2Ptr; ++p2I) + p3.append(v2->postfixes.at(p2I. value())); + ++p2Ptr; + sameV1V2 &= 1; + } else { + std::pair res = intersectF(v1->postfixes.at(p1Ptr.value()), + v2->postfixes.at(p2Ptr.value())); + if (sameV1V2 !=0 && (sameV1V2 & res.second) == 0) { + if ((sameV1V2 & 1) == 1) + for (MapIterator p1I = p1.constBegin(); p1I != p1Ptr; ++p1I) + p3.append(v1->postfixes.at(p1I.value())); + if (sameV1V2 == 2) + for (MapIterator p2I = p2.constBegin(); p2I != p2Ptr; ++p2I) + p3.append(v2->postfixes.at(p2I.value())); + } + sameV1V2 &= res.second; + if (sameV1V2 == 0 && !res.first.isNull()) + p3.append(res.first); + ++p1Ptr; + ++p2Ptr; + } + } + } + if (p1Ptr != p1End) { + if (sameV1V2 == 1) + for (MapIterator p1I = p1.constBegin(); p1I != p1Ptr; ++p1I) + p3.append(v1->postfixes.at(p1I.value())); + sameV1V2 &= 2; + } else if (p2Ptr != p2End) { + if (sameV1V2 == 2) { + for (MapIterator p2I = p2.constBegin(); p2I != p2Ptr; ++p2I) + p3.append(v2->postfixes.at(p2I. value())); + } + sameV1V2 &= 1; + } + switch (sameV1V2) { + case 0: + if (p3.isEmpty()) + return std::make_pair(P(0),0); + else + return std::make_pair(TrieNode::create(v1->prefix,p3),0); + case 2: + if (index1 == 0) + return std::make_pair(v2,2); + else + return std::make_pair(TrieNode::create( + v1->prefix.left(index1).append(v2->prefix), v2->postfixes), 0); + default: + return std::make_pair(v1,sameV1V2); + } + } + // i == iEnd && j != jEnd + foreach (const P &t1, v1->postfixes) + if ((!t1->prefix.isEmpty()) && t1->prefix.at(0) == *j) { + std::pair res = intersectF(v2,t1,j-v2->prefix.constBegin()); + if (index1 == 0) + return std::make_pair(res.first, (((res.second & 1)==1) ? 2 : 0)); + else + return std::make_pair(TrieNode::create( + v1->prefix.left(index1).append(res.first->prefix), + res.first->postfixes), 0); + } + return std::make_pair(P(0), 0); + } else { + // i != iEnd && j == jEnd + foreach (P t2, v2->postfixes) + if (!t2->prefix.isEmpty() && t2->prefix.at(0) == *i) { + std::pair res = intersectF(v1,t2,i-v1->prefix.constBegin()); + return std::make_pair(res.first, (res.second & 1)); + } + return std::make_pair(P(0), 0); + } +} + +namespace { +class InplaceTrie{ +public: + TrieNode::Ptr trie; + + void operator()(QString s){ + trie = TrieNode::insertF(trie,s); + } +}; +} + +std::pair TrieNode::mergeF( + const TrieNode::Ptr &v1, const TrieNode::Ptr &v2) +{ + //could be much more efficient if implemented directly on the trie like intersectF + InplaceTrie t; + t.trie = v1; + enumerateTrieNode(v2, t, QString()); + return std::make_pair(t.trie, ((t.trie == v1) ? 1 : 0)); +} + +QDebug &TrieNode::printStrings(QDebug &dbg, const TrieNode::Ptr &trie) +{ + if (trie.isNull()) + return dbg << "Trie{*NULL*}"; + dbg<<"Trie{ contents:["; + bool first = true; + foreach (const QString &s, stringList(trie)) { + if (!first) + dbg << ","; + else + first = false; + dbg << s; + } + dbg << "]}"; + return dbg; +} + +QDebug &TrieNode::describe(QDebug &dbg, const TrieNode::Ptr &trie, + int indent = 0) +{ + dbg.space(); + dbg.nospace(); + if (trie.isNull()) { + dbg << "NULL"; + return dbg; + } + dbg << trie->prefix; + int newIndent = indent + trie->prefix.size() + 3; + bool newLine = false; + foreach (TrieNode::Ptr sub, trie->postfixes) { + if (newLine) { + dbg << "\n"; + for (int i=0; i < newIndent; ++i) + dbg << " "; + } else { + newLine = true; + } + describe(dbg, sub, newIndent); + } + return dbg; +} + +QDebug &operator<<(QDebug &dbg, const TrieNode::Ptr &trie) +{ + dbg.nospace()<<"Trie{\n"; + TrieNode::describe(dbg,trie,0); + dbg << "}"; + return dbg.space(); +} +QDebug &operator<<(QDebug &dbg, const Trie &trie) +{ + dbg.nospace()<<"Trie{\n"; + TrieNode::describe(dbg,trie.trie,0); + dbg << "}"; + return dbg.space(); +} +Trie::Trie() {} +Trie::Trie(const TrieNode::Ptr &trie) : trie(trie) {} +Trie::Trie(const Trie &o) : trie(o.trie){} + +QStringList Trie::complete(const QString &root, const QString &base, + LookupFlags flags) const +{ + QStringList res; + TrieNode::complete(res, trie, root, base, flags); + return res; +} + +bool Trie::contains(const QString &value, LookupFlags flags) const +{ + return TrieNode::contains(trie, value, flags); +} + +QStringList Trie::stringList() const +{ + return TrieNode::stringList(trie); +} + +/*! + \brief inserts into the current trie. + + Non thread safe, only use this on an instance that is used only + in a single theread, or that is protected by locks. + */ +void Trie::insert(const QString &value) +{ + trie = TrieNode::insertF(trie, value); +} + +/*! + \brief intesects into the current trie. + + Non thread safe, only use this on an instance that is used only + in a single theread, or that is protected by locks. + */ +void Trie::intersect(const Trie &v) +{ + trie = TrieNode::intersectF(trie, v.trie).first; +} + +/*! + \brief merges the given trie into the current one. + + Non thread safe, only use this on an instance that is used only + in a single theread, or that is protected by locks. + */ +void Trie::merge(const Trie &v) +{ + trie = TrieNode::mergeF(trie, v.trie).first; +} + +Trie Trie::insertF(const QString &value) const +{ + return Trie(TrieNode::insertF(trie, value)); +} + +Trie Trie::intersectF(const Trie &v) const +{ + return Trie(TrieNode::intersectF(trie, v.trie).first); +} + +Trie Trie::mergeF(const Trie &v) const +{ + return Trie(TrieNode::mergeF(trie, v.trie).first); +} + +/*! + \fn int matchStrength(const QString &searchStr, const QString &str) + + Returns a number defining how well the serachStr matches str. + + Quite simplistic, looks only at the first match, and prefers contiguos + matches, or matches to ca capitalized or separated word. +*/ +int matchStrength(const QString &searchStr, const QString &str) +{ + QString::const_iterator i = searchStr.constBegin(), iEnd = searchStr.constEnd(), + j = str.constBegin(), jEnd = str.constEnd(); + bool lastWasNotUpper=true, lastWasSpacer=true, lastWasMatch = false; + int res = 0; + while (i != iEnd && j != jEnd) { + bool thisIsUpper = (*j).isUpper(); + bool thisIsLetterOrNumber = (*j).isLetterOrNumber(); + if ((*i).toLower() == (*j).toLower()) { + if (lastWasMatch || (lastWasNotUpper && thisIsUpper) + || (thisIsUpper && (*i).isUpper()) + || (lastWasSpacer && thisIsLetterOrNumber)) + ++res; + lastWasMatch = true; + ++i; + } else { + lastWasMatch = false; + } + ++j; + lastWasNotUpper = !thisIsUpper; + lastWasSpacer = !thisIsLetterOrNumber; + } + if (i != iEnd) + return iEnd - i; + return res; +} + +namespace { +class CompareMatchStrength{ + QString searchStr; +public: + CompareMatchStrength(const QString& searchStr) : searchStr(searchStr) { } + bool operator()(const QString &v1, const QString &v2) { + return matchStrength(searchStr,v1) > matchStrength(searchStr,v2); + } +}; +} + +/*! + \fn QStringList matchingStrengthSort(const QString &searchStr, QStringList &res) + + returns a number defining the matching strength of res to the given searchStr +*/ +QStringList matchStrengthSort(const QString &searchStr, QStringList &res) +{ + CompareMatchStrength compare(searchStr); + std::stable_sort(res.begin(), res.end(), compare); + return res; +} + +} // end namespace PersistentTrie +} // end namespace QmlJS diff --git a/src/libs/qmljs/persistenttrie.h b/src/libs/qmljs/persistenttrie.h new file mode 100644 index 00000000000..94a68d0a3ce --- /dev/null +++ b/src/libs/qmljs/persistenttrie.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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 PERSISTENTTRIE_H +#define PERSISTENTTRIE_H + +#include + +#include +#include + +#include + +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QStringList) +QT_FORWARD_DECLARE_CLASS(QDebug) + +namespace QmlJS { +namespace PersistentTrie { + +enum LookupFlags { + CaseInsensitive = 0x1, + Partial = 0x2, + SkipChars = 0x4, + SkipSpaces = 0x8 +}; + +class QMLJS_EXPORT TrieNode +{ +public: + typedef const TrieNode CTrie; + typedef QSharedPointer Ptr; + QString prefix; + QList postfixes; + + TrieNode(const QString &pre = QString(), QList post = QList()); + TrieNode(const TrieNode &o); + static Ptr create(const QString &pre = QString(), QList post = QList()); + + static void complete(QStringList &results, const Ptr &trie, const QString &root, + const QString &base = QString(), LookupFlags flags = LookupFlags(CaseInsensitive|Partial)); + static bool contains(const Ptr &trie, const QString &value, LookupFlags flags = LookupFlags(0)); + static QStringList stringList(const Ptr &trie); + + static Ptr insertF(const Ptr &trie, const QString &value); + static std::pair intersectF(const Ptr &v1, const Ptr &v2, int index1=0); + static std::pair mergeF(const Ptr &v1, const Ptr &v2); + + static QDebug &printStrings(QDebug &dbg, const Ptr &trie); + static QDebug &describe(QDebug &dbg, const Ptr &trie, int indent); +}; + +class QMLJS_EXPORT Trie +{ +public: + Trie(); + Trie(const TrieNode::Ptr &t); + Trie(const Trie &o); + + QStringList complete(const QString &root, const QString &base = QString(), + LookupFlags flags = LookupFlags(CaseInsensitive|Partial)) const; + bool contains(const QString &value, LookupFlags flags = LookupFlags(0)) const; + QStringList stringList() const; + + Trie insertF(const QString &value) const; + Trie intersectF(const Trie &v) const; + Trie mergeF(const Trie &v) const; + + void insert(const QString &value); + void intersect(const Trie &v); + void merge(const Trie &v); + + friend QDebug &operator<<(QDebug &dbg, const TrieNode::Ptr &trie); + friend QDebug &operator<<(QDebug &dbg, const Trie &trie); + + TrieNode::Ptr trie; +}; + +template void enumerateTrieNode(const TrieNode::Ptr &trie, T &t, + QString base = QString()) +{ + if (trie.isNull()) + return; + base.append(trie->prefix); + foreach (const TrieNode::Ptr subT, trie->postfixes) { + enumerateTrieNode(subT,t,base); + } + if (trie->postfixes.isEmpty()) + t(base); +} + +QMLJS_EXPORT int matchStrength(const QString &searchStr, const QString &str); +QMLJS_EXPORT QStringList matchStrengthSort(const QString &searchString, QStringList &res); + +QMLJS_EXPORT QDebug &operator<<(QDebug &dbg, const TrieNode::Ptr &trie); +QMLJS_EXPORT QDebug &operator<<(QDebug &dbg, const Trie &trie); + +} // end namespace PersistentTrie +} // end namespace QmlJS + +#endif // PERSISTENTTRIE_H diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index 3d335f62f41..c5f2c3ba506 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -37,7 +37,8 @@ HEADERS += \ $$PWD/consolemanagerinterface.h \ $$PWD/consoleitem.h \ $$PWD/iscriptevaluator.h \ - $$PWD/qmljssimplereader.h + $$PWD/qmljssimplereader.h \ + $$PWD/persistenttrie.h SOURCES += \ $$PWD/qmljsbind.cpp \ @@ -65,7 +66,8 @@ SOURCES += \ $$PWD/jsoncheck.cpp \ $$PWD/consolemanagerinterface.cpp \ $$PWD/consoleitem.cpp \ - $$PWD/qmljssimplereader.cpp + $$PWD/qmljssimplereader.cpp \ + $$PWD/persistenttrie.cpp RESOURCES += \ $$PWD/qmljs.qrc diff --git a/src/libs/qmljs/qmljs.qbs b/src/libs/qmljs/qmljs.qbs index cbd81c5158e..72dfa88c6ef 100644 --- a/src/libs/qmljs/qmljs.qbs +++ b/src/libs/qmljs/qmljs.qbs @@ -97,6 +97,8 @@ QtcLibrary { "parser/qmljsmemorypool_p.h", "parser/qmljsparser.cpp", "parser/qmljsparser_p.h", + "persistenttrie.cpp", + "persistenttrie.h", "consolemanagerinterface.cpp", "consolemanagerinterface.h", "consoleitem.cpp", diff --git a/tests/auto/qml/persistenttrie/completion.data b/tests/auto/qml/persistenttrie/completion.data new file mode 100644 index 00000000000..b94aeb18193 --- /dev/null +++ b/tests/auto/qml/persistenttrie/completion.data @@ -0,0 +1,10 @@ +com.bla.Pippo 0.1 +com.bla.Pippo 2.0 +com.bla.Pippo 2.1 +com.bla.Pallino 0.1 +QtQuick 1.0 + +pa +com.bla.Pallino 0.1 + +13 diff --git a/tests/auto/qml/persistenttrie/intersect.data b/tests/auto/qml/persistenttrie/intersect.data new file mode 100644 index 00000000000..a388f080780 --- /dev/null +++ b/tests/auto/qml/persistenttrie/intersect.data @@ -0,0 +1,81 @@ +abbc +a +acc +tzk +ttk +mm + +a +artz +tzj +tzg +art +tzg + +abbc +a +acc +tzk +ttk +mm + +abbc +ab +mmk +ab +abc +ab + +abbc +a +acc +tzk +ttk +mm + +com.bla.Pippo 0.1 +com.bla.Pippo 2.0 +com.bla.Pippo 2.1 +com.bla.Pallino 0.1 +QtQuick 1.0 + +a +artz +tzj +tzg +art +tzg + +abbc +ab +mmk +ab +abc +ab + +a +artz +tzj +tzg +art +tzg + +com.bla.Pippo 0.1 +com.bla.Pippo 2.0 +com.bla.Pippo 2.1 +com.bla.Pallino 0.1 +QtQuick 1.0 + +abbc +ab +mmk +ab +abc +ab + +a +artz +tzj +tzg +art +tzg diff --git a/tests/auto/qml/persistenttrie/listAll.data b/tests/auto/qml/persistenttrie/listAll.data new file mode 100644 index 00000000000..79a3477b2e1 --- /dev/null +++ b/tests/auto/qml/persistenttrie/listAll.data @@ -0,0 +1,26 @@ +abbc +a +acc +tzk +ttk +mm + +a +artz +tzj +tzg +art +tzg + +abbc +ab +mmk +ab +abc +ab + +com.bla.Pippo 0.1 +com.bla.Pippo 2.0 +com.bla.Pippo 2.1 +com.bla.Pallino 0.1 +QtQuick 1.0 diff --git a/tests/auto/qml/persistenttrie/merge.data b/tests/auto/qml/persistenttrie/merge.data new file mode 100644 index 00000000000..a388f080780 --- /dev/null +++ b/tests/auto/qml/persistenttrie/merge.data @@ -0,0 +1,81 @@ +abbc +a +acc +tzk +ttk +mm + +a +artz +tzj +tzg +art +tzg + +abbc +a +acc +tzk +ttk +mm + +abbc +ab +mmk +ab +abc +ab + +abbc +a +acc +tzk +ttk +mm + +com.bla.Pippo 0.1 +com.bla.Pippo 2.0 +com.bla.Pippo 2.1 +com.bla.Pallino 0.1 +QtQuick 1.0 + +a +artz +tzj +tzg +art +tzg + +abbc +ab +mmk +ab +abc +ab + +a +artz +tzj +tzg +art +tzg + +com.bla.Pippo 0.1 +com.bla.Pippo 2.0 +com.bla.Pippo 2.1 +com.bla.Pallino 0.1 +QtQuick 1.0 + +abbc +ab +mmk +ab +abc +ab + +a +artz +tzj +tzg +art +tzg diff --git a/tests/auto/qml/persistenttrie/persistenttrie.pro b/tests/auto/qml/persistenttrie/persistenttrie.pro new file mode 100644 index 00000000000..03f039fcb71 --- /dev/null +++ b/tests/auto/qml/persistenttrie/persistenttrie.pro @@ -0,0 +1,25 @@ +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/languageutils/languageutils.pri) +include($$IDE_SOURCE_TREE/src/libs/qmljs/qmljs.pri) + +TARGET = tst_trie_check + +HEADERS += tst_testtrie.h + +SOURCES += \ + tst_testtrie.cpp + +TEMPLATE = app +TARGET = tester +DEFINES += QMLJS_BUILD_DIR QT_CREATOR + +OTHER_FILES += \ + listAll.data \ + intersect.data \ + merge.data \ + completion.data diff --git a/tests/auto/qml/persistenttrie/tst_testtrie.cpp b/tests/auto/qml/persistenttrie/tst_testtrie.cpp new file mode 100644 index 00000000000..601f9e1635d --- /dev/null +++ b/tests/auto/qml/persistenttrie/tst_testtrie.cpp @@ -0,0 +1,374 @@ +/**************************************************************************** +** +** 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 "tst_testtrie.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace QmlJS::PersistentTrie; + +void tst_TestTrie::initTestCase() { +} + +const bool VERBOSE=false; + +tst_TestTrie::tst_TestTrie() { QObject::QObject(); } + +void tst_TestTrie::testListAll_data() +{ + QTest::addColumn("strs"); + + QFile f(QString(TESTSRCDIR)+QString("/listAll.data")); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + QTextStream stream(&f); + int iline = 0; + while (true) { + QStringList list; + QString line; + while (true) { + line=stream.readLine(); + if (line.isEmpty()) + break; + list.append(line); + } + QTest::newRow(QString::number(iline++).toLatin1()) << list; + list.clear(); + if (stream.atEnd()) + break; + } +} + +void tst_TestTrie::testListAll() +{ + QFETCH(QStringList, strs); + Trie trie; + foreach (const QString &s, strs) + trie.insert(s); + QStringList ref=strs; + ref.sort(); + ref.removeDuplicates(); + QStringList content=trie.stringList(); + content.sort(); + if (VERBOSE && ref != content) { + QDebug dbg = qDebug(); + dbg << "ERROR inserting ["; + bool comma = false; + foreach (const QString &s, strs) { + if (comma) + dbg << ","; + else + comma = true; + dbg << s; + } + dbg << "] one gets " << trie; + } + QCOMPARE(ref, content); + foreach (const QString &s,strs) { + if (VERBOSE && ! trie.contains(s)) { + qDebug() << "ERROR could not find " << s << "in" << trie; + } + QVERIFY(trie.contains(s)); + } +} + +void tst_TestTrie::testMerge_data() +{ + QTest::addColumn("str1"); + QTest::addColumn("str2"); + + QFile f(QString(TESTSRCDIR)+QString("/merge.data")); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + QTextStream stream(&f); + int iline = 0; + while (true) { + QStringList list1; + QString line; + while (true) { + line=stream.readLine(); + if (line.isEmpty()) + break; + list1.append(line); + } + QStringList list2; + while (true) { + line=stream.readLine(); + if (line.isEmpty()) + break; + list2.append(line); + } + QTest::newRow(QString::number(iline++).toLatin1()) << list1 << list2; + list1.clear(); + list2.clear(); + if (stream.atEnd()) + break; + } +} + +void tst_TestTrie::testMerge() +{ + QFETCH(QStringList, str1); + QFETCH(QStringList, str2); + Trie trie1; + foreach (const QString &s, str1) + trie1.insert(s); + Trie trie2; + foreach (const QString &s, str2) + trie2.insert(s); + QStringList ref=str1; + ref.append(str2); + ref.sort(); + ref.removeDuplicates(); + Trie trie3 = trie1.mergeF(trie2); + QStringList content=trie3.stringList(); + content.sort(); + if (VERBOSE && ref != content) { + QDebug dbg=qDebug(); + dbg << "ERROR merging ["; + bool comma = false; + foreach (const QString &s, str1) { + if (comma) + dbg << ","; + else + comma = true; + dbg << s; + } + dbg << "] => " << trie1 << " and ["; + comma = false; + foreach (const QString &s, str2) { + if (comma) + dbg << ","; + else + comma = true; + dbg << s; + } + dbg << "] => " << trie2 + << " to " << trie3; + } + QCOMPARE(ref, content); +} + +void tst_TestTrie::testIntersect_data() +{ + QTest::addColumn("str1"); + QTest::addColumn("str2"); + + QFile f(QString(TESTSRCDIR)+QString("/intersect.data")); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + QTextStream stream(&f); + int iline = 0; + while (true) { + QStringList list1; + QString line; + while (true) { + line=stream.readLine(); + if (line.isEmpty()) + break; + list1.append(line); + } + QStringList list2; + while (true) { + line=stream.readLine(); + if (line.isEmpty()) + break; + list2.append(line); + } + QTest::newRow(QString::number(iline++).toLatin1()) << list1 << list2; + list1.clear(); + list2.clear(); + if (stream.atEnd()) + break; + } +} + +void tst_TestTrie::testIntersect() +{ + QFETCH(QStringList, str1); + QFETCH(QStringList, str2); + Trie trie1; + foreach (const QString &s, str1) + trie1.insert(s); + Trie trie2; + foreach (const QString &s, str2) + trie2.insert(s); + QSet ref1; + foreach (const QString &s, str1) + ref1.insert(s); + QSet ref2; + foreach (const QString &s, str2) + ref2.insert(s); + ref1.intersect(ref2); + Trie trie3 = trie1.intersectF(trie2); + ref2.clear(); + foreach (const QString &s, trie3.stringList()) + ref2.insert(s); + if (VERBOSE && ref1 != ref2) { + QDebug dbg=qDebug(); + dbg << "ERROR intersecting ["; + bool comma = false; + foreach (const QString &s, str1) { + if (comma) + dbg << ","; + else + comma = true; + dbg << s; + } + dbg << "] => " << trie1 << " and ["; + comma = false; + foreach (const QString &s, str2) { + if (comma) + dbg << ","; + else + comma = true; + dbg << s; + } + dbg << "] => " << trie2 + << " to " << trie3; + } + QCOMPARE(ref1, ref2); +} + +void tst_TestTrie::testCompletion_data() +{ + QTest::addColumn("trieContents"); + QTest::addColumn("str"); + QTest::addColumn("completions"); + QTest::addColumn("flags"); + + QFile f(QString(TESTSRCDIR)+QString("/completion.data")); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + QTextStream stream(&f); + int iline = 0; + while (true) { + QStringList list1; + QString line; + while (true) { + line = stream.readLine(); + if (line.isEmpty()) + break; + list1.append(line); + } + QString str = stream.readLine(); + QStringList list2; + while (true) { + line = stream.readLine(); + if (line.isEmpty()) + break; + list2.append(line); + } + int flags = stream.readLine().toInt(); + QTest::newRow(QString::number(iline++).toLatin1()) << list1 << str << list2 << flags; + list1.clear(); + list2.clear(); + if (stream.atEnd()) + break; + } +} + +void tst_TestTrie::testCompletion() { + QFETCH(QStringList, trieContents); + QFETCH(QString, str); + QFETCH(QStringList, completions); + QFETCH(int, flags); + + Trie trie; + foreach (const QString &s, trieContents) + trie.insert(s); + QStringList res = trie.complete(str, QString(), LookupFlags(flags)); + res = matchStrengthSort(str, res); + if (VERBOSE && res != completions) { + qDebug() << "unexpected completions for " << str + << " in " << trie; + qDebug() << "expected :["; + foreach (const QString &s, completions) { + qDebug() << s; + } + qDebug() << "] got ["; + foreach (const QString &s, res) { + qDebug() << s; + } + qDebug() << "]"; + } + QCOMPARE(res, completions); +} + +void interactiveCompletionTester(){ + Trie trie; + qDebug() << "interactive completion tester, insert the strings int the trie (empty line to stop)"; + QTextStream stream(stdin); + QString line; + while (true) { + line=stream.readLine(); + if (line.isEmpty()) + break; + trie.insert(line); + } + qDebug() << "testing Complete on " << trie; + while (true) { + qDebug() << "insert a string to complete (empty line to stop)"; + line=stream.readLine(); + if (line.isEmpty()) + break; + QStringList res=trie.complete(line, QString(), + LookupFlags(CaseInsensitive|SkipChars|SkipSpaces)); + res = matchStrengthSort(line,res); + qDebug() << "possible completions:["; + foreach (const QString &s, res) { + qDebug() << s; + } + qDebug() << "]"; + } +} + +#ifdef INTERACTIVE_COMPLETION_TEST + +int main(int , const char *[]) +{ + interactiveCompletionTester(); + + return 0; +} + +#else + +QTEST_MAIN(tst_TestTrie); + +//#include "moc_tst_testtrie.cpp" + +#endif diff --git a/tests/auto/qml/persistenttrie/tst_testtrie.h b/tests/auto/qml/persistenttrie/tst_testtrie.h new file mode 100644 index 00000000000..2559e4a0c07 --- /dev/null +++ b/tests/auto/qml/persistenttrie/tst_testtrie.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 + +QT_FORWARD_DECLARE_CLASS(QStringList) + +class tst_TestTrie : public QObject +{ + Q_OBJECT +public: + tst_TestTrie(); + +private slots: + void initTestCase(); + //void cleanupTestCase(); + //void init(); + //void cleanup(); + + void testListAll_data(); + void testMerge_data(); + void testIntersect_data(); + void testCompletion_data(); + + void testListAll(); + void testMerge(); + void testIntersect(); + void testCompletion(); +};