/** @file value.hh * * @brief Value API. * * This file contains value handles. These include the main value handle, * which represents any Cubescript value as a tagged union (and you use it * for handling of things such as command arguments and return values), as * well as string references and bytecode references. * * @copyright See COPYING.md in the project tree for further information. */ #ifndef LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH #define LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH #include #include #include namespace cubescript { struct ident; /** @brief Bytecode reference. * * This is an object representing a bytecode reference. Bytecode references * are executable in a Cubescript thread. The typical way to get a bytecode * reference is as an argument to a command. You can also compile values * explicitly. * * Bytecode references use refcounting to maintain their lifetime, so once * you hold a reference, it will not be freed at very least until you are * done with it and the object is destroyed. * * The API does not expose any specifics of the bytecode format either. * This is an implementation detail; bytecode is also not meant to be * serialized and stored on disk (it's not guaranteed to be portable). */ struct LIBCUBESCRIPT_EXPORT bcode_ref { /** @brief Initialize a null reference. * * Null references can still be executed, but will not do anything. */ bcode_ref(): p_code(nullptr) {} /** @brief Copy a reference. * * A reference copy will increase the internal reference count and will * point to the same bytecode. */ bcode_ref(bcode_ref const &v); /** @brief Move a reference. * * A reference move will not change the internal reference count; the * other reference will become a null reference. */ bcode_ref(bcode_ref &&v): p_code(v.p_code) { v.p_code = nullptr; } /** @brief Destroy a reference. * * This will decrease the reference count, and if it's zero, free the * bytecode. */ ~bcode_ref(); /** @brief Copy-assign a reference. * * A reference copy will increase the internal reference count and will * point to the same bytecode. */ bcode_ref &operator=(bcode_ref const &v); /** @brief Move-assign a reference. * * A reference move will not change the internal reference count; the * other reference will become a null reference. */ bcode_ref &operator=(bcode_ref &&v); /** @brief Check if the bytecode is empty. * * Empty bytecode does not mean the same thing as null bytecode. While * null bytecode is considered empty, there can be non-null empty * bytecode; that is, a bytecode representing what an empty string * would compile into. */ bool empty() const; /** @brief Check if the bytecode is null. * * This only checks if the bytecode is null. In general, you will want * to use bcode_ref::empty() instead. */ explicit operator bool() const; private: friend struct bcode_p; bcode_ref(struct bcode *v); struct bcode *p_code; }; /** @brief String reference. * * This Cubescript implementation uses interned strings everywhere in the * language and the API. This means every string in the language exists in * exactly one copy - if you try to create another string with the same * contents, it will point to the same data. This structure represents * a single reference to such string. By providing a reference counting * mechanism, it is possible to manage strings in a memory-safe manner. * * There is also no such thing as a null reference in this case. If you * have a string reference, it always points to a valid string no matter * what. * * It is not safe to have string references still around after the main * Cubescript thread is destroyed. Therefore, you should always make sure * that all string references you are holding are gone by the time the * main thread calls its destructor. * * For compatibility, all strings that are pointed to by string references * are null terminated and therefore can be used with C-style APIs. */ struct LIBCUBESCRIPT_EXPORT string_ref { friend struct any_value; friend struct string_pool; /** @brief String references are not default-constructible. */ string_ref() = delete; /** @brief Create a new string reference. * * You need to provide a thread and a view of the string you wish * to create a reference for. The implementation will then ensure * that either a new string is allocated internally, or an existing * string's reference count is incremented. */ string_ref(state &cs, std::string_view str); /** @brief Copy a string reference. * * This will increase the reference count for the pointed-to string. * There is explicitly no moving as this would create null references. */ string_ref(string_ref const &ref); /** @brief Destroy a string reference. * * This will decrease the reference count. If it becomes zero, appropriate * actions will be taken (the exact behavior is implementation-defined). * * It is not safe to call this after destruction of the main thread. */ ~string_ref(); /** @brief Copy-assign a string reference. * * This will increase the reference count for the pointed-to string. * There is explicitly no moving as this would create null references. */ string_ref &operator=(string_ref const &ref); /** @brief Get a view to the pointed-to string. * * This creates a view of the string. The view is not its own reference, * therefore it is possible it will get invalidated after the destructor * of this reference is called. */ operator std::string_view() const; /** @brief A convenience wrapper to get the size. * * Like `view().size()`. */ std::size_t size() const { return view().size(); } /** @brief A convenience wrapper to get the length. * * Like `view().length()`. */ std::size_t length() const { return view().length(); } /** @brief Get a C string pointer. * * The pointer may become dangling once the reference is destroyed. * The string itself is always null terminated. */ char const *data() const; /** @brief A convenience wrapper to get the view. * * Since instantiating a view can be ugly, this is a quick method * to get a view of a random string reference. */ std::string_view view() const { return std::string_view{*this}; } /** @brief Check if the string is empty. */ bool empty() const { return (size() == 0); } /** @brief Check if the string equals another. * * This is effectively a `data() == s.data()` address comparison, and * therefore always has constant time complexity. */ bool operator==(string_ref const &s) const; /** @brief Check if the string does not equal another. * * This is effectively a `data() != s.data()` address comparison, and * therefore always has constant time complexity. */ bool operator!=(string_ref const &s) const; private: string_ref(char const *p); char const *p_str; }; /** @brief The type of a value. * * The cubescript::any_value structure can hold multiple types. Not all of * them are representable in the language. */ enum class value_type { NONE = 0, /**< @brief No value. */ INTEGER, /**< @brief Integer value (cubescript::integer_type). */ FLOAT, /**< @brief Floating point value (cubescript::float_type). */ STRING, /**< @brief String value (cubescript::string_ref). */ CODE, /**< @brief Bytecode value (cubescript::bcode_ref). */ IDENT /**< @brief Ident value (cubescript::ident). */ }; /** @brief A tagged union representing a value. * * This structure is used to represent argument and result types of commands * as well as values of aliases. When assigned to an alias, the only value * must not contain bytecode or an ident reference, as those cannot be * represented as values in the language. * * Of course, to the language, every value looks like a string. It is however * still possible to differentiate them on C++ side for better performance, * more efficient storage and greater convenience. * * When the value contains a string or bytecode, it holds a reference like * cubescript::string_ref or cubescript::bcode_ref would. * * Upon setting different types, the old type will get cleared, which may * include a reference count decrease. */ struct LIBCUBESCRIPT_EXPORT any_value { /** @brief Construct a value_type::NONE value. */ any_value(); /** @brief Destroy the value. * * If holding a reference counted value, the refcount will be decreased * and the value will be possibly freed. */ ~any_value(); /** @brief Copy the value. */ any_value(any_value const &); /** @brief Move the value. * * The other value becomes a value_type::NULL value. */ any_value(any_value &&v); /** @brief Copy-assign the value. */ any_value &operator=(any_value const &); /** @brief Move-assign the value. * * The other value becomes a value_type::NULL value. */ any_value &operator=(any_value &&); /** @brief Get the type of the value. */ value_type get_type() const; /** @brief Set the value to an integer. * * The type becomes value_type::INTEGER. */ void set_integer(integer_type val); /** @brief Set the value to a float. * * The type becomes value_type::FLOAT. */ void set_float(float_type val); /** @brief Set the value to a string. * * The type becomes value_type::STRING. The string will be allocated * (if non-existent) like a cubescript::string_ref, and its reference * count will be increased. This is why it is necessary to provide a state. */ void set_string(std::string_view val, state &cs); /** @brief Set the value to a string reference. * * The type becomes value_type::STRING. The value will get copied * (therefore, the reference count will be increased). */ void set_string(string_ref const &val); /** @brief Set the value to a value_type::NONE. */ void set_none(); /** @brief Set the value to a bytecode reference. * * The type becomes value_type::CODE. The value will get copied * (therefore, the reference count will be increased). */ void set_code(bcode_ref const &val); /** @brief Set the value to an indent. * * The type becomes value_type::IDENT. No reference counting is * performed, so after main thread destruction this may become * dangling (and unsafe to use). */ void set_ident(ident *val); /** @brief Get the value as a string reference. * * If the contained value is not a string, an appropriate conversion * will occur. This will not affect the contained type, all conversions * are only intermediate. * * If the type is not convertible, an empty string is used. */ string_ref get_string(state &cs) const; /** @brief Get the value as an integer. * * If the contained value is not an integer, an appropriate conversion * will occur. This will not affect the contained type, all conversions * are only intermediate. * * Floating point values are rounded down and converted to integers. * * If the type is not convertible, 0 is returned. */ integer_type get_integer() const; /** @brief Get the value as a float. * * If the contained value is not a float, an appropriate conversion * will occur. This will not affect the contained type, all conversions * are only intermediate. * * If the type is not convertible, 0 is returned. */ float_type get_float() const; /** @brief Get the value as a bytecode. * * If the contained value is not bytecode, null bytecode is returned. */ bcode_ref get_code() const; /** @brief Get the value as an ident. * * If the contained value is not an ident, a dummy is returned. */ ident &get_ident(state &cs) const; /** @brief Get the value as representable inside the language. * * The returned value is the same value except if the original contents * were bytecode or an ident - in those cases the returned type is * value_type::NONE. */ any_value get_plain() const; /** @brief Get the value converted to a boolean. * * For integer and float values, anything other than zero will become * `true`, while zero becomes `false`. Empty strings are `false`; other * strings first attempt conversion to an integer - if it's convertible * (strong conversion rules apply), it's treated like an integer. If it * is not, it's converted to a float (strong rules apply) and if it is * convertible, it's treated like a float. Any non-integer non-float * string is considered `true`. * * For any other type, `false` is returned. */ bool get_bool() const; /** @brief Force the type to value_type::NONE. * * Like set_none(). */ void force_none(); /** @brief Force the type to be representable in the language. * * Like `*this = get_plain()`. */ void force_plain(); /** @brief Force the type to value_type::FLOAT. * * Like `set_float(get_float())`. * * @return The value. */ float_type force_float(); /** @brief Force the type to value_type::INTEGER. * * Like `set_integer(get_integer())`. * * @return The value. */ integer_type force_integer(); /** @brief Force the type to value_type::STRING. * * Like `set_string(get_string(cs))`. * * @return A view to the string. */ std::string_view force_string(state &cs); /** @brief Force the type to value_type::CODE. * * If the contained value is already bytecode, nothing happens. Otherwise * the value is converted to a string (like get_string()) and this string * is compiled as bytecode. * * @return A bytecode reference. */ bcode_ref force_code(state &cs); /** @brief Force the type to value_type::IDENT. * * If the contained value is already an ident, nothing happens. Otherwise * the value is converted to a string (like get_string()) and this string * is used as a name of the ident. If an ident of such name exists, it * will be stored, otherwise a new alias is pre-created (it will not be * visible to the language until a value is assigned to it though). * * @return An ident reference. */ ident &force_ident(state &cs); private: std::aligned_union_t<1, integer_type, float_type, void *> p_stor; value_type p_type; }; } /* namespace cubescript */ #endif /* LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH */