278 lines
8.8 KiB
C++
278 lines
8.8 KiB
C++
// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <initializer_list>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <hilti/base/util.h>
|
|
|
|
namespace hilti::detail {
|
|
|
|
using normalizer_func = std::optional<std::string> (*)(std::string_view);
|
|
inline std::optional<std::string> identityNormalizer(std::string_view s) { return std::nullopt; }
|
|
|
|
/**
|
|
* Base class for representing scoped language IDs. It provides a number of
|
|
* standard accessors and manipulators to support operations on/with
|
|
* namespaces. This class assumes that namespaces are separated with `::`.
|
|
*
|
|
* @tparam Derived name of the class deriving from this one (CRTP).
|
|
* @tparam N a function that may preprocess/normalize all ID components before storing them
|
|
*
|
|
*/
|
|
template<class Derived, normalizer_func N = identityNormalizer>
|
|
class IDBase {
|
|
public:
|
|
/** Tag for creating IDs from an already normalized string. */
|
|
struct AlreadyNormalized {};
|
|
|
|
/** Creates an empty ID. */
|
|
IDBase() { _init("", false); }
|
|
|
|
/** Creates an ID from an (not normalized) string. */
|
|
IDBase(const char* s) { _init(s, false); }
|
|
explicit IDBase(std::string_view s) { _init(s, false); }
|
|
|
|
/**
|
|
* Creates an ID from a string that's already normalized. The assumption is
|
|
* that the input string is the output of a prior `str()` call on an
|
|
* existing ID object.
|
|
*/
|
|
IDBase(std::string_view s, AlreadyNormalized) { _init(s, true); }
|
|
|
|
/** Concatenates multiple strings into a single ID, separating them with `::`. */
|
|
template<typename... T, typename enable = std::enable_if_t<(... && std::is_convertible_v<T, std::string_view>)>>
|
|
explicit IDBase(const T&... s) {
|
|
_init((util::join<std::string>({s...}, "::")), false);
|
|
}
|
|
|
|
/** Concatenates multiple strings into a single ID, separating them with `::`. */
|
|
IDBase(std::initializer_list<std::string_view> x) { _init(util::join(x, "::"), false); }
|
|
|
|
IDBase(const IDBase& other) : _id(std::move(other._id)) {}
|
|
|
|
IDBase(IDBase&& other) noexcept : _id(std::move(other._id)) {}
|
|
|
|
~IDBase() = default;
|
|
|
|
IDBase& operator=(const IDBase& other) {
|
|
if ( &other == this )
|
|
return *this;
|
|
|
|
_id = other._id;
|
|
_views.reset();
|
|
return *this;
|
|
}
|
|
|
|
IDBase& operator=(IDBase&& other) noexcept {
|
|
if ( &other == this )
|
|
return *this;
|
|
|
|
_id = std::move(other._id);
|
|
_views.reset();
|
|
return *this;
|
|
}
|
|
|
|
/** Returns the ID's full name as a string. */
|
|
const std::string& str() const { return _id; }
|
|
|
|
/** Returns the ID local part, which is the most-rhs element of the ID path. */
|
|
Derived local() const { return Derived(_cachedViews()->local, AlreadyNormalized()); }
|
|
|
|
/** Returns the ID's namespace. That's everything except the local part. */
|
|
Derived namespace_() const { return Derived(_cachedViews()->namespace_, AlreadyNormalized()); }
|
|
|
|
/** Returns true if the ID's value has length zero. */
|
|
bool empty() const { return _id.empty(); }
|
|
|
|
/** Returns the number of namespace components. */
|
|
size_t length() const { return _cachedViews()->path.size(); }
|
|
|
|
/** Returns true if the ID is absolute, i.e., starts with `::`. */
|
|
bool isAbsolute() const { return ! _id.empty() && _id[0] == ':'; }
|
|
|
|
/**
|
|
* Returns a new ID containing just single component of the path's of the
|
|
* ID. Indices are zero-based and, if negative, counted from the end
|
|
* Python-style.
|
|
*
|
|
* @param i index of path component to return
|
|
*/
|
|
Derived sub(int i) const {
|
|
const auto& path = _cachedViews()->path;
|
|
|
|
if ( i < 0 )
|
|
i = static_cast<int>(path.size()) + i;
|
|
|
|
if ( i >= 0 && static_cast<size_t>(i) < path.size() )
|
|
return Derived(path[i], AlreadyNormalized());
|
|
else
|
|
return Derived();
|
|
}
|
|
|
|
/**
|
|
* Returns a new ID containing a subpath of the ID. Indices are
|
|
* zero-based and, if negative, counted from the end Python-style.
|
|
*
|
|
* @param from 1st index to include
|
|
* @param to one beyond last index to include
|
|
*/
|
|
Derived sub(int from, int to) const {
|
|
return Derived(util::join(util::slice(_cachedViews()->path, from, to), "::"), AlreadyNormalized());
|
|
}
|
|
|
|
/**
|
|
* "Rebases" the ID relative to another one.
|
|
*
|
|
* If the ID already starts with `root`, the remaining part is returned.
|
|
* If not, the returned value is `root` plus the ID.
|
|
*/
|
|
Derived relativeTo(const Derived& root) const {
|
|
if ( _id == root._id )
|
|
return Derived();
|
|
|
|
if ( ! util::startsWith(_id, root._id + "::") )
|
|
return Derived(root + _id, AlreadyNormalized());
|
|
|
|
return Derived(_id.substr(root._id.size() + 2), AlreadyNormalized());
|
|
}
|
|
|
|
/**
|
|
* Turns the ID into absolute one, i.e., prepends `::` if not already
|
|
* present. Aftwerwards, `isAbsolute()` will return true.
|
|
*/
|
|
Derived makeAbsolute() const {
|
|
if ( isAbsolute() )
|
|
return Derived(*this);
|
|
|
|
return Derived("::" + _id, AlreadyNormalized());
|
|
}
|
|
|
|
/** Appends an ID, separating it with `::`. */
|
|
Derived& operator+=(const Derived& other) {
|
|
if ( ! other.empty() ) {
|
|
if ( empty() )
|
|
*this = other;
|
|
else
|
|
*this = Derived(_id + "::" + other._id, AlreadyNormalized());
|
|
}
|
|
|
|
return static_cast<Derived&>(*this);
|
|
}
|
|
|
|
/** Appends an ID, separating it with `::`. */
|
|
Derived& operator+=(std::string_view other) {
|
|
auto other_ = Derived(other);
|
|
return *this += other_;
|
|
}
|
|
|
|
/** Concatenates two IDs, separating them with `::`. */
|
|
Derived operator+(std::string_view other) const {
|
|
Derived n = Derived(*this);
|
|
n += other;
|
|
return n;
|
|
}
|
|
|
|
/** Concatenates two IDs, separating them with `::`. */
|
|
Derived operator+(const Derived& other) const {
|
|
Derived n = Derived(*this);
|
|
n += other;
|
|
return n;
|
|
}
|
|
|
|
bool operator==(const Derived& other) const { return _id == other._id; };
|
|
bool operator!=(const Derived& other) const { return ! (*this == other); }
|
|
bool operator<(const Derived& other) const { return _id < other._id; };
|
|
|
|
/** Returns true if the ID is not empty. */
|
|
explicit operator bool() const { return ! empty(); }
|
|
|
|
/** Returns the ID as a string, with all components normalized. */
|
|
operator std::string() const { return _id; }
|
|
|
|
/** Returns the ID as a string, with all components normalized. */
|
|
operator std::string_view() const { return _id; }
|
|
|
|
private:
|
|
// Caches views into the ID string.
|
|
struct Views {
|
|
std::vector<std::string_view> path; // views into _id; empty for empty ID
|
|
std::string_view local; // view into _id
|
|
std::string_view namespace_; // view into _id
|
|
};
|
|
|
|
void _init(std::string_view s, bool already_normalized) {
|
|
if ( s.empty() )
|
|
return;
|
|
|
|
if ( already_normalized )
|
|
_id = s;
|
|
|
|
else {
|
|
_id.reserve(s.size()); // we'll need at least this much
|
|
for ( size_t i = 0; i < s.size(); /* empty */ ) {
|
|
if ( auto p = s.find("::", i); p != std::string::npos ) {
|
|
_normalizeAndAdd(s.substr(i, p - i));
|
|
_id += "::";
|
|
i = p + 2;
|
|
}
|
|
else {
|
|
_normalizeAndAdd(s.substr(i));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _normalizeAndAdd(std::string_view x) {
|
|
assert(x.find("::") == std::string::npos);
|
|
if ( auto nx = N(x) )
|
|
_id += *nx;
|
|
else
|
|
_id += x;
|
|
}
|
|
|
|
Views* _cachedViews() const noexcept {
|
|
if ( _views )
|
|
return _views.get();
|
|
|
|
_views = std::make_unique<Views>();
|
|
|
|
size_t ns_end = std::string::npos;
|
|
_views->path.clear();
|
|
|
|
for ( size_t i = 0; i < _id.size(); /* empty */ ) {
|
|
if ( auto p = _id.find("::", i); p != std::string::npos ) {
|
|
_views->path.emplace_back(std::string_view(_id).substr(i, p - i));
|
|
i = p + 2;
|
|
ns_end = p;
|
|
}
|
|
else {
|
|
_views->path.emplace_back(std::string_view(_id).substr(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( ns_end != std::string::npos ) {
|
|
_views->namespace_ = std::string_view(_id).substr(0, ns_end);
|
|
_views->local = std::string_view(_id).substr(ns_end + 2);
|
|
}
|
|
else {
|
|
_views->namespace_ = {};
|
|
_views->local = std::string_view(_id);
|
|
}
|
|
|
|
return _views.get();
|
|
}
|
|
|
|
std::string _id; // normalized full-path ID
|
|
mutable std::unique_ptr<Views> _views; // allocated on demand first time view information is needed
|
|
};
|
|
|
|
} // namespace hilti::detail
|