document values

master
Daniel Kolesa 2021-04-22 05:19:47 +02:00
parent e7578f7e17
commit 0a432d2f19
2 changed files with 334 additions and 10 deletions

View File

@ -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:

View File

@ -61,7 +61,6 @@ namespace cubescript {
#endif
#if !defined(LIBCUBESCRIPT_CONF_USER_SPAN)
#if __has_include(<span>) || 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<typename T>
using span_type = std::span<T>;
#else
using span_type = void;
#endif
#endif
#if !defined(LIBCUBESCRIPT_CONF_USER_INTEGER)