ACAV 69797fb42a2b
Abstract Syntax Tree (AST) visualization tool for C, C++, and Objective-C
Loading...
Searching...
No Matches
SourceLocationIndex.cpp
1/*$!{
2* Aurora Clang AST Viewer (ACAV)
3*
4* Copyright (c) 2026 Min Liu
5* Copyright (c) 2026 Michael David Adams
6*
7* SPDX-License-Identifier: GPL-2.0-or-later
8*
9* This program is free software; you can redistribute it and/or modify
10* it under the terms of the GNU General Public License as published by
11* the Free Software Foundation; either version 2 of the License, or
12* (at your option) any later version.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License along
20* with this program; if not, see <https://www.gnu.org/licenses/>.
21}$!*/
22
24#include "core/AstNode.h"
25#include <algorithm>
26#include <utility>
27
28namespace acav {
29
30// ============================================================================
31// Interval Implementation
32// ============================================================================
33
34bool Interval::contains(unsigned line, unsigned column) const {
35 // Check if point is after or at begin
36 bool afterBegin = (line > beginLine) ||
37 (line == beginLine && column >= beginColumn);
38
39 // Check if point is before or at end
40 bool beforeEnd = (line < endLine) || (line == endLine && column <= endColumn);
41
42 return afterBegin && beforeEnd;
43}
44
45bool Interval::operator<(const Interval &other) const {
46 if (beginLine != other.beginLine) {
47 return beginLine < other.beginLine;
48 }
49 return beginColumn < other.beginColumn;
50}
51
52// ============================================================================
53// IntervalTree Implementation
54// ============================================================================
55
56IntervalTree::SourcePoint IntervalTree::beginPoint(const Interval &interval) {
57 return {interval.beginLine, interval.beginColumn};
58}
59
60IntervalTree::SourcePoint IntervalTree::endPoint(const Interval &interval) {
61 return {interval.endLine, interval.endColumn};
62}
63
64bool IntervalTree::pointLess(SourcePoint lhs, SourcePoint rhs) {
65 if (lhs.line != rhs.line) {
66 return lhs.line < rhs.line;
67 }
68 return lhs.column < rhs.column;
69}
70
71bool IntervalTree::startsAfter(const Interval &interval, SourcePoint point) {
72 return pointLess(point, beginPoint(interval));
73}
74
75bool IntervalTree::endsBefore(const Interval &interval, SourcePoint point) {
76 return pointLess(endPoint(interval), point);
77}
78
79bool IntervalTree::intervalEndGreater(const Interval &lhs,
80 const Interval &rhs) {
81 SourcePoint lhsEnd = endPoint(lhs);
82 SourcePoint rhsEnd = endPoint(rhs);
83 if (lhsEnd.line != rhsEnd.line) {
84 return lhsEnd.line > rhsEnd.line;
85 }
86 if (lhsEnd.column != rhsEnd.column) {
87 return lhsEnd.column > rhsEnd.column;
88 }
89 return pointLess(beginPoint(rhs), beginPoint(lhs));
90}
91
93 intervals_.push_back(interval);
94}
95
97 if (finalized_) {
98 return;
99 }
100
101 // Sort intervals by start position
102 std::sort(intervals_.begin(), intervals_.end());
103 queryRoot_ = buildQueryTree(intervals_);
104 finalized_ = true;
105}
106
107std::vector<AstViewNode *> IntervalTree::query(unsigned line,
108 unsigned column) const {
109 std::vector<AstViewNode *> results;
110
111 queryPoint(queryRoot_.get(), SourcePoint{line, column}, results);
112
113 // Sort by depth (deepest first = most specific)
114 std::sort(results.begin(), results.end(),
115 [this](AstViewNode *a, AstViewNode *b) {
116 return getDepth(a) > getDepth(b);
117 });
118
119 return results;
120}
121
122std::unique_ptr<IntervalTree::QueryNode>
123IntervalTree::buildQueryTree(std::vector<Interval> intervals) {
124 if (intervals.empty()) {
125 return nullptr;
126 }
127
128 SourcePoint center = beginPoint(intervals[intervals.size() / 2]);
129 std::vector<Interval> left;
130 std::vector<Interval> right;
131 std::vector<Interval> crossing;
132 left.reserve(intervals.size() / 2);
133 right.reserve(intervals.size() / 2);
134 crossing.reserve(intervals.size());
135
136 for (const Interval &interval : intervals) {
137 if (endsBefore(interval, center)) {
138 left.push_back(interval);
139 } else if (startsAfter(interval, center)) {
140 right.push_back(interval);
141 } else {
142 crossing.push_back(interval);
143 }
144 }
145
146 auto node = std::make_unique<QueryNode>();
147 node->center = center;
148 node->byBegin = std::move(crossing);
149 node->byEndDescending = node->byBegin;
150 std::sort(node->byEndDescending.begin(), node->byEndDescending.end(),
151 intervalEndGreater);
152 node->left = buildQueryTree(std::move(left));
153 node->right = buildQueryTree(std::move(right));
154 return node;
155}
156
157void IntervalTree::queryPoint(const QueryNode *node, SourcePoint point,
158 std::vector<AstViewNode *> &results) {
159 if (!node) {
160 return;
161 }
162
163 if (pointLess(point, node->center)) {
164 for (const Interval &interval : node->byBegin) {
165 if (startsAfter(interval, point)) {
166 break;
167 }
168 results.push_back(interval.node);
169 }
170 queryPoint(node->left.get(), point, results);
171 return;
172 }
173
174 if (pointLess(node->center, point)) {
175 for (const Interval &interval : node->byEndDescending) {
176 if (endsBefore(interval, point)) {
177 break;
178 }
179 results.push_back(interval.node);
180 }
181 queryPoint(node->right.get(), point, results);
182 return;
183 }
184
185 for (const Interval &interval : node->byBegin) {
186 results.push_back(interval.node);
187 }
188}
189
191 unsigned beginColumn,
192 unsigned endLine,
193 unsigned endColumn) const {
194 // Find first interval starting at or after (beginLine, beginColumn)
195 Interval searchKey{beginLine, beginColumn, 0, 0, nullptr};
196 auto it = std::lower_bound(intervals_.begin(), intervals_.end(), searchKey);
197
198 for (; it != intervals_.end(); ++it) {
199 // Stop if interval starts after range end
200 if (it->beginLine > endLine ||
201 (it->beginLine == endLine && it->beginColumn > endColumn)) {
202 break;
203 }
204
205 // Check if interval is fully contained: start >= range start, end <= range end
206 bool startsInRange = it->beginLine > beginLine ||
207 (it->beginLine == beginLine && it->beginColumn >= beginColumn);
208 bool endsInRange = it->endLine < endLine ||
209 (it->endLine == endLine && it->endColumn <= endColumn);
210
211 if (startsInRange && endsInRange) {
212 return it->node;
213 }
214 }
215
216 return nullptr;
217}
218
219unsigned IntervalTree::getDepth(AstViewNode *node) const {
220 if (!node) {
221 return 0;
222 }
223
224 unsigned depth = 0;
225 AstViewNode *current = node;
226 while (current->getParent()) {
227 ++depth;
228 current = current->getParent();
229 }
230 return depth;
231}
232
233// ============================================================================
234// SourceLocationIndex Implementation
235// ============================================================================
236
238 if (!node) {
239 return;
240 }
241
242 const SourceRange &range = node->getSourceRange();
243 FileID fileId = range.begin().fileID();
244
245 // Skip invalid locations (compiler-generated nodes)
246 if (fileId == FileManager::InvalidFileID) {
247 return;
248 }
249
250 bool endsBeforeStart =
251 range.end().line() < range.begin().line() ||
252 (range.end().line() == range.begin().line() &&
253 range.end().column() < range.begin().column());
254 if (endsBeforeStart) {
255 return;
256 }
257
258 // Create interval
259 Interval interval{range.begin().line(), range.begin().column(),
260 range.end().line(), range.end().column(), node};
261
262 // Insert into appropriate tree (create if needed)
263 trees_[fileId].insert(interval);
264}
265
267 // Finalize all trees (sort intervals)
268 for (auto &[fileId, tree] : trees_) {
269 tree.finalize();
270 }
271}
272
273std::vector<AstViewNode *> SourceLocationIndex::getNodesAt(FileID fileId,
274 unsigned line,
275 unsigned column) const {
276 auto it = trees_.find(fileId);
277 if (it == trees_.end()) {
278 return {}; // No tree for this file
279 }
280
281 return it->second.query(line, column);
282}
283
285 FileID fileId, unsigned beginLine, unsigned beginColumn, unsigned endLine,
286 unsigned endColumn) const {
287 auto it = trees_.find(fileId);
288 if (it == trees_.end()) {
289 return nullptr;
290 }
291
292 return it->second.queryFirstContained(beginLine, beginColumn, endLine,
293 endColumn);
294}
295
297 std::size_t total = 0;
298 for (const auto &[fileId, tree] : trees_) {
299 total += tree.size();
300 }
301 return total;
302}
303
305 return trees_.find(fileId) != trees_.end();
306}
307
308} // namespace acav
AST data structures and memory management.
std::size_t FileID
Type-safe identifier for registered files. 0 is reserved for invalid.
Definition FileManager.h:38
Source-to-AST node lookup index.
Represents node in AST tree hierarchy.
Definition AstNode.h:195
const SourceRange & getSourceRange() const
Get source range (delegates to data node).
Definition AstNode.h:219
static constexpr FileID InvalidFileID
Reserved invalid FileID.
Definition FileManager.h:47
AstViewNode * queryFirstContained(unsigned beginLine, unsigned beginColumn, unsigned endLine, unsigned endColumn) const
Find first interval fully contained within a range.
void finalize()
Sort intervals by start position (call once after all inserts).
std::vector< AstViewNode * > query(unsigned line, unsigned column) const
Find all intervals containing a point.
void insert(Interval interval)
Add interval to collection (not sorted yet).
void finalize()
Finalize all interval trees (call after all nodes added).
void addNode(AstViewNode *node)
Add node to index.
std::vector< AstViewNode * > getNodesAt(FileID fileId, unsigned line, unsigned column) const
Query nodes at specific source position.
bool hasFile(FileID fileId) const
Check whether the current TU AST contains any indexed nodes for a file.
std::size_t getTotalIntervals() const
Get total number of intervals across all files.
AstViewNode * getFirstNodeContainedInRange(FileID fileId, unsigned beginLine, unsigned beginColumn, unsigned endLine, unsigned endColumn) const
Query first node fully contained within a range.
Represents a span of source code (begin to end).
Represents an interval in source code with associated AST node.
bool operator<(const Interval &other) const
Compare intervals by start position for sorting.
bool contains(unsigned line, unsigned column) const
Check if interval contains a point.