Port SelectionTree from clangd to clice (#166)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "AST/SourceCode.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
|
||||
@@ -7,40 +8,137 @@ namespace clice::testing {
|
||||
|
||||
struct AnnotatedSource {
|
||||
std::string content;
|
||||
/// All named offsets
|
||||
llvm::StringMap<std::uint32_t> offsets;
|
||||
|
||||
llvm::StringMap<LocalSourceRange> ranges;
|
||||
|
||||
std::vector<std::uint32_t> nameless_offsets;
|
||||
|
||||
/// Point Annotation:
|
||||
/// - $(key): Marks a single point.
|
||||
///
|
||||
/// Range Annotation:
|
||||
/// - @key[...content...]: Marks a range.
|
||||
///
|
||||
/// A range annotation for 'key' creates both a `ranges["key"]` and an `offsets["key"]`
|
||||
/// (pointing to the start).
|
||||
static AnnotatedSource from(llvm::StringRef content) {
|
||||
std::string source;
|
||||
llvm::StringMap<std::uint32_t> offsets;
|
||||
|
||||
source.reserve(content.size());
|
||||
|
||||
llvm::StringMap<std::uint32_t> offsets;
|
||||
llvm::StringMap<LocalSourceRange> ranges;
|
||||
std::vector<std::uint32_t> nameless_offsets;
|
||||
|
||||
std::uint32_t offset = 0;
|
||||
for(uint32_t i = 0; i < content.size();) {
|
||||
auto c = content[i];
|
||||
std::uint32_t i = 0;
|
||||
|
||||
// Helper lambda to parse a point annotation $(key).
|
||||
// It captures all necessary variables by reference.
|
||||
// Returns true if a point was successfully parsed, false otherwise.
|
||||
auto try_parse_point_annotation = [&]() -> bool {
|
||||
if(content[i] != '$') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Peek ahead to see if it's "$(key)" or just "$"
|
||||
if(i + 1 < content.size() && content[i + 1] == '(') {
|
||||
// It's the full "$(key)" syntax
|
||||
uint32_t key_start = i + 2;
|
||||
size_t key_end = content.find(')', key_start);
|
||||
|
||||
if(key_end == llvm::StringRef::npos) {
|
||||
return false;
|
||||
} // Malformed
|
||||
|
||||
llvm::StringRef key = content.slice(key_start, key_end);
|
||||
/// empty key is regarded as a nameless, and `()` is not consumed.
|
||||
if(key.empty()) {
|
||||
// It's the shorthand "$" syntax for an nameless key
|
||||
nameless_offsets.emplace_back(offset);
|
||||
i += 1; // Advance cursor past the single '$'
|
||||
} else {
|
||||
offsets.try_emplace(key, offset);
|
||||
i = key_end + 1; // Advance cursor past the entire "$(key)"
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// It's the shorthand "$" syntax for an nameless key
|
||||
nameless_offsets.emplace_back(offset);
|
||||
i += 1; // Advance cursor past the single '$'
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
while(i < content.size()) {
|
||||
// Check for a point annotation first.
|
||||
if(try_parse_point_annotation()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char c = content[i];
|
||||
|
||||
// Handle Range: @key[...]
|
||||
if(c == '@') {
|
||||
// Skip '@'
|
||||
i += 1;
|
||||
auto key = content.substr(i).take_until([](char c) { return c == ' '; });
|
||||
offsets.try_emplace(key, offset);
|
||||
|
||||
const char open_bracket = '[';
|
||||
const char close_bracket = ']';
|
||||
|
||||
llvm::StringRef key = content.substr(i).take_until(
|
||||
[&](char c) { return isspace(c) || c == open_bracket; });
|
||||
i += key.size();
|
||||
|
||||
while(i < content.size() && isspace(content[i])) {
|
||||
i++;
|
||||
}
|
||||
|
||||
assert(i < content.size() && content[i] == open_bracket &&
|
||||
"Expect @key[...] for ranges.");
|
||||
i += 1; // Skip '['
|
||||
|
||||
uint32_t begin_offset = offset;
|
||||
int bracket_level = 1;
|
||||
|
||||
while(i < content.size() && bracket_level > 0) {
|
||||
// Inside a range, we can still have nested point annotations.
|
||||
if(try_parse_point_annotation()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char inner_c = content[i];
|
||||
if(inner_c == open_bracket)
|
||||
bracket_level++;
|
||||
else if(inner_c == close_bracket)
|
||||
bracket_level--;
|
||||
|
||||
if(bracket_level > 0) {
|
||||
source += inner_c;
|
||||
offset += 1;
|
||||
i += 1;
|
||||
} else {
|
||||
i += 1; // Skip the final ']'
|
||||
}
|
||||
}
|
||||
|
||||
ranges.try_emplace(key, LocalSourceRange{begin_offset, offset});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '$') {
|
||||
assert(i + 1 < content.size() && content[i + 1] == '(' && "expect $(name)");
|
||||
i += 2;
|
||||
auto key = content.substr(i).take_until([](char c) { return c == ')'; });
|
||||
i += key.size() + 1;
|
||||
offsets.try_emplace(key, offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
offset += 1;
|
||||
// If nothing else matched, it's a regular character.
|
||||
source += c;
|
||||
offset += 1;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return AnnotatedSource{std::move(source), std::move(offsets)};
|
||||
return AnnotatedSource{
|
||||
std::move(source),
|
||||
std::move(offsets),
|
||||
std::move(ranges),
|
||||
std::move(nameless_offsets),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,7 +179,7 @@ struct AnnotatedSources {
|
||||
while(!content.empty()) {
|
||||
llvm::StringRef line = content.take_front(content.find_first_of("\r\n"));
|
||||
content = content.drop_front(line.size());
|
||||
if(content.starts_with("\n")) {
|
||||
if(content.starts_with("\r\n")) {
|
||||
content = content.drop_front(2);
|
||||
} else if(content.starts_with("\n")) {
|
||||
content = content.drop_front(1);
|
||||
|
||||
Reference in New Issue
Block a user