1 | // Copyright (C) 2021 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 |
3 | |
4 | #ifndef QQMLJSSOURCELOCATION_P_H |
5 | #define QQMLJSSOURCELOCATION_P_H |
6 | |
7 | #include <QtCore/private/qglobal_p.h> |
8 | #include <QtCore/qhashfunctions.h> |
9 | |
10 | // |
11 | // W A R N I N G |
12 | // ------------- |
13 | // |
14 | // This file is not part of the Qt API. It exists purely as an |
15 | // implementation detail. This header file may change from version to |
16 | // version without notice, or even be removed. |
17 | // |
18 | // We mean it. |
19 | // |
20 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | namespace QQmlJS { |
24 | |
25 | class SourceLocation |
26 | { |
27 | public: |
28 | explicit SourceLocation(quint32 offset = 0, quint32 length = 0, quint32 line = 0, quint32 column = 0) |
29 | : offset(offset), length(length), |
30 | startLine(line), startColumn(column) |
31 | { } |
32 | |
33 | private: |
34 | struct LocationInfo |
35 | { |
36 | quint32 offset; |
37 | quint32 startLine; |
38 | quint32 startColumn; |
39 | }; |
40 | |
41 | template <typename Predicate> |
42 | static LocationInfo findLocationIf(QStringView text, Predicate &&predicate, |
43 | const SourceLocation &startHint = SourceLocation{}) |
44 | { |
45 | quint32 i = startHint.isValid() ? startHint.offset : 0; |
46 | quint32 endLine = startHint.isValid() ? startHint.startLine : 1; |
47 | quint32 endColumn = startHint.isValid() ? startHint.startColumn : 1; |
48 | const quint32 end = quint32(text.size()); |
49 | |
50 | for (; i < end; ++i) { |
51 | if (predicate(i, endLine, endColumn)) |
52 | return LocationInfo{ .offset: i, .startLine: endLine, .startColumn: endColumn }; |
53 | |
54 | const QChar currentChar = text.at(n: i); |
55 | const bool isLineFeed = currentChar == u'\n'; |
56 | const bool isCarriageReturn = currentChar == u'\r'; |
57 | // note: process the newline on the "\n" part of "\r\n", and treat "\r" as normal |
58 | // character |
59 | const bool isHalfANewline = |
60 | isCarriageReturn && (i + 1 < end && text.at(n: i + 1) == u'\n'); |
61 | |
62 | if (isHalfANewline || (!isCarriageReturn && !isLineFeed)) { |
63 | ++endColumn; |
64 | continue; |
65 | } |
66 | |
67 | // catch positions after the end of the line |
68 | if (predicate(i, endLine, std::numeric_limits<quint32>::max())) |
69 | return LocationInfo{ .offset: i, .startLine: endLine, .startColumn: endColumn }; |
70 | |
71 | // catch positions after the end of the file and return the last character of the last |
72 | // line |
73 | if (i == end - 1) { |
74 | if (predicate(i, std::numeric_limits<quint32>::max(), |
75 | std::numeric_limits<quint32>::max())) { |
76 | return LocationInfo{ .offset: i, .startLine: endLine, .startColumn: endColumn }; |
77 | } |
78 | } |
79 | |
80 | ++endLine; |
81 | endColumn = 1; |
82 | } |
83 | |
84 | // not found, return last position |
85 | return LocationInfo{ .offset: i, .startLine: endLine, .startColumn: endColumn }; |
86 | } |
87 | |
88 | public: |
89 | static quint32 offsetFrom(QStringView text, quint32 line, quint32 column, |
90 | const SourceLocation &startHint = SourceLocation{}) |
91 | { |
92 | // sanity check that hint can actually be used |
93 | const SourceLocation hint = |
94 | (startHint.startLine < line |
95 | || (startHint.startLine == line && startHint.startColumn <= column)) |
96 | ? startHint |
97 | : SourceLocation{}; |
98 | |
99 | const auto result = findLocationIf( |
100 | text, |
101 | predicate: [line, column](quint32, quint32 currentLine, quint32 currentColumn) { |
102 | return line <= currentLine && column <= currentColumn; |
103 | }, |
104 | startHint: hint); |
105 | return result.offset; |
106 | } |
107 | static std::pair<quint32, quint32> |
108 | rowAndColumnFrom(QStringView text, quint32 offset, |
109 | const SourceLocation &startHint = SourceLocation{}) |
110 | { |
111 | // sanity check that hint can actually be used |
112 | const SourceLocation hint = startHint.offset <= offset ? startHint : SourceLocation{}; |
113 | |
114 | const auto result = findLocationIf( |
115 | text, |
116 | predicate: [offset](quint32 currentOffset, quint32, quint32) { |
117 | return offset == currentOffset; |
118 | }, |
119 | startHint: hint); |
120 | return std::make_pair(x: result.startLine, y: result.startColumn); |
121 | } |
122 | |
123 | bool isValid() const { return *this != SourceLocation(); } |
124 | |
125 | quint32 begin() const { return offset; } |
126 | quint32 end() const { return offset + length; } |
127 | |
128 | // Returns a zero length location at the start of the current one. |
129 | SourceLocation startZeroLengthLocation() const |
130 | { |
131 | return SourceLocation(offset, 0, startLine, startColumn); |
132 | } |
133 | // Returns a zero length location at the end of the current one. |
134 | SourceLocation endZeroLengthLocation(QStringView text) const |
135 | { |
136 | auto [row, column] = rowAndColumnFrom(text, offset: offset + length, startHint: *this); |
137 | return SourceLocation{ offset + length, 0, row, column }; |
138 | } |
139 | |
140 | // attributes |
141 | // ### encode |
142 | quint32 offset; |
143 | quint32 length; |
144 | quint32 startLine; |
145 | quint32 startColumn; |
146 | |
147 | friend size_t qHash(const SourceLocation &location, size_t seed = 0) |
148 | { |
149 | return qHashMulti(seed, args: location.offset, args: location.length, |
150 | args: location.startLine, args: location.startColumn); |
151 | } |
152 | |
153 | friend bool operator==(const SourceLocation &a, const SourceLocation &b) |
154 | { |
155 | return a.offset == b.offset && a.length == b.length |
156 | && a.startLine == b.startLine && a.startColumn == b.startColumn; |
157 | } |
158 | |
159 | friend bool operator!=(const SourceLocation &a, const SourceLocation &b) { return !(a == b); } |
160 | |
161 | // Returns a source location starting at the beginning of l1, l2 and ending at the end of them. |
162 | // Ignores invalid source locations. |
163 | friend SourceLocation combine(const SourceLocation &l1, const SourceLocation &l2) { |
164 | quint32 e = qMax(a: l1.end(), b: l2.end()); |
165 | SourceLocation res; |
166 | if (l1.offset <= l2.offset) |
167 | res = (l1.isValid() ? l1 : l2); |
168 | else |
169 | res = (l2.isValid() ? l2 : l1); |
170 | res.length = e - res.offset; |
171 | return res; |
172 | } |
173 | }; |
174 | |
175 | } // namespace QQmlJS |
176 | |
177 | QT_END_NAMESPACE |
178 | |
179 | #endif |
180 | |