From 0a432d2f195f717f625ef928733fe371af894c96 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 22 Apr 2021 05:19:47 +0200 Subject: [PATCH] document values --- include/cubescript/cubescript/value.hh | 340 ++++++++++++++++++++++++- include/cubescript/cubescript_conf.hh | 4 - 2 files changed, 334 insertions(+), 10 deletions(-) diff --git a/include/cubescript/cubescript/value.hh b/include/cubescript/cubescript/value.hh index 51342f1..216fd36 100644 --- a/include/cubescript/cubescript/value.hh +++ b/include/cubescript/cubescript/value.hh @@ -21,24 +21,84 @@ 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; - operator bool() 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; @@ -48,38 +108,116 @@ private: 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 std::string_view{*this}.size(); - } - std::size_t length() const { - return std::string_view{*this}.length(); + 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; private: @@ -88,46 +226,236 @@ private: 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, INTEGER, FLOAT, STRING, CODE, IDENT + 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, `nullptr` is returned. + */ ident *get_ident() 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: diff --git a/include/cubescript/cubescript_conf.hh b/include/cubescript/cubescript_conf.hh index 1c3f638..5ef430a 100644 --- a/include/cubescript/cubescript_conf.hh +++ b/include/cubescript/cubescript_conf.hh @@ -61,7 +61,6 @@ namespace cubescript { #endif #if !defined(LIBCUBESCRIPT_CONF_USER_SPAN) -#if __has_include() || defined(LIBCS_GENERATING_DOC) /** @brief The span type used. * * By default, this is `std::span`. You will almost never want to override @@ -73,9 +72,6 @@ namespace cubescript { */ template using span_type = std::span; -#else - using span_type = void; -#endif #endif #if !defined(LIBCUBESCRIPT_CONF_USER_INTEGER)