Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

272 lines
8.8 KiB
C++

// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
#pragma once
#include <algorithm>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <hilti/ast/expression.h>
#include <hilti/ast/forward.h>
#include <hilti/ast/node.h>
#include <hilti/base/util.h>
namespace hilti {
namespace attribute {
/**
* Represents a specific attribute. While we generally identify attributes by
* their names (`&...`), we wrap those strings into `Kind` instances so that we
* can (1) define global constants for all valid attribute names, and (2)
* retain a list of all known attributes for error checking.
*
* Note that generally, one should only use the global, pre-defined constants
* when referring to a specific attribute. Avoid creating new instances on the
* fly.
*/
class Kind {
public:
explicit Kind(std::string name) : _name(std::move(name)) { _register(); }
Kind() = delete;
Kind(const Kind& other) = default;
Kind(Kind&& other) = default;
~Kind() = default; // not unregistering here, so that attribute remains known
operator std::string() const { return _name; }
Kind& operator=(const Kind& other) = default;
Kind& operator=(Kind&& other) = default;
bool operator==(const Kind& other) const { return _name == other._name; }
bool operator!=(const Kind& other) const { return ! operator==(other); }
// Returns a corresponding attribute kind iff the string matches to one of
// the known attributes created so far. Otherwise throws an `out_of_range`
// exception.
static Kind fromString(const std::string_view& s) {
if ( _known_attributes && _known_attributes->count(std::string(s)) > 0 )
return Kind(std::string(s));
else
throw std::out_of_range("unknown attribute kind: " + std::string(s));
}
private:
void _register();
std::string _name = "<unset attribute>";
static std::set<std::string>* _known_attributes;
};
/** Returns whether `kind` is in `kinds` */
inline bool isOneOf(const Kind& kind, std::initializer_list<Kind> kinds) {
return std::find(kinds.begin(), kinds.end(), kind) != kinds.end();
}
inline auto to_string(const Kind& kind) { return std::string(kind); }
inline std::ostream& operator<<(std::ostream& out, const Kind& x) {
out << to_string(x);
return out;
}
namespace kind {
inline auto from_string(const std::string_view& s) { return Kind::fromString(s); }
// In the following, we predefine all attributes that are part of the HILTI language.
const Kind AlwaysEmit("&always-emit");
const Kind Anchor("&anchor");
const Kind Anonymous("&anonymous");
const Kind Convert("&convert");
const Kind Cxxname("&cxxname");
const Kind Debug("&debug");
const Kind Default("&default");
const Kind HavePrototype("&have_prototype");
const Kind Internal("&internal");
const Kind NeededByFeature("&needed-by-feature");
const Kind NoEmit("&no-emit");
const Kind Nosub("&nosub");
const Kind OnHeap("&on-heap");
const Kind Optional("&optional");
const Kind Priority("&priority");
const Kind RequiresTypeFeature("&requires-type-feature");
const Kind Static("&static");
} // namespace kind
} // namespace attribute
/** AST node for an attribute. */
class Attribute : public Node {
public:
/** Returns the kind of the attribute, derived from its tag. */
const auto& kind() const { return _kind; }
/** Returns true if an argument is associated with the attribute. */
auto hasValue() const { return child(0) != nullptr; }
/**
* Returns the attribute associated with the node.
*
* @exception `std::out_of_range` if the attribute does not have an argument
*/
Node* value() const { return child(0); }
/**
* Returns the expression argument associated with the attribute.
*
* @return the argument, or an error if the attribute does not have an
* argument, or if it's not an expression.
*/
Result<Expression*> valueAsExpression() const;
/**
* Returns the expression argument associated with the attribute as a
* string, assuming it represents a constant integer value.
*
* @return the argument, or an error if the attribute does not have an
* argument, or if it's not a constant string.
*/
Result<std::string> valueAsString() const;
/**
* Returns the expression argument associated with the attribute as a
* signed integer, assuming it represents a constant integer value. Both
* signed and unsigned integer values are accepted, with the latter cased
* into signed for the return value
*
* @return the argument, or an error if the attribute does not have an
* argument, or if it's not a constant integer.
*/
Result<int64_t> valueAsInteger() const;
/**
* Coerce the attribute's expression value to a specified type, modifying
* the node in place.
*
* @return A successful return value if either the coercion succeeded
* (then the result's value is true), or nothing was to be done (then the
* result's value is false); a failure if a coercion would have been
* necessary, but failed, or the attribute does not have a expression value.
*/
Result<bool> coerceValueTo(Builder* builder, QualifiedType* dst);
node::Properties properties() const final {
auto p = node::Properties{{"tag", to_string(kind())}};
return Node::properties() + std::move(p);
}
/**
* Factory for an attribute coming with an argument. The argument must be
* an AST node representing an expression.
*
* @param kind the attribute's internal representation
* @param v node representing the argument to associate with the attribute; must be an expression
* @param m meta data to associate with the node
*/
static auto create(ASTContext* ctx, const attribute::Kind& kind, Expression* v, const Meta& m = Meta()) {
return ctx->make<Attribute>(ctx, {v}, kind, m);
}
/**
* Factory for an attribute with no argument.
*
* @param kind the attribute's internal representation
* @param m meta data to associate with the node
*/
static auto create(ASTContext* ctx, const attribute::Kind& kind, const Meta& m = Meta()) {
return create(ctx, kind, nullptr, m);
}
protected:
Attribute(ASTContext* ctx, Nodes children, attribute::Kind kind, Meta m = Meta())
: Node(ctx, NodeTags, std::move(children), std::move(m)), _kind(std::move(kind)) {}
std::string _dump() const override;
HILTI_NODE_0(Attribute, final);
private:
attribute::Kind _kind;
};
/** AST node holding a set of `Attribute` nodes. */
class AttributeSet : public Node {
public:
/** Returns the set's attributes. */
auto attributes() const { return children<Attribute>(0, {}); }
/**
* Retrieves an attribute with a given kind from the set. If multiple
* attributes with that kind exist, it's undefined which one is returned.
*
* @return attribute if found
*/
Attribute* find(const attribute::Kind& kind) const;
/**
* Retrieves all attributes with a given kind from the set.
*
* @return all attributes with matching kind
*/
hilti::node::Set<Attribute> findAll(const attribute::Kind& kind) const;
/**
* Returns true if there's an attribute with a given kind in the set.
*
* @param true if found
*/
bool has(const attribute::Kind& kind) const { return find(kind) != nullptr; }
/** Adds an attribute to the set. */
void add(ASTContext* ctx, Attribute* a) {
addChild(ctx, a);
// Combine this location with the attribute's location so this spans the range
setMeta(meta().mergeLocation(a->location()));
}
/** Removes all attributes of the given kind. */
void remove(const attribute::Kind& kind);
/** Returns true if the set has at least one element. */
operator bool() const { return ! attributes().empty(); }
static auto create(ASTContext* ctx, const Attributes& attrs = {}, Meta m = Meta()) {
return ctx->make<AttributeSet>(ctx, attrs, std::move(m));
}
protected:
/**
* Constructs a set from from a vector of attributes.
*
* @param a vector to initialize attribute set from
* @param m meta data to associate with the node
*/
explicit AttributeSet(ASTContext* ctx, Nodes children, Meta m = Meta())
: Node(ctx, NodeTags, std::move(children), std::move(m)) {}
/**
* Constructs an empty set.
*
* @param m meta data to associate with the node
*/
AttributeSet(ASTContext* ctx, Meta m = Meta()) : Node(ctx, {node::tag::AttributeSet}, {}, std::move(m)) {}
std::string _dump() const override;
HILTI_NODE_0(AttributeSet, final);
};
} // namespace hilti
namespace std {
template<>
struct hash<hilti::attribute::Kind> {
size_t operator()(const hilti::attribute::Kind& x) const { return std::hash<std::string>()(x); }
};
} // namespace std