diff options
Diffstat (limited to 'src/qmldom/qqmldompath.cpp')
-rw-r--r-- | src/qmldom/qqmldompath.cpp | 967 |
1 files changed, 967 insertions, 0 deletions
diff --git a/src/qmldom/qqmldompath.cpp b/src/qmldom/qqmldompath.cpp new file mode 100644 index 0000000000..d2b4582bc0 --- /dev/null +++ b/src/qmldom/qqmldompath.cpp @@ -0,0 +1,967 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qqmldomitem_p.h" +#include "qqmldomerrormessage_p.h" +#include <QtCore/QDebug> +#include <QtCore/QTextStream> +#include <QtCore/QChar> + +#include <cstdint> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { +class ErrorMessage; + +namespace PathEls { + +/*! +\internal +\class QQmljs::Dom::QmlPath::Path + +\brief Represents an immutable JsonPath like path in the Qml code model (from a DomItem to another + DomItem) + +It can be created either from a string, with the path static functions +or by modifying an existing path +\code +Path qmlFilePath = + Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]"); +Path imports = qmlFilePath.subField(u"imports") +Path currentComponentImports = Path::current(u"component").subField(u"imports"); +\endcode + +This is a way to refer to elements in the Dom models that is not dependent from the source location, +and thus can be used also be used in visual tools. +A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe +even when "dangling", thus it is a good long term reference to an element in the Dom model. + +Path objects are a value type that have a shared pointer to extra data if needed, thus one should +use them as value objects. +The implementation has still optimization potential, but the behavior for the user should be already +the final one. + +Path is both a range, and a single element (a bit like strings and characters in python). + +The root contexts are: +\list +\li \l{$modules} All the known modules (even not imported), this is a global, rename independent + reference +\li \l{$cpp} The Cpp names (namespaces, and Cpp types) visible in the current component +\li \l{$libs} The plugins/libraries and their contents +\li \l{$top} A top level entry in the DOM model, either $env or $universe (stepping in the universe + one looses the reference to its environment) +\li \l{$env} The environment containing the currently available modules, i.e. the top level entry in + the DOM model +\li \l{$universe} The dom unverse used by ths environment, and potentially shared with others that + contains all the known parse entries, and also the latest, potentially invalid entries +\li \l{$} ? undecided, one the previous ones? +\endlist + +The current contexts are: +\list +\li \l{@obj} The current object (if in a map or list goes up until it is in the current object) . +\li \l{@component} The root object of the current component. +\li \l{@module} The current module instantiation. +\li \l{@ids} The ids in the current component. +\li \l{@types} All the types in the current component (reachable through imports, respecting renames) +\li \l{@lookupStrict} The strict lookup inside the current object: localJS, ids, properties, proto + properties, component, its properties, global context, oterwise error +\li \l{@lookupDynamic} The default lookup inside the current object: localJS, ids, properties, proto + properties, component, its properties, global context, .. +\li \l{@lookup} Either lookupStrict or lookupDynamic depending on the current component and context. +\li \l{@} ? undecided, one the previous ones +\endlist + */ + +void Base::dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const { + if (hasSquareBrackets) + sink(u"["); + sink(name); + if (hasSquareBrackets) + sink(u"]"); +} + +Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription) + : filterFunction(f), filterDescription(filterDescription) {} + +QString Filter::name() const { + return QLatin1String("?(%1)").arg(filterDescription); } + +bool Filter::checkName(QStringView s) const +{ + return s.startsWith(u"?(") + && s.mid(2, s.size()-3) == filterDescription + && s.endsWith(u")"); +} + +enum class ParserState{ + Start, + IndexOrKey, + End +}; + +int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) +{ + int k1 = static_cast<int>(p1.kind()); + int k2 = static_cast<int>(p2.kind()); + if (k1 < k2) + return -1; + if (k1 > k2) + return 1; + switch (p1.kind()) { + case Kind::Empty: + return 0; + case Kind::Field: + return std::get<Field>(p1.m_data).fieldName.compare(std::get<Field>(p2.m_data).fieldName); + case Kind::Index: + if (std::get<Index>(p1.m_data).indexValue < std::get<Index>(p2.m_data).indexValue) + return -1; + if (std::get<Index>(p1.m_data).indexValue > std::get<Index>(p2.m_data).indexValue) + return 1; + return 0; + case Kind::Key: + return std::get<Key>(p1.m_data).keyValue.compare(std::get<Key>(p2.m_data).keyValue); + case Kind::Root: + { + PathRoot k1 = std::get<Root>(p1.m_data).contextKind; + PathRoot k2 = std::get<Root>(p2.m_data).contextKind; + if (k1 == PathRoot::Env || k1 == PathRoot::Universe) + k1 = PathRoot::Top; + if (k2 == PathRoot::Env || k2 == PathRoot::Universe) + k2 = PathRoot::Top; + int c = int(k1) - int(k2); + if (c != 0) + return c; + return std::get<Root>(p1.m_data).contextName.compare(std::get<Root>(p2.m_data).contextName); + } + case Kind::Current: + { + int c = int(std::get<Current>(p1.m_data).contextKind) + - int(std::get<Current>(p2.m_data).contextKind); + if (c != 0) + return c; + return std::get<Current>(p1.m_data).contextName + .compare(std::get<Current>(p2.m_data).contextName); + } + case Kind::Any: + return 0; + case Kind::Filter: + { + int c = std::get<Filter>(p1.m_data).filterDescription + .compare(std::get<Filter>(p2.m_data).filterDescription); + if (c != 0) + return c; + if (std::get<Filter>(p1.m_data).filterDescription.startsWith(u"<")) { + // assuming non comparable native code (target comparison is not portable) + auto pp1 = &p1; + auto pp2 = &p2; + if (pp1 < pp2) + return -1; + if (pp1 > pp2) + return 1; + } + return 0; + } + } + Q_ASSERT(false && "unexpected PathComponent in PathComponent::cmp"); + return 0; +} + +} // namespace PathEls + +const PathEls::PathComponent &Path::component(int i) const +{ + static Component emptyComponent; + if (i < 0) + i += m_length; + if (i >= m_length || i < 0) { + Q_ASSERT(false && "index out of bounds"); + return emptyComponent; + } + i = i - m_length - m_endOffset; + auto data = m_data.get(); + while (data) { + i += data->components.size(); + if (i >= 0) + return std::as_const(data)->components[i]; + data = data->parent.get(); + } + Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)"); + return emptyComponent; +} + +Path Path::operator[](int i) const +{ + return mid(i,1); +} + +QQmlJS::Dom::Path::operator bool() const +{ + return length() != 0; +} + +PathIterator Path::begin() const +{ + return PathIterator{*this}; +} + +PathIterator Path::end() const +{ + return PathIterator(); +} + +PathRoot Path::headRoot() const +{ + auto &comp = component(0); + if (PathEls::Root const * r = comp.asRoot()) + return r->contextKind; + return PathRoot::Other; +} + +PathCurrent Path::headCurrent() const +{ + auto comp = component(0); + if (PathEls::Current const * c = comp.asCurrent()) + return c->contextKind; + return PathCurrent::Other; +} + +Path::Kind Path::headKind() const +{ + if (m_length == 0) + return Path::Kind::Empty; + return component(0).kind(); +} + +QString Path::headName() const +{ + return component(0).name(); +} + +bool Path::checkHeadName(QStringView name) const +{ + return component(0).checkName(name); +} + +index_type Path::headIndex(index_type defaultValue) const +{ + return component(0).index(defaultValue); +} + +function<bool(const DomItem &)> Path::headFilter() const +{ + auto &comp = component(0); + if (PathEls::Filter const * f = comp.asFilter()) { + return f->filterFunction; + } + return {}; +} + +Path Path::head() const +{ + return mid(0,1); +} + +Path Path::last() const +{ + return mid(m_length-1, 1); +} + +Source Path::split() const +{ + int i = length(); + while (i>0) { + const PathEls::PathComponent &c=component(--i); + if (c.kind() == Kind::Field || c.kind() == Kind::Root || c.kind() == Kind::Current) { + return Source{mid(0, i), mid(i)}; + } + } + return Source{Path(), *this}; +} + +bool inQString(QStringView el, const QString &base) +{ + if (quintptr(base.constData()) > quintptr(el.begin()) + || quintptr(base.constData() + base.size()) < quintptr(el.begin())) + return false; + ptrdiff_t diff = base.constData() - el.begin(); + return diff >= 0 && diff < base.size(); +} + +bool inQString(const QString &el, const QString &base) +{ + if (quintptr(base.constData()) > quintptr(el.constData()) + || quintptr(base.constData() + base.size()) < quintptr(el.constData())) + return false; + ptrdiff_t diff = base.constData() - el.constData(); + return diff >= 0 && diff < base.size() && diff + el.size() < base.size(); +} + +Path Path::fromString(QStringView s, const ErrorHandler &errorHandler) +{ + if (s.isEmpty()) + return Path(); + int len=1; + const QChar dot = QChar::fromLatin1('.'); + const QChar lsBrace = QChar::fromLatin1('['); + const QChar rsBrace = QChar::fromLatin1(']'); + const QChar dollar = QChar::fromLatin1('$'); + const QChar at = QChar::fromLatin1('@'); + const QChar quote = QChar::fromLatin1('"'); + const QChar backslash = QChar::fromLatin1('\\'); + const QChar underscore = QChar::fromLatin1('_'); + const QChar tilda = QChar::fromLatin1('~'); + for (int i=0; i < s.size(); ++i) + if (s.at(i) == lsBrace || s.at(i) == dot) + ++len; + QVector<Component> components; + components.reserve(len); + int i = 0; + int i0 = 0; + PathEls::ParserState state = PathEls::ParserState::Start; + QStringList strVals; + while (i < s.size()) { + // skip space + while (i < s.size() && s.at(i).isSpace()) + ++i; + if (i >= s.size()) + break; + QChar c = s.at(i++); + switch (state) { + case PathEls::ParserState::Start: + if (c == dollar) { + i0 = i; + while (i < s.size() && s.at(i).isLetterOrNumber()){ + ++i; + } + components.append(Component(PathEls::Root(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; + } else if (c == at) { + i0 = i; + while (i < s.size() && s.at(i).isLetterOrNumber()){ + ++i; + } + components.append(Component(PathEls::Current(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; + } else if (c.isLetter()) { + myErrors().warning(tr("Field expressions should start with a dot, even when at the start of the path %1.") + .arg(s)).handle(errorHandler); + return Path(); + } else { + --i; + state = PathEls::ParserState::End; + } + break; + case PathEls::ParserState::IndexOrKey: + if (c.isDigit()) { + i0 = i-1; + while (i < s.size() && s.at(i).isDigit()) + ++i; + bool ok; + components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString() + .toLongLong(&ok)))); + if (!ok) { + myErrors().warning(tr("Error extracting integer from '%1' at char %2.") + .arg(s.mid(i0,i-i0)) + .arg(QString::number(i0))).handle(errorHandler); + } + } else if (c.isLetter() || c == tilda || c == underscore) { + i0 = i-1; + while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) + ++i; + components.append(Component(PathEls::Key(s.mid(i0, i - i0).toString()))); + } else if (c == quote) { + i0 = i; + QString strVal; + bool properEnd = false; + while (i < s.size()) { + c = s.at(i); + if (c == quote) { + properEnd = true; + break; + } else if (c == backslash) { + strVal.append(s.mid(i0, i - i0).toString()); + c = s.at(++i); + i0 = i + 1; + if (c == QChar::fromLatin1('n')) + strVal.append(QChar::fromLatin1('\n')); + else if (c == QChar::fromLatin1('r')) + strVal.append(QChar::fromLatin1('\r')); + else if (c == QChar::fromLatin1('t')) + strVal.append(QChar::fromLatin1('\t')); + else + strVal.append(c); + } + ++i; + } + if (properEnd) { + strVal.append(s.mid(i0, i - i0).toString()); + ++i; + } else { + myErrors().error(tr("Unclosed quoted string at char %1.") + .arg(QString::number(i - 1))).handle(errorHandler); + return Path(); + } + components.append(PathEls::Key(strVal)); + } else if (c == QChar::fromLatin1('*')) { + components.append(Component(PathEls::Any())); + } else if (c == QChar::fromLatin1('?')) { + while (i < s.size() && s.at(i).isSpace()) + ++i; + if (i >= s.size() || s.at(i) != QChar::fromLatin1('(')) { + myErrors().error(tr("Expected a brace in filter after the question mark (at char %1).") + .arg(QString::number(i))).handle(errorHandler); + return Path(); + } + i0 = ++i; + while (i < s.size() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping?? + if (i >= s.size() || s.at(i) != QChar::fromLatin1(')')) { + myErrors().error(tr("Expected a closing brace in filter after the question mark (at char %1).") + .arg(QString::number(i))).handle(errorHandler); + return Path(); + } + //auto filterDesc = s.mid(i0,i-i0); + ++i; + myErrors().error(tr("Filter from string not yet implemented.")).handle(errorHandler); + return Path(); + } else { + myErrors().error(tr("Unexpected character '%1' after square bracket at %2.") + .arg(c).arg(i-1)).handle(errorHandler); + return Path(); + } + while (i < s.size() && s.at(i).isSpace()) ++i; + if (i >= s.size() || s.at(i) != rsBrace) { + myErrors().error(tr("square braces misses closing brace at char %1.") + .arg(QString::number(i))).handle(errorHandler); + return Path(); + } else { + ++i; + } + state = PathEls::ParserState::End; + break; + case PathEls::ParserState::End: + if (c == dot) { + while (i < s.size() && s.at(i).isSpace()) ++i; + if (i == s.size()) { + components.append(Component()); + state = PathEls::ParserState::End; + } else if (s.at(i).isLetter() || s.at(i) == underscore || s.at(i) == tilda) { + i0 = i; + while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) { + ++i; + } + components.append(Component(PathEls::Field(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; + } else if (s.at(i).isDigit()) { + i0 = i; + while (i < s.size() && s.at(i).isDigit()){ + ++i; + } + bool ok; + components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString().toLongLong(&ok)))); + if (!ok) { + myErrors().warning(tr("Error extracting integer from '%1' at char %2.") + .arg(s.mid(i0,i-i0)) + .arg(QString::number(i0))).handle(errorHandler); + return Path(); + } else { + myErrors().info(tr("Index should use square brackets and not a dot (at char %1).") + .arg(QString::number(i0))).handle(errorHandler); + } + state = PathEls::ParserState::End; + } else if (s.at(i) == dot || s.at(i) == lsBrace) { + components.append(Component()); + state = PathEls::ParserState::End; + } else if (s.at(i) == at) { + i0 = ++i; + while (i < s.size() && s.at(i).isLetterOrNumber()){ + ++i; + } + components.append(Component(PathEls::Current(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; + } else if (s.at(i) == dollar) { + i0 = ++i; + while (i < s.size() && s.at(i).isLetterOrNumber()){ + ++i; + } + components.append(Component(PathEls::Root(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; + } else { + c=s.at(i); + myErrors().error(tr("Unexpected character '%1' after dot (at char %2).") + .arg(QStringView(&c,1)) + .arg(QString::number(i-1))).handle(errorHandler); + return Path(); + } + } else if (c == lsBrace) { + state = PathEls::ParserState::IndexOrKey; + } else { + myErrors().error(tr("Unexpected character '%1' after end of component (char %2).") + .arg(QStringView(&c,1)) + .arg(QString::number(i-1))).handle(errorHandler); + return Path(); + } + break; + } + } + switch (state) { + case PathEls::ParserState::Start: + return Path(); + case PathEls::ParserState::IndexOrKey: + errorHandler(myErrors().error(tr("unclosed square brace at end."))); + + return Path(); + case PathEls::ParserState::End: + return Path(0, components.size(), std::make_shared<PathEls::PathData>( + strVals, components)); + } + Q_ASSERT(false && "Unexpected state in Path::fromString"); + return Path(); +} + +Path Path::Root(PathRoot s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Root(s))))); +} + +Path Path::Root(const QString &s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(s), QVector<Component>(1,Component(PathEls::Root(s))))); +} + +Path Path::Index(index_type i) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Index(i))))); +} + +Path Path::Root(QStringView s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Root(s))))); +} + + +Path Path::Field(QStringView s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Field(s))))); +} + +Path Path::Field(const QString &s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(s), QVector<Component>(1,Component(PathEls::Field(s))))); +} + +Path Path::Key(QStringView s) +{ + return Path( + 0, 1, + std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1, Component(PathEls::Key(s.toString()))))); +} + +Path Path::Key(const QString &s) +{ + return Path(0, 1, + std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1, Component(PathEls::Key(s))))); +} + +Path Path::Current(PathCurrent s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))))); +} + +Path Path::Current(const QString &s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(s), QVector<Component>(1,Component(PathEls::Current(s))))); +} + +Path Path::Current(QStringView s) +{ + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))))); +} + +Path Path::Empty() +{ + return Path(); +} + +Path Path::empty() const +{ + if (m_endOffset != 0) + return noEndOffset().empty(); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component()), m_data)); +} + +Path Path::field(const QString &name) const +{ + auto res = field(QStringView(name)); + res.m_data->strData.append(name); + return res; +} + +Path Path::field(QStringView name) const +{ + if (m_endOffset != 0) + return noEndOffset().field(name); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Field(name))), m_data)); +} + +Path Path::key(const QString &name) const +{ + if (m_endOffset != 0) + return noEndOffset().key(name); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Key(name))), m_data)); +} + +Path Path::key(QStringView name) const +{ + return key(name.toString()); +} + +Path Path::index(index_type i) const +{ + if (m_endOffset != 0) + return noEndOffset().index(i); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(i)), m_data)); +} + +Path Path::any() const +{ + if (m_endOffset != 0) + return noEndOffset().any(); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Any())), m_data)); +} + +Path Path::filter(const function<bool(const DomItem &)> &filterF, const QString &desc) const +{ + auto res = filter(filterF, QStringView(desc)); + res.m_data->strData.append(desc); + return res; +} + +Path Path::filter(const function<bool(const DomItem &)> &filter, QStringView desc) const +{ + if (m_endOffset != 0) + return noEndOffset().filter(filter, desc); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Filter(filter, desc))), m_data)); +} + +Path Path::current(PathCurrent s) const +{ + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data)); +} + +Path Path::current(const QString &s) const +{ + auto res = current(QStringView(s)); + res.m_data->strData.append(s); + return res; +} + +Path Path::current(QStringView s) const +{ + if (m_endOffset != 0) + return noEndOffset().current(s); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data)); +} + +Path Path::path(const Path &toAdd, bool avoidToAddAsBase) const +{ + if (toAdd.length() == 0) + return *this; + int resLength = length() + toAdd.length(); + if (m_endOffset != 0) { + Path thisExtended = this->expandBack(); + if (thisExtended.length() > resLength) + thisExtended = thisExtended.mid(0, resLength); + Path added = thisExtended.mid(length(), thisExtended.length() - length()); + if (added == toAdd.mid(0, toAdd.length())) { + if (resLength == thisExtended.length()) + return thisExtended; + else + return thisExtended.path(toAdd.mid(added.length(), resLength - thisExtended.length())); + } + } + if (!avoidToAddAsBase) { + Path toAddExtended = toAdd.expandFront(); + if (toAddExtended.length() >= resLength) { + toAddExtended = toAddExtended.mid(toAddExtended.length() - resLength, resLength); + if (toAddExtended.mid(0,length()) == *this) + return toAddExtended; + } + } + QVector<Component> components; + components.reserve(toAdd.length()); + QStringList addedStrs; + bool addHasStr = false; + auto data = toAdd.m_data.get(); + while (data) { + if (!data->strData.isEmpty()) { + addHasStr = true; + break; + } + data = data->parent.get(); + } + if (addHasStr) { + QStringList myStrs; + data = m_data.get(); + while (data) { + myStrs.append(data->strData); + data = data->parent.get(); + } + data = toAdd.m_data.get(); + while (data) { + for (int ij = 0; ij < data->strData.size(); ++ij) { + bool hasAlready = false; + for (int ii = 0; ii < myStrs.size() && !hasAlready; ++ii) + hasAlready = inQString(data->strData[ij], myStrs[ii]); + if (!hasAlready) + addedStrs.append(data->strData[ij]); + } + data = data->parent.get(); + } + } + QStringList toAddStrs; + for (int i = 0; i < toAdd.length(); ++i) { + components.append(toAdd.component(i)); + QStringView compStrView = toAdd.component(i).stringView(); + if (!compStrView.isEmpty()) { + for (int j = 0; j < addedStrs.size(); ++j) { + if (inQString(compStrView, addedStrs[j])) { + toAddStrs.append(addedStrs[j]); + addedStrs.removeAt(j); + break; + } + } + } + } + return Path(0, m_length + toAdd.length(), std::make_shared<PathEls::PathData>( + toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data))); +} + +Path Path::expandFront() const +{ + int newLen = 0; + auto data = m_data.get(); + while (data) { + newLen += data->components.size(); + data = data->parent.get(); + } + newLen -= m_endOffset; + return Path(m_endOffset, newLen, m_data); +} + +Path Path::expandBack() const +{ + if (m_endOffset > 0) + return Path(0, m_length + m_endOffset, m_data); + return *this; +} + +Path &Path::operator++() +{ + if (m_length > 0) + m_length -= 1; + return *this; +} + +Path Path::operator++(int) +{ + Path res = *this; + if (m_length > 0) + m_length -= 1; + return res; +} + +int Path::cmp(const Path &p1, const Path &p2) +{ + // lexicographic ordering + const int lMin = qMin(p1.m_length, p2.m_length); + if (p1.m_data.get() == p2.m_data.get() && p1.m_endOffset == p2.m_endOffset && p1.m_length == p2.m_length) + return 0; + for (int i = 0; i < lMin; ++i) { + int c = Component::cmp(p1.component(i), p2.component(i)); + if (c != 0) + return c; + } + if (lMin < p2.m_length) + return -1; + if (p1.m_length > lMin) + return 1; + return 0; +} + +Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data) + :m_endOffset(endOffset), m_length(length), m_data(data) +{ +} + +Path Path::noEndOffset() const +{ + if (m_length == 0) + return Path(); + if (m_endOffset == 0) + return *this; + // peel back + qint16 endOffset = m_endOffset; + std::shared_ptr<PathEls::PathData> lastData = m_data; + while (lastData && endOffset >= lastData->components.size()) { + endOffset -= lastData->components.size(); + lastData = lastData->parent; + } + if (endOffset > 0) { + Q_ASSERT(lastData && "Internal problem, reference to non existing PathData"); + return Path(0, m_length, std::make_shared<PathEls::PathData>( + lastData->strData, lastData->components.mid(0, lastData->components.size() - endOffset), lastData->parent)); + } + return Path(0, m_length, lastData); +} + +Path Path::appendComponent(const PathEls::PathComponent &c) +{ + if (m_endOffset != 0) { + Path newP = noEndOffset(); + return newP.appendComponent(c); + } + if (m_data && m_data.use_count() != 1) { + // create a new path (otherwise paths linking to this will change) + Path newP(c); + newP.m_data->parent = m_data; + newP.m_length = static_cast<quint16>(m_length + 1); + return newP; + } + auto my_data = + (m_data ? m_data + : std::make_shared<PathEls::PathData>(QStringList(), + QVector<PathEls::PathComponent>())); + switch (c.kind()) { + case PathEls::Kind::Any: + case PathEls::Kind::Empty: + case PathEls::Kind::Index: + // no string + case PathEls::Kind::Field: + // string assumed to stay valid (Fields::...) + my_data->components.append(c); + break; + case PathEls::Kind::Current: + if (c.asCurrent()->contextKind == PathCurrent::Other) { + my_data->strData.append(c.asCurrent()->contextName.toString()); + my_data->components.append(PathEls::Current(my_data->strData.last())); + } else { + my_data->components.append(c); + } + break; + case PathEls::Kind::Filter: + if (!c.asFilter()->filterDescription.isEmpty()) { + my_data->strData.append(c.asFilter()->filterDescription.toString()); + my_data->components.append( + PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last())); + } else { + my_data->components.append(c); + } + break; + case PathEls::Kind::Key: + my_data->components.append(c); + break; + case PathEls::Kind::Root: + if (c.asRoot()->contextKind == PathRoot::Other) { + my_data->strData.append(c.asRoot()->contextName.toString()); + my_data->components.append(PathEls::Root(my_data->strData.last())); + } else { + my_data->components.append(c); + } + break; + } + if (m_data) + m_endOffset = 1; + return Path { 0, static_cast<quint16>(m_length + 1), my_data }; +} + +ErrorGroups Path::myErrors() +{ + static ErrorGroups res = {{NewErrorGroup("PathParsing")}}; + return res; +} + +void Path::dump(const Sink &sink) const +{ + bool first = true; + for (int i = 0; i < m_length; ++i) { + auto &c = component(i); + if (!c.hasSquareBrackets()) { + if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current)) + sink(u"."); + } + c.dump(sink); + first = false; + } +} + +QString Path::toString() const +{ + QString res; + QTextStream stream(&res); + dump([&stream](QStringView str){ stream << str; }); + stream.flush(); + return res; +} + +Path Path::dropFront(int n) const +{ + if (m_length > n && n >= 0) + return Path(m_endOffset, m_length - n, m_data); + return Path(); +} + +Path Path::dropTail(int n) const +{ + if (m_length > n && n >= 0) + return Path(m_endOffset + n, m_length - n, m_data); + return Path(); +} + +Path Path::mid(int offset, int length) const +{ + length = qMin(m_length - offset, length); + if (offset < 0 || offset >= m_length || length <= 0 || length > m_length) + return Path(); + int newEndOffset = m_endOffset + m_length - offset - length; + return Path(newEndOffset, length, m_data); +} + +Path Path::mid(int offset) const +{ + return mid(offset, m_length - offset); +} + +Path Path::fromString(const QString &s, const ErrorHandler &errorHandler) +{ + Path res = fromString(QStringView(s), errorHandler); + if (res.m_data) + res.m_data->strData.append(s); + return res; +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE + +#include "moc_qqmldompath_p.cpp" |