// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details. #pragma once #include #include #include #include #include #include #include namespace hilti::detail { using normalizer_func = std::optional (*)(std::string_view); inline std::optional 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 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)>> explicit IDBase(const T&... s) { _init((util::join({s...}, "::")), false); } /** Concatenates multiple strings into a single ID, separating them with `::`. */ IDBase(std::initializer_list 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(path.size()) + i; if ( i >= 0 && static_cast(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(*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 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(); 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; // allocated on demand first time view information is needed }; } // namespace hilti::detail