Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmldompath.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5#include <QtCore/QDebug>
6#include <QtCore/QTextStream>
7#include <QtCore/QChar>
8
9#include <cstdint>
10
11QT_BEGIN_NAMESPACE
12namespace QQmlJS {
13namespace Dom {
14class ErrorMessage;
15
16namespace PathEls {
17
18/*!
19\internal
20\class QQmljs::Dom::QmlPath::Path
21
22\brief Represents an immutable JsonPath like path in the Qml code model (from a DomItem to another
23 DomItem)
24
25
26\section1 Implementation Details
27
28The Path class is implemented as a linked list tree where components of the path can be shared
29between multiple paths.
30
31We will represent a list of components with
32\badcode
33[a] // one element "a" in the list
34[a,b,c] // three elements in the list
35\endcode
36and the linked list tree structure with arrows:
37\badcode
38[a,b,c] <- [y,z] // one linked list made up of two nodes consisting of a, b, c, y and z
39 ^
40 |______ [q] <- [w] // one linked list made up of three nodes consisting of a, b, c, q and w,
41 // sharing the a,b and c part with the above list
42\endcode
43
44Appending components to the path will create a new linked list node, for example when appending \c d
45to \c{[a,b,c]}:
46\badcode
47[a,b,c] <- [d]
48\endcode
49This allows to share the path prefix when constructing paths recursively, for example.
50
51\section2 PathData as elements of the linked list trees
52
53Each node of the linked list tree is stored as \c PathData. It contains a vector of
54\c PathComponents, a pointer to the previous element in the path (called parent), and a QStringList
55to extend the lifetime of QStringViews used inside of PathComponent.
56
57\section3 PathComponents
58
59The path can also be "compressed", as the linked list structure contains vectors of components:
60\c {a.b.c.d} could be represented by
61\badcode
62[a] <- [b] <- [c] <- [d]
63\endcode
64(which is the default) or also
65\badcode
66[a, b] <- [c, d]
67\endcode
68when \c{[c,d]} got appended to \c{[a, b]} via \c withPath, for example.
69
70\section3 String Data
71
72The PathData is responsible to own the string data used by the PathComponents it contains.
73PathComponents do not contain any owning QStrings, they only hold QStringViews. To construct a
74PathData, store the string data in the QStringList such that it can be referenced by the
75QStringViews of PathData's PathComponents. PathData are assumed to be immutable after construction
76to avoid any dangling references due to a QStringList reallocation, for example.
77
78\section2 Appending
79
80Appending one component to the Path allows to share the "parent" path in the memory and should be
81the preferred way of constructing paths for DomItems.
82
83\section2 Iteration
84
85To iterate through the path, you need two iterators. One for the current data (that contains the
86vector of components and the pointer to the "parent") and one for the current idx in the vector of
87components.
88
89To access \c d in that path:
90\badcode
91[a, b] <- [c, d]
92\endcode
93you need to do
94\badcode
95path.m_data->components[1].
96\endcode
97to access \c b instead, you need to do:
98\badcode
99path.m_data->parent->components[1]
100\endcode
101
102Note that all kind of forward iterations (getting the components a, b, c, and d in this order from
103a.b.c.d) is a bit more involved, and backward iteration is much faster/easier.
104
105\section2 Slicing
106
107The path also supports slicing via the \c m_length and \c m_endOffset members. \c m_endOffset
108ignores the n last components, for example \c{a.b.c.d} can be stored as
109
110\badcode
111[root] <- [a] <- [b] <- [c] <- [d] <- [e] <- [f] <- [g]
112\endcode
113with m_endOffset = 3 and m_length = 4.
114
115\section2 Lifetime in the DOM
116
117Paths are usually created during the Dom construction, and destroyed when the DomItem or Dom
118element holding it is destroyed.
119
120The underlying PathData, on the other hand, is managed by a std::shared_ptr. It is destroyed when
121all Paths referencing it are destroyed. PathData can be referenced either directly through Path's \c
122m_data member, or indirectly through another PathData's \c m_parent member.
123
124\section1 Usage
125
126It can be created either from a string, with the path static functions
127or by modifying an existing path
128\code
129Path qmlFilePath =
130 Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]");
131Path imports = qmlFilePath.withField(u"imports")
132Path currentComponentImports = Path::fromCurrent(u"component").withField(u"imports");
133\endcode
134
135This is a way to refer to elements in the Dom models that is not dependent from the source location,
136and thus can also be used in visual tools.
137A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe
138even when "dangling", thus it is a good long term reference to an element in the Dom model.
139
140Path objects are a value type that have a shared pointer to extra data if needed, thus one should
141use them as value objects.
142The implementation has still optimization potential, but the behavior for the user should be already
143the final one.
144
145Path is both a range, and a single element (a bit like strings and characters in python).
146
147The root contexts are:
148\list
149\li \l{$modules} All the known modules (even not imported), this is a global, rename independent
150 reference
151\li \l{$cpp} The Cpp names (namespaces, and Cpp types) visible in the current component
152\li \l{$libs} The plugins/libraries and their contents
153\li \l{$top} A top level entry in the DOM model, either $env or $universe (stepping in the universe
154 one looses the reference to its environment)
155\li \l{$env} The environment containing the currently available modules, i.e. the top level entry in
156 the DOM model
157\li \l{$universe} The dom unverse used by ths environment, and potentially shared with others that
158 contains all the known parse entries, and also the latest, potentially invalid entries
159\li \l{$} ? undecided, one the previous ones?
160\endlist
161
162The current contexts are:
163\list
164\li \l{@obj} The current object (if in a map or list goes up until it is in the current object) .
165\li \l{@component} The root object of the current component.
166\li \l{@module} The current module instantiation.
167\li \l{@ids} The ids in the current component.
168\li \l{@types} All the types in the current component (reachable through imports, respecting
169 renames)
170\li \l{@lookupStrict} The strict lookup inside the current object: localJS, ids, properties, proto
171 properties, component, its properties, global context, oterwise error
172\li \l{@lookupDynamic} The default lookup inside the current object: localJS, ids, properties, proto
173 properties, component, its properties, global context, ..
174\li \l{@lookup} Either lookupStrict or lookupDynamic depending on the current component and context.
175\li \l{@} ? undecided, one the previous ones
176\endlist
177 */
178
179void Base::dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const {
180 if (hasSquareBrackets)
181 sink(u"[");
182 sink(name);
183 if (hasSquareBrackets)
184 sink(u"]");
185}
186
187Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription)
189
190QString Filter::name() const {
191 return QLatin1String("?(%1)").arg(filterDescription); }
192
193bool Filter::checkName(QStringView s) const
194{
195 return s.startsWith(u"?(")
196 && s.mid(2, s.size()-3) == filterDescription
197 && s.endsWith(u")");
198}
199
205
207{
208 int k1 = static_cast<int>(p1.kind());
209 int k2 = static_cast<int>(p2.kind());
210 if (k1 < k2)
211 return -1;
212 if (k1 > k2)
213 return 1;
214 switch (p1.kind()) {
215 case Kind::Empty:
216 return 0;
217 case Kind::Field:
218 return std::get<Field>(p1.m_data).fieldName.compare(std::get<Field>(p2.m_data).fieldName);
219 case Kind::Index:
220 if (std::get<Index>(p1.m_data).indexValue < std::get<Index>(p2.m_data).indexValue)
221 return -1;
222 if (std::get<Index>(p1.m_data).indexValue > std::get<Index>(p2.m_data).indexValue)
223 return 1;
224 return 0;
225 case Kind::Key:
226 return std::get<Key>(p1.m_data).keyValue.compare(std::get<Key>(p2.m_data).keyValue);
227 case Kind::Root:
228 {
229 PathRoot k1 = std::get<Root>(p1.m_data).contextKind;
230 PathRoot k2 = std::get<Root>(p2.m_data).contextKind;
231 if (k1 == PathRoot::Env || k1 == PathRoot::Universe)
232 k1 = PathRoot::Top;
233 if (k2 == PathRoot::Env || k2 == PathRoot::Universe)
234 k2 = PathRoot::Top;
235 int c = int(k1) - int(k2);
236 if (c != 0)
237 return c;
238 return std::get<Root>(p1.m_data).contextName.compare(std::get<Root>(p2.m_data).contextName);
239 }
240 case Kind::Current:
241 {
242 int c = int(std::get<Current>(p1.m_data).contextKind)
243 - int(std::get<Current>(p2.m_data).contextKind);
244 if (c != 0)
245 return c;
246 return std::get<Current>(p1.m_data).contextName
247 .compare(std::get<Current>(p2.m_data).contextName);
248 }
249 case Kind::Any:
250 return 0;
251 case Kind::Filter:
252 {
253 int c = std::get<Filter>(p1.m_data).filterDescription
254 .compare(std::get<Filter>(p2.m_data).filterDescription);
255 if (c != 0)
256 return c;
257 if (std::get<Filter>(p1.m_data).filterDescription.startsWith(u"<")) {
258 // assuming non comparable native code (target comparison is not portable)
259 auto pp1 = &p1;
260 auto pp2 = &p2;
261 if (pp1 < pp2)
262 return -1;
263 if (pp1 > pp2)
264 return 1;
265 }
266 return 0;
267 }
268 }
269 Q_ASSERT(false && "unexpected PathComponent in PathComponent::cmp");
270 return 0;
271}
272
273} // namespace PathEls
274
275/*!
276\internal
277\brief Returns the n.th component in linear(!) time.
278
279Especially component(0)-calls have to iterate through the entire path.
280Example: returns \c d for 3 on
281\badcode
282a.b <- c.d <- e <- f
283\endcode
284*/
285const PathEls::PathComponent &Path::component(int i) const
286{
287 static Component emptyComponent;
288 if (i < 0)
289 i += m_length;
290 if (i >= m_length || i < 0) {
291 Q_ASSERT(false && "index out of bounds");
292 return emptyComponent;
293 }
294 i = i - m_length - m_endOffset;
295 auto data = m_data.get();
296 while (data) {
297 i += data->components.size();
298 if (i >= 0)
299 return std::as_const(data)->components[i];
300 data = data->parent.get();
301 }
302 Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)");
303 return emptyComponent;
304}
305
306Path Path::operator[](int i) const
307{
308 return mid(i,1);
309}
310
311QQmlJS::Dom::Path::operator bool() const
312{
313 return length() != 0;
314}
315
317{
318 return PathIterator{*this};
319}
320
322{
323 return PathIterator();
324}
325
327{
328 auto &comp = component(0);
329 if (PathEls::Root const * r = comp.asRoot())
330 return r->contextKind;
331 return PathRoot::Other;
332}
333
335{
336 auto comp = component(0);
337 if (PathEls::Current const * c = comp.asCurrent())
338 return c->contextKind;
339 return PathCurrent::Other;
340}
341
343{
344 if (m_length == 0)
345 return Path::Kind::Empty;
346 return component(0).kind();
347}
348
350{
351 return component(0).name();
352}
353
354bool Path::checkHeadName(QStringView name) const
355{
356 return component(0).checkName(name);
357}
358
359index_type Path::headIndex(index_type defaultValue) const
360{
361 return component(0).index(defaultValue);
362}
363
364function<bool(const DomItem &)> Path::headFilter() const
365{
366 auto &comp = component(0);
367 if (PathEls::Filter const * f = comp.asFilter()) {
368 return f->filterFunction;
369 }
370 return {};
371}
372
373Path Path::head() const
374{
375 return mid(0,1);
376}
377
378Path Path::last() const
379{
380 return mid(m_length-1, 1);
381}
382
383/*!
384\internal
385\brief Splits the path at the last field, root or current Component.
386*/
388{
389 int i = length();
390 while (i>0) {
391 const PathEls::PathComponent &c=component(--i);
393 return Source{mid(0, i), mid(i)};
394 }
395 }
396 return Source{Path(), *this};
397}
398
399Path Path::fromString(QStringView s, const ErrorHandler &errorHandler)
400{
401 if (s.isEmpty())
402 return Path();
403 int len=1;
404 const QChar dot = QChar::fromLatin1('.');
405 const QChar lsBrace = QChar::fromLatin1('[');
406 const QChar rsBrace = QChar::fromLatin1(']');
407 const QChar dollar = QChar::fromLatin1('$');
408 const QChar at = QChar::fromLatin1('@');
409 const QChar quote = QChar::fromLatin1('"');
410 const QChar backslash = QChar::fromLatin1('\\');
411 const QChar underscore = QChar::fromLatin1('_');
412 const QChar tilda = QChar::fromLatin1('~');
413 for (int i=0; i < s.size(); ++i)
414 if (s.at(i) == lsBrace || s.at(i) == dot)
415 ++len;
416 QVector<Component> components;
417 components.reserve(len);
418 int i = 0;
419 int i0 = 0;
421 while (i < s.size()) {
422 // skip space
423 while (i < s.size() && s.at(i).isSpace())
424 ++i;
425 if (i >= s.size())
426 break;
427 QChar c = s.at(i++);
428 switch (state) {
430 if (c == dollar) {
431 i0 = i;
432 while (i < s.size() && s.at(i).isLetterOrNumber()){
433 ++i;
434 }
435 components.append(Component(PathEls::Root(s.mid(i0,i-i0))));
437 } else if (c == at) {
438 i0 = i;
439 while (i < s.size() && s.at(i).isLetterOrNumber()){
440 ++i;
441 }
442 components.append(Component(PathEls::Current(s.mid(i0,i-i0))));
444 } else if (c.isLetter()) {
445 myErrors().warning(tr("Field expressions should start with a dot, even when at the start of the path %1.")
446 .arg(s)).handle(errorHandler);
447 return Path();
448 } else {
449 --i;
451 }
452 break;
454 if (c.isDigit()) {
455 i0 = i-1;
456 while (i < s.size() && s.at(i).isDigit())
457 ++i;
458 bool ok;
459 components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString()
460 .toLongLong(&ok))));
461 if (!ok) {
462 myErrors().warning(tr("Error extracting integer from '%1' at char %2.")
463 .arg(s.mid(i0,i-i0))
464 .arg(QString::number(i0))).handle(errorHandler);
465 }
466 } else if (c.isLetter() || c == tilda || c == underscore) {
467 i0 = i-1;
468 while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda))
469 ++i;
470 components.append(Component(PathEls::Key(s.mid(i0, i - i0).toString())));
471 } else if (c == quote) {
472 i0 = i;
473 QString strVal;
474 bool properEnd = false;
475 while (i < s.size()) {
476 c = s.at(i);
477 if (c == quote) {
478 properEnd = true;
479 break;
480 } else if (c == backslash) {
481 strVal.append(s.mid(i0, i - i0).toString());
482 c = s.at(++i);
483 i0 = i + 1;
484 if (c == QChar::fromLatin1('n'))
485 strVal.append(QChar::fromLatin1('\n'));
486 else if (c == QChar::fromLatin1('r'))
487 strVal.append(QChar::fromLatin1('\r'));
488 else if (c == QChar::fromLatin1('t'))
489 strVal.append(QChar::fromLatin1('\t'));
490 else
491 strVal.append(c);
492 }
493 ++i;
494 }
495 if (properEnd) {
496 strVal.append(s.mid(i0, i - i0).toString());
497 ++i;
498 } else {
499 myErrors().error(tr("Unclosed quoted string at char %1.")
500 .arg(QString::number(i - 1))).handle(errorHandler);
501 return Path();
502 }
503 components.append(PathEls::Key(strVal));
504 } else if (c == QChar::fromLatin1('*')) {
505 components.append(Component(PathEls::Any()));
506 } else if (c == QChar::fromLatin1('?')) {
507 while (i < s.size() && s.at(i).isSpace())
508 ++i;
509 if (i >= s.size() || s.at(i) != QChar::fromLatin1('(')) {
510 myErrors().error(tr("Expected a brace in filter after the question mark (at char %1).")
511 .arg(QString::number(i))).handle(errorHandler);
512 return Path();
513 }
514 i0 = ++i;
515 while (i < s.size() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping??
516 if (i >= s.size() || s.at(i) != QChar::fromLatin1(')')) {
517 myErrors().error(tr("Expected a closing brace in filter after the question mark (at char %1).")
518 .arg(QString::number(i))).handle(errorHandler);
519 return Path();
520 }
521 //auto filterDesc = s.mid(i0,i-i0);
522 ++i;
523 myErrors().error(tr("Filter from string not yet implemented.")).handle(errorHandler);
524 return Path();
525 } else {
526 myErrors().error(tr("Unexpected character '%1' after square bracket at %2.")
527 .arg(c).arg(i-1)).handle(errorHandler);
528 return Path();
529 }
530 while (i < s.size() && s.at(i).isSpace()) ++i;
531 if (i >= s.size() || s.at(i) != rsBrace) {
532 myErrors().error(tr("square braces misses closing brace at char %1.")
533 .arg(QString::number(i))).handle(errorHandler);
534 return Path();
535 } else {
536 ++i;
537 }
539 break;
541 if (c == dot) {
542 while (i < s.size() && s.at(i).isSpace()) ++i;
543 if (i == s.size()) {
544 components.append(Component());
546 } else if (s.at(i).isLetter() || s.at(i) == underscore || s.at(i) == tilda) {
547 i0 = i;
548 while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) {
549 ++i;
550 }
551 components.append(Component(PathEls::Field(s.mid(i0,i-i0))));
553 } else if (s.at(i).isDigit()) {
554 i0 = i;
555 while (i < s.size() && s.at(i).isDigit()){
556 ++i;
557 }
558 bool ok;
559 components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString().toLongLong(&ok))));
560 if (!ok) {
561 myErrors().warning(tr("Error extracting integer from '%1' at char %2.")
562 .arg(s.mid(i0,i-i0))
563 .arg(QString::number(i0))).handle(errorHandler);
564 return Path();
565 } else {
566 myErrors().info(tr("Index should use square brackets and not a dot (at char %1).")
567 .arg(QString::number(i0))).handle(errorHandler);
568 }
570 } else if (s.at(i) == dot || s.at(i) == lsBrace) {
571 components.append(Component());
573 } else if (s.at(i) == at) {
574 i0 = ++i;
575 while (i < s.size() && s.at(i).isLetterOrNumber()){
576 ++i;
577 }
578 components.append(Component(PathEls::Current(s.mid(i0,i-i0))));
580 } else if (s.at(i) == dollar) {
581 i0 = ++i;
582 while (i < s.size() && s.at(i).isLetterOrNumber()){
583 ++i;
584 }
585 components.append(Component(PathEls::Root(s.mid(i0,i-i0))));
587 } else {
588 c=s.at(i);
589 myErrors().error(tr("Unexpected character '%1' after dot (at char %2).")
590 .arg(QStringView(&c,1))
591 .arg(QString::number(i-1))).handle(errorHandler);
592 return Path();
593 }
594 } else if (c == lsBrace) {
596 } else {
597 myErrors().error(tr("Unexpected character '%1' after end of component (char %2).")
598 .arg(QStringView(&c,1))
599 .arg(QString::number(i-1))).handle(errorHandler);
600 return Path();
601 }
602 break;
603 }
604 }
605 switch (state) {
607 return Path();
609 errorHandler(myErrors().error(tr("unclosed square brace at end.")));
610
611 return Path();
613 return Path(0, components.size(), std::make_shared<PathEls::PathData>(
614 QStringList(), components));
615 }
616 Q_ASSERT(false && "Unexpected state in Path::fromString");
617 return Path();
618}
619
621{
622 return Path(0,1,std::make_shared<PathEls::PathData>(
623 QStringList(), QVector<Component>(1,Component(PathEls::Root(s)))));
624}
625
626Path Path::fromRoot(const QString &s)
627{
628 return Path(0,1,std::make_shared<PathEls::PathData>(
629 QStringList(s), QVector<Component>(1,Component(PathEls::Root(s)))));
630}
631
632Path Path::fromIndex(index_type i)
633{
634 return Path(0,1,std::make_shared<PathEls::PathData>(
635 QStringList(), QVector<Component>(1,Component(PathEls::Index(i)))));
636}
637
638Path Path::fromRoot(QStringView s)
639{
640 return Path(0,1,std::make_shared<PathEls::PathData>(
641 QStringList(), QVector<Component>(1,Component(PathEls::Root(s)))));
642}
643
644
645Path Path::fromField(QStringView s)
646{
647 return Path(0,1,std::make_shared<PathEls::PathData>(
648 QStringList(), QVector<Component>(1,Component(PathEls::Field(s)))));
649}
650
651Path Path::fromField(const QString &s)
652{
653 return Path(0,1,std::make_shared<PathEls::PathData>(
654 QStringList(s), QVector<Component>(1,Component(PathEls::Field(s)))));
655}
656
657Path Path::fromKey(QStringView s)
658{
659 return Path(
660 0, 1,
661 std::make_shared<PathEls::PathData>(
662 QStringList(), QVector<Component>(1, Component(PathEls::Key(s.toString())))));
663}
664
665Path Path::fromKey(const QString &s)
666{
667 return Path(0, 1,
668 std::make_shared<PathEls::PathData>(
669 QStringList(), QVector<Component>(1, Component(PathEls::Key(s)))));
670}
671
673{
674 return Path(0,1,std::make_shared<PathEls::PathData>(
675 QStringList(), QVector<Component>(1,Component(PathEls::Current(s)))));
676}
677
678Path Path::fromCurrent(const QString &s)
679{
680 return Path(0,1,std::make_shared<PathEls::PathData>(
681 QStringList(s), QVector<Component>(1,Component(PathEls::Current(s)))));
682}
683
684Path Path::fromCurrent(QStringView s)
685{
686 return Path(0,1,std::make_shared<PathEls::PathData>(
687 QStringList(), QVector<Component>(1,Component(PathEls::Current(s)))));
688}
689
691{
692 if (m_endOffset != 0)
693 return noEndOffset().withEmpty();
694 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
695 QStringList(), QVector<Component>(1,Component()), m_data));
696}
697
698Path Path::withField(const QString &name) const
699{
700 auto res = withField(QStringView(name));
701 res.m_data->strData.append(name);
702 return res;
703}
704
705Path Path::withField(QStringView name) const
706{
707 if (m_endOffset != 0)
708 return noEndOffset().withField(name);
709 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
710 QStringList(), QVector<Component>(1,Component(PathEls::Field(name))), m_data));
711}
712
713Path Path::withKey(const QString &name) const
714{
715 if (m_endOffset != 0)
716 return noEndOffset().withKey(name);
717 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
718 QStringList(), QVector<Component>(1,Component(PathEls::Key(name))), m_data));
719}
720
721Path Path::withKey(QStringView name) const
722{
723 return withKey(name.toString());
724}
725
726Path Path::withIndex(index_type i) const
727{
728 if (m_endOffset != 0)
729 return noEndOffset().withIndex(i);
730 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
731 QStringList(), QVector<Component>(1,Component(i)), m_data));
732}
733
735{
736 if (m_endOffset != 0)
737 return noEndOffset().withAny();
738 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
739 QStringList(), QVector<Component>(1,Component(PathEls::Any())), m_data));
740}
741
742Path Path::withFilter(const function<bool(const DomItem &)> &filterF, const QString &desc) const
743{
744 auto res = withFilter(filterF, QStringView(desc));
745 res.m_data->strData.append(desc);
746 return res;
747}
748
749Path Path::withFilter(const function<bool(const DomItem &)> &filter, QStringView desc) const
750{
751 if (m_endOffset != 0)
752 return noEndOffset().withFilter(filter, desc);
753 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
754 QStringList(), QVector<Component>(1,Component(PathEls::Filter(filter, desc))), m_data));
755}
756
758{
759 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
760 QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data));
761}
762
763Path Path::withCurrent(const QString &s) const
764{
765 auto res = withCurrent(QStringView(s));
766 res.m_data->strData.append(s);
767 return res;
768}
769
770Path Path::withCurrent(QStringView s) const
771{
772 if (m_endOffset != 0)
773 return noEndOffset().withCurrent(s);
774 return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
775 QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data));
776}
777
778static QVector<Path::Component>
779reverseAndConcat(const std::vector<QVector<Path::Component> *> &list, qsizetype size)
780{
781 using Component = Path::Component;
782
783 QVector<Component> components;
784 components.reserve(size);
785 for (auto it = list.crbegin(), end = list.crend(); it != end; ++it) {
786 for (auto it2 = (**it).cbegin(), end2 = (**it).cend(); it2 != end2; ++it2) {
787 components.append(*it2);
788 if (components.size() >= size)
789 return components;
790 }
791 }
792 return components;
793}
794
796collectBackwards(PathEls::PathData *pathStart, qsizetype size)
797{
798 using Component = Path::Component;
799 std::vector<QVector<Component> *> componentList;
800 qsizetype foundComponents = 0;
801 QStringList addedStrs;
802 for (auto data = pathStart; data; data = data->parent.get()) {
803 addedStrs.append(data->strData);
804 componentList.push_back(&data->components);
805 foundComponents += data->components.size();
806
807 if (foundComponents >= size)
808 return std::make_pair(componentList, addedStrs);
809 }
810 return std::make_pair(componentList, addedStrs);
811}
812
813/*!
814\internal
815\brief Returns a copy of this with \c toAdd appended to it
816
817Compresses \c toAdd into one vector of components before adding it to \c this.
818*/
819Path Path::withPath(const Path &toAdd, bool avoidToAddAsBase) const
820{
821 if (toAdd.length() == 0)
822 return *this;
823 const int resLength = length() + toAdd.length();
824 if (m_endOffset != 0) {
825 Path thisExtended = this->expandBack();
826 if (thisExtended.length() > resLength)
827 thisExtended = thisExtended.mid(0, resLength);
828 Path added = thisExtended.mid(length(), thisExtended.length() - length());
829 if (added == toAdd.mid(0, toAdd.length())) {
830 if (resLength == thisExtended.length())
831 return thisExtended;
832 else
833 return thisExtended.withPath(toAdd.mid(added.length(), resLength - thisExtended.length()));
834 }
835 }
836 if (!avoidToAddAsBase) {
837 Path toAddExtended = toAdd.expandFront();
838 if (toAddExtended.length() >= resLength) {
839 toAddExtended = toAddExtended.mid(toAddExtended.length() - resLength, resLength);
840 if (toAddExtended.mid(0,length()) == *this)
841 return toAddExtended;
842 }
843 }
844
845 const Path toAddNoEndOffset = toAdd.noEndOffset();
846 auto [componentListList, addedStrs] =
847 collectBackwards(toAddNoEndOffset.m_data.get(), toAdd.length());
848
849 QVector<Component> componentList = reverseAndConcat(componentListList, toAdd.length());
850
851 Path result(
852 0, resLength,
853 std::make_shared<PathEls::PathData>(addedStrs, componentList, noEndOffset().m_data));
854 return result;
855}
856
857/*!
858\internal
859\brief Expand a path prefix hidden by slicing
860
861This sets m_length to the maximum length this path can have.
862
863For example, for a path
864\badcode
865b -> c -> d when encoded as a -> b -> c -> d -> e -> f, m_endOffset = 2, m_length = 3
866\endcode
867expandFront() will return
868\badcode
869a -> b -> c -> d when encoded as a -> b -> c -> d -> e -> f, m_endOffset = 2, m_length = 4
870\endcode
871*/
873{
874 int newLen = 0;
875 auto data = m_data.get();
876 while (data) {
877 newLen += data->components.size();
878 data = data->parent.get();
879 }
880 newLen -= m_endOffset;
881 return Path(m_endOffset, newLen, m_data);
882}
883
884/*!
885\internal
886\brief Expand a path suffix hidden by slicing.
887
888This sets m_endOffset to 0.
889
890For example, for a path
891\badcode
892c -> d when encoded as a -> b -> c -> d -> e -> f -> g, m_endOffset = 3, m_length = 2
893\endcode
894expandBack() will return
895\badcode
896c -> d -> e -> f -> g when encoded as a -> b -> c -> d -> e -> f -> g, m_endOffset = 0, m_length = 5
897\endcode
898*/
900{
901 if (m_endOffset > 0)
902 return Path(0, m_length + m_endOffset, m_data);
903 return *this;
904}
905
907{
908 if (m_length > 0)
909 m_length -= 1;
910 return *this;
911}
912
914{
915 Path res = *this;
916 if (m_length > 0)
917 m_length -= 1;
918 return res;
919}
920
921int Path::cmp(const Path &p1, const Path &p2)
922{
923 // lexicographic ordering
924 const int lMin = qMin(p1.m_length, p2.m_length);
925 if (p1.m_data.get() == p2.m_data.get() && p1.m_endOffset == p2.m_endOffset && p1.m_length == p2.m_length)
926 return 0;
927 for (int i = 0; i < lMin; ++i) {
928 int c = Component::cmp(p1.component(i), p2.component(i));
929 if (c != 0)
930 return c;
931 }
932 if (lMin < p2.m_length)
933 return -1;
934 if (p1.m_length > lMin)
935 return 1;
936 return 0;
937}
938
939Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data)
940 :m_endOffset(endOffset), m_length(length), m_data(data)
941{
942}
943
944Path Path::noEndOffset() const
945{
946 if (m_length == 0)
947 return Path();
948 if (m_endOffset == 0)
949 return *this;
950 // peel back
951 qint16 endOffset = m_endOffset;
952 std::shared_ptr<PathEls::PathData> lastData = m_data;
953 while (lastData && endOffset >= lastData->components.size()) {
954 endOffset -= lastData->components.size();
955 lastData = lastData->parent;
956 }
957 if (endOffset > 0) {
958 Q_ASSERT(lastData && "Internal problem, reference to non existing PathData");
959 return Path(0, m_length, std::make_shared<PathEls::PathData>(
960 lastData->strData, lastData->components.mid(0, lastData->components.size() - endOffset), lastData->parent));
961 }
962 return Path(0, m_length, lastData);
963}
964
966{
967 if (m_endOffset != 0) {
968 Path newP = noEndOffset();
969 return newP.withComponent(c);
970 }
971 if (m_data && m_data.use_count() != 1) {
972 // create a new path (otherwise paths linking to this will change)
973 Path newP(c);
974 newP.m_data->parent = m_data;
975 newP.m_length = static_cast<quint16>(m_length + 1);
976 return newP;
977 }
978 auto my_data =
979 (m_data ? m_data
980 : std::make_shared<PathEls::PathData>(QStringList(),
981 QVector<PathEls::PathComponent>()));
982 switch (c.kind()) {
986 // no string
988 // string assumed to stay valid (Fields::...)
989 my_data->components.append(c);
990 break;
993 my_data->strData.append(c.asCurrent()->contextName.toString());
994 my_data->components.append(PathEls::Current(my_data->strData.last()));
995 } else {
996 my_data->components.append(c);
997 }
998 break;
1000 if (!c.asFilter()->filterDescription.isEmpty()) {
1001 my_data->strData.append(c.asFilter()->filterDescription.toString());
1002 my_data->components.append(
1003 PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last()));
1004 } else {
1005 my_data->components.append(c);
1006 }
1007 break;
1008 case PathEls::Kind::Key:
1009 my_data->components.append(c);
1010 break;
1013 my_data->strData.append(c.asRoot()->contextName.toString());
1014 my_data->components.append(PathEls::Root(my_data->strData.last()));
1015 } else {
1016 my_data->components.append(c);
1017 }
1018 break;
1019 }
1020 if (m_data)
1021 m_endOffset = 1;
1022 return Path { 0, static_cast<quint16>(m_length + 1), my_data };
1023}
1024
1026{
1027 static ErrorGroups res = {{NewErrorGroup("PathParsing")}};
1028 return res;
1029}
1030
1031void Path::dump(const Sink &sink) const
1032{
1033 bool first = true;
1034 for (int i = 0; i < m_length; ++i) {
1035 auto &c = component(i);
1036 if (!c.hasSquareBrackets()) {
1037 if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current))
1038 sink(u".");
1039 }
1040 c.dump(sink);
1041 first = false;
1042 }
1043}
1044
1046{
1047 QString res;
1048 QTextStream stream(&res);
1049 dump([&stream](QStringView str){ stream << str; });
1050 stream.flush();
1051 return res;
1052}
1053
1054Path Path::dropFront(int n) const
1055{
1056 if (m_length > n && n >= 0)
1057 return Path(m_endOffset, m_length - n, m_data);
1058 return Path();
1059}
1060
1061Path Path::dropTail(int n) const
1062{
1063 if (m_length > n && n >= 0)
1064 return Path(m_endOffset + n, m_length - n, m_data);
1065 return Path();
1066}
1067
1068Path Path::mid(int offset, int length) const
1069{
1070 length = qMin(m_length - offset, length);
1071 if (offset < 0 || offset >= m_length || length <= 0 || length > m_length)
1072 return Path();
1073 int newEndOffset = m_endOffset + m_length - offset - length;
1074 return Path(newEndOffset, length, m_data);
1075}
1076
1077Path Path::mid(int offset) const
1078{
1079 return mid(offset, m_length - offset);
1080}
1081
1082Path Path::fromString(const QString &s, const ErrorHandler &errorHandler)
1083{
1084 Path res = fromString(QStringView(s), errorHandler);
1085 if (res.m_data)
1086 res.m_data->strData.append(s);
1087 return res;
1088}
1089
1090} // end namespace Dom
1091} // end namespace QQmlJS
1092QT_END_NAMESPACE
1093
1094#include "moc_qqmldompath_p.cpp"
A value type that references any element of the Dom.
Represents a set of tags grouping a set of related error messages.
ErrorMessage warning(const Dumper &message) const
ErrorMessage error(const Dumper &message) const
Represents an error message connected to the dom.
void dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const
bool checkName(QStringView s) const
Filter(const std::function< bool(const DomItem &)> &f, QStringView filterDescription=u"<native code filter>")
static int cmp(const PathComponent &p1, const PathComponent &p2)
const Current * asCurrent() const
Path withCurrent(const QString &s) const
PathCurrent headCurrent() const
Path dropTail(int n=1) const
Path withEmpty() const
index_type headIndex(index_type defaultValue=-1) const
Path withIndex(index_type i) const
static int cmp(const Path &p1, const Path &p2)
Path expandBack() const
Expand a path suffix hidden by slicing.
Path withKey(QStringView name) const
Path withPath(const Path &toAdd, bool avoidToAddAsBase=false) const
Returns a copy of this with toAdd appended to it.
QString headName() const
Source split() const
Splits the path at the last field, root or current Component.
PathIterator begin() const
PathEls::Kind Kind
static Path fromRoot(PathRoot r)
Path withComponent(const PathEls::PathComponent &c)
static ErrorGroups myErrors()
Path withAny() const
bool checkHeadName(QStringView name) const
Path operator[](int i) const
Path withKey(const QString &name) const
Path head() const
Path withCurrent(QStringView s=u"") const
Path mid(int offset, int length) const
Path expandFront() const
Expand a path prefix hidden by slicing.
Path withFilter(const std::function< bool(const DomItem &)> &, QStringView desc=u"<native code filter>") const
Path withCurrent(PathCurrent s) const
void dump(const Sink &sink) const
Path last() const
Kind headKind() const
Path withFilter(const std::function< bool(const DomItem &)> &, const QString &) const
Path mid(int offset) const
QString toString() const
PathIterator end() const
Path withField(QStringView name) const
std::function< bool(const DomItem &)> headFilter() const
Path dropFront(int n=1) const
Path withField(const QString &name) const
PathEls::PathComponent Component
static Path fromCurrent(PathCurrent c)
PathRoot headRoot() const
Path(const PathEls::PathComponent &c)
static QVector< Path::Component > reverseAndConcat(const std::vector< QVector< Path::Component > * > &list, qsizetype size)
std::function< void(const ErrorMessage &)> ErrorHandler
bool operator==(const Path &lhs, const Path &rhs)
static std::pair< std::vector< QVector< Path::Component > * >, QStringList > collectBackwards(PathEls::PathData *pathStart, qsizetype size)
#define NewErrorGroup(name)