diff --git a/include/cubescript/cubescript/error.hh b/include/cubescript/cubescript/error.hh index 4ed2ed2..fc369d4 100644 --- a/include/cubescript/cubescript/error.hh +++ b/include/cubescript/cubescript/error.hh @@ -20,27 +20,71 @@ namespace cubescript { struct state; +/** @brief An internal Cubescript error. + * + * This is an error that is never expected; it is thrown + * when some API call fails due to most likely a bug. + */ struct internal_error: std::runtime_error { using std::runtime_error::runtime_error; }; +/** @brief Represents the simplified call stack at a point in time. + * + * This is a simplified call stack; it is generally carried by errors + * and can be utilized to print out a stack trace. + * + * The actual stack is represented as a linked list of nodes. There can + * be a gap in the list, if the user has limited the maximum debug depth + * with the `dbgalias` cubescript variable; the bottommost node will always + * represent the bottom of the stack, while the nodes above it will be the + * rest of the stack or a part of the stack starting from the top. + */ struct LIBCUBESCRIPT_EXPORT stack_state { + /** @brief A node in the call stack. + * + * The nodes are indexed. The bottommost node has index 1, the topmost + * node has index N (where N is the number of levels the call stack has). + */ struct node { - node const *next; - struct ident const *id; - int index; + node const *next; /**< @brief Next level. */ + struct ident const *id; /**< @brief The ident of this level. */ + int index; /**< @brief The level index. */ }; stack_state() = delete; + + /** @brief Construct the stack state. */ stack_state(state &cs, node *nd = nullptr, bool gap = false); + stack_state(stack_state const &) = delete; + + /** @brief Move the stack state somewhere else. + * + * Stack states are movable, but not copyable. + */ stack_state(stack_state &&st); + + /** @brief Destroy the stack state. */ ~stack_state(); stack_state &operator=(stack_state const &) = delete; + + /** @brief Move-assign the stack state somewhere else. + * + * Stack states are move assignable, but not copy assignable. + */ stack_state &operator=(stack_state &&); + /** @brief Get the pointer to the topmost (current) level. */ node const *get() const; + + /** @brief Get whether the stack is incomplete. + * + * A value of `true` means the call stack has a gap in it, i.e. the + * bottommost node has index 1 while the node just above it has index + * greater than 2. + */ bool gap() const; private: @@ -49,28 +93,46 @@ private: bool p_gap; }; +/** @brief Represents a Cubescript error. + * + * This is a standard error that can be thrown by either the Cubescript APIs + * or from the language itself (either by the user, or by incorrect use of + * the API). + * + * It has a message attached, as well as the current state of the call stack, + * represented as cubescript::stack_state. + * + * Each Cubescript thread internally stores a buffer for the error message, + * which is reused for each error raised from that thread. + */ struct LIBCUBESCRIPT_EXPORT error { friend struct state; error() = delete; error(error const &) = delete; + + /** @brief Errors are move constructible. */ error(error &&v): p_errbeg{v.p_errbeg}, p_errend{v.p_errend}, p_stack{std::move(v.p_stack)} {} + /** @brief Get a view of the error message. */ std::string_view what() const { return std::string_view{p_errbeg, std::size_t(p_errend - p_errbeg)}; } + /** @brief Get a reference to the call stack state. */ stack_state &get_stack() { return p_stack; } + /** @brief Get a reference to the call stack state. */ stack_state const &get_stack() const { return p_stack; } + /** @brief Construct an error using an unformatted string. */ error(state &cs, std::string_view msg): p_errbeg{}, p_errend{}, p_stack{cs} { @@ -83,6 +145,7 @@ struct LIBCUBESCRIPT_EXPORT error { p_stack = save_stack(cs); } + /** @brief Construct an error using a `printf`-style format string. */ template error(state &cs, std::string_view msg, A const &...args): p_errbeg{}, p_errend{}, p_stack{cs} diff --git a/include/cubescript/cubescript/ident.hh b/include/cubescript/cubescript/ident.hh index e4e86bc..445ac49 100644 --- a/include/cubescript/cubescript/ident.hh +++ b/include/cubescript/cubescript/ident.hh @@ -18,8 +18,17 @@ namespace cubescript { +/** @brief The type of the ident. + * + * Cubescript has a selection of idents. This represents the type of each. + */ enum class ident_type { - IVAR = 0, FVAR, SVAR, COMMAND, ALIAS, SPECIAL + IVAR = 0, /**< @brief Integer builtin variable. */ + FVAR, /**< @brief Float builtin variable. */ + SVAR, /**< @brief String builtin variable. */ + COMMAND, /**< @brief Builtin command. */ + ALIAS, /**< @brief User assigned variable. */ + SPECIAL /**< @brief Other (internal unexposed type). */ }; struct global_var; @@ -29,38 +38,156 @@ struct string_var; struct alias; struct command; +/** @brief The ident structure. + * + * Every object within the Cubescript language is represented with an ident. + * This is the generic base interface. There are some operations that are + * available on any ident. + * + * You can also check the actual type with it (cubescript::ident_type) and + * decide to cast it to its appropriate specific type, or use the helpers. + */ struct LIBCUBESCRIPT_EXPORT ident { + /** @brief Get the cubescript::ident_type of this ident. */ ident_type get_type() const; + + /** @brief Get a view to the name of the ident. */ std::string_view get_name() const; + + /** @brief Get the index of the ident. + * + * Idents are internally indexed. There is no guarantee of what index + * the ident will have, but you can still use it to identify the object + * with an integer (it is guaranteed that once created, it will stay the + * same for the whole lifetime of the main thread). + */ int get_index() const; + /** @brief Check if the ident is a cubescript::alias. + * + * Effectively like `get_type() == ident_type::ALIAS`. + */ bool is_alias() const; + + /** @brief Try to convert this to cubescript::alias. + * + * @return A pointer to the alias if it is one, or `nullptr`. + */ alias *get_alias(); alias const *get_alias() const; + /** @brief Check if the ident is a cubescript::command. + * + * Effectively like `get_type() == ident_type::COMMAND`. + */ bool is_command() const; + + /** @brief Try to convert this to cubescript::command. + * + * @return A pointer to the command if it is one, or `nullptr`. + */ command *get_command(); command const *get_command() const; + /** @brief Check if the ident is a special ident. + * + * Effectively like `get_type() == ident_type::SPECIAL`. + */ bool is_special() const; + /** @brief Check if the ident is a cubescript::global_var. + * + * This will return `true` if ident::get_type() returns either + * ident_type::IVAR, ident_type::FVAR or ident_type::SVAR. + */ bool is_var() const; + + /** @brief Try to convert this to cubescript::global_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ global_var *get_var(); + + /** @brief Try to convert this to cubescript::global_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ global_var const *get_var() const; + /** @brief Check if the ident is a cubescript::integer_var. + * + * Effectively like `get_type() == ident_type::IVAR`. + */ bool is_ivar() const; + + /** @brief Try to convert this to cubescript::integer_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ integer_var *get_ivar(); + + /** @brief Try to convert this to cubescript::integer_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ integer_var const *get_ivar() const; + /** @brief Check if the ident is a cubescript::float_var. + * + * Effectively like `get_type() == ident_type::FVAR`. + */ bool is_fvar() const; + + /** @brief Try to convert this to cubescript::float_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ float_var *get_fvar(); + + /** @brief Try to convert this to cubescript::float_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ float_var const *get_fvar() const; + /** @brief Check if the ident is a cubescript::string_var. + * + * Effectively like `get_type() == ident_type::SVAR`. + */ bool is_svar() const; + + /** @brief Try to convert this to cubescript::string_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ string_var *get_svar(); + + /** @brief Try to convert this to cubescript::string_var. + * + * @return A pointer to the var if it is one, or `nullptr`. + */ string_var const *get_svar() const; + /** @brief Get if the ident is overridden. + * + * This can be true for aliases or builtins. When an alias or a builtin + * is assigned to and the VM is in override mode or the builtin is + * var_type::OVERRIDABLE, they are marked as overridden (and builtins + * have their value saved beforehand). + * + * This can be cleared later, which will erase the value (for aliases) + * or restore the saved one (for builtins). For aliases, this can be + * specific to the Cubescript thread. + */ bool is_overridden(state &cs) const; + + /** @brief Get if the ident is persistent. + * + * This can be true in two cases. Either it's a builtin and it has the + * var_type::PERSISTENT flag, or it's an alias that is assigned to while + * the VM is in persist mode. The latter can be thread specific (when the + * alias is currently pushed). + */ bool is_persistent(state &cs) const; protected: @@ -71,69 +198,209 @@ protected: struct ident_impl *p_impl{}; }; +/** @brief An additional cubescript::global_var type. + * + * Global vars can have no additional type, or they can be persistent, or + * they can be overridable. Persistent variables are meant to be saved and + * loaded later (the actual logic is up to the user of the library). + * + * Overridable variables are overridden when assigned to (this can also + * happen to normal variables when the VM is in override mode), which saves + * their old value (which can be restored later when un-overridden). This + * is mutually exclusive; overridable variables cannot be persistent, and + * attempting to assign to a persistent variable while the VM is in override + * mode will raise an error. + */ enum class var_type { - DEFAULT = 0, - PERSISTENT, - OVERRIDABLE + DEFAULT = 0, /**< @brief The default type. */ + PERSISTENT, /**< @brief Persistent variable. */ + OVERRIDABLE /**< @brief Overridable variable. */ }; +/** @brief A global variable. + * + * This represents one of cubescript::integer_var, cubescript::float_var or + * cubescript::string_var as a single interface, with shared operations. + */ struct LIBCUBESCRIPT_EXPORT global_var: ident { + /** @brief Get whether the variable is read only. + * + * Variables can be set as read only during their creation (but not + * later). This will prevent assignments to them from within the language + * or using checked APIs, but it is still possible to assign to them + * using raw APIs. The raw APIs will not invoke value triggers, however. + */ bool is_read_only() const; + + /** @brief Get whether the variable is overridable. + * + * Equivalent to `get_variable_type() == var_type::OVERRIDABLE`. + */ bool is_overridable() const; + /** @brief Get the cubescript::var_type of the variable. */ var_type get_variable_type() const; + /** @brief Save the variable. + * + * This is mainly intended for variable assignment triggers. If the + * variable is overridable or the given thread is in override mode, + * this will save the current value of the variable (if not already + * overridden). Otherwise, it will clear any existing overridden flag. + * + * @throw cubescript::error if the thread is in override mode and the + * variable is persistent. + */ void save(state &cs); protected: global_var() = default; }; +/** @brief An integer variable. + * + * A specialization of cubescript::global_var for integer values. + */ struct LIBCUBESCRIPT_EXPORT integer_var: global_var { + /** @brief Get the value of the variable. */ integer_type get_value() const; + + /** @brief Set the value of the variable. + * + * If read only, an error is raised. If `do_write` is `false`, nothing + * will be performed other than the read-only checking. If `trigger` is + * `false`, a potential variable change trigger command will not be + * invoked. The value is saved with global_var::save(), assuming + * `do_write` is `true`. After that, integer_var::set_raw_value() + * is invoked, and then the trigger. + * + * @throw cubescript::error if read only or if the changed trigger throws. + */ void set_value( state &cs, integer_type val, bool do_write = true, bool trigger = true ); + + /** @brief Set the value of the variable in a raw manner. + * + * This will always set the value and ignore any kinds of checks. It will + * not invoke any triggers either, nor it will save the the value. + */ void set_raw_value(integer_type val); protected: integer_var() = default; }; +/** @brief A float variable. + * + * A specialization of cubescript::global_var for float values. + */ struct LIBCUBESCRIPT_EXPORT float_var: global_var { + /** @brief Get the value of the variable. */ float_type get_value() const; + + /** @brief Set the value of the variable. + * + * If read only, an error is raised. If `do_write` is `false`, nothing + * will be performed other than the read-only checking. If `trigger` is + * `false`, a potential variable change trigger command will not be + * invoked. The value is saved with global_var::save(), assuming + * `do_write` is `true`. After that, integer_var::set_raw_value() + * is invoked, and then the trigger. + * + * @throw cubescript::error if read only or if the changed trigger throws. + */ void set_value( state &cs, float_type val, bool do_write = true, bool trigger = true ); + + /** @brief Set the value of the variable in a raw manner. + * + * This will always set the value and ignore any kinds of checks. It will + * not invoke any triggers either, nor it will save the the value. + */ void set_raw_value(float_type val); protected: float_var() = default; }; +/** @brief A string variable. + * + * A specialization of cubescript::global_var for string values. + */ struct LIBCUBESCRIPT_EXPORT string_var: global_var { + /** @brief Get the value of the variable. */ string_ref get_value() const; + + /** @brief Set the value of the variable. + * + * If read only, an error is raised. If `do_write` is `false`, nothing + * will be performed other than the read-only checking. If `trigger` is + * `false`, a potential variable change trigger command will not be + * invoked. The value is saved with global_var::save(), assuming + * `do_write` is `true`. After that, integer_var::set_raw_value() + * is invoked, and then the trigger. + * + * @throw cubescript::error if read only or if the changed trigger throws. + */ void set_value( state &cs, string_ref val, bool do_write = true, bool trigger = true ); + + /** @brief Set the value of the variable in a raw manner. + * + * This will always set the value and ignore any kinds of checks. It will + * not invoke any triggers either, nor it will save the the value. + */ void set_raw_value(string_ref val); protected: string_var() = default; }; +/** @brief An alias. + * + * An alias is an ident that is created inside the language, for example + * by assignment. Any variable that you can assign to or look up and is not + * a builtin is an alias. Aliases don't have special assignment syntax nor + * they have changed triggers nor value saving. They technically always + * represent a string within the language, though on C++ side they can + * have float or integer values too. + */ struct LIBCUBESCRIPT_EXPORT alias: ident { + /** @brief Get if this alias represents a function argument. + * + * This is true for `argN` aliases representing the arguments passed to + * the current function. + */ bool is_arg() const; + /** @brief Get the value of the alias for the given thread. */ any_value get_value(state &cs) const; + + /** @brief Set the value of the alias for the given thread. */ void set_value(state &cs, any_value v); protected: alias() = default; }; +/** @brief A command. + * + * Commands are builtins that can be invoked from the language and have a + * native implementation registered from C++. Once registered, a command + * cannot be unregistered or otherwise changed. + */ struct LIBCUBESCRIPT_EXPORT command: ident { + /** @brief Get the argument list. */ std::string_view get_args() const; + + /** @brief Get the number of arguments the command expects. + * + * Only non-variadic arguments count here (i.e. no repeated arguments, + * no `C`, no `V`; everything else counts as one argument). + */ int get_num_args() const; protected: diff --git a/include/cubescript/cubescript_conf.hh b/include/cubescript/cubescript_conf.hh index c06faff..1c3f638 100644 --- a/include/cubescript/cubescript_conf.hh +++ b/include/cubescript/cubescript_conf.hh @@ -19,10 +19,8 @@ # include "cubescript_conf_user.hh" #endif -#if __has_include() +#if !defined(LIBCUBESCRIPT_CONF_USER_SPAN) # include -#elif !defined(LIBCUBESCRIPT_CONF_USER_SPAN) -# error "This implementation does not provide an std::span." #endif namespace cubescript { diff --git a/src/cs_ident.cc b/src/cs_ident.cc index dc14bab..ffd5e45 100644 --- a/src/cs_ident.cc +++ b/src/cs_ident.cc @@ -351,6 +351,7 @@ LIBCUBESCRIPT_EXPORT void global_var::save(state &cs) { } if (!(p_impl->p_flags & IDENT_FLAG_OVERRIDDEN)) { static_cast(p_impl)->save_val(); + p_impl->p_flags |= IDENT_FLAG_OVERRIDDEN; } } else { p_impl->p_flags &= IDENT_FLAG_OVERRIDDEN;