complete the public API documentation
parent
0a432d2f19
commit
960f463259
|
@ -46,6 +46,11 @@ struct command;
|
|||
*
|
||||
* 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.
|
||||
*
|
||||
* An ident always has a valid name. A valid name is pretty much any
|
||||
* valid Cubescript word (see cubescript::parse_word()) which does not
|
||||
* begin with a number (a digit, a `+` or `-` followed by a digit or a
|
||||
* period followed by a digit, or a period followed by a digit).
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT ident {
|
||||
/** @brief Get the cubescript::ident_type of this ident. */
|
||||
|
|
|
@ -23,61 +23,292 @@ namespace cubescript {
|
|||
|
||||
struct state;
|
||||
|
||||
using alloc_func = void *(*)(void *, void *, size_t, size_t);
|
||||
/** @brief The allocator function signature
|
||||
*
|
||||
* This is the signature of the function pointer passed to do allocations.
|
||||
*
|
||||
* The first argument is the user data, followed by the old pointer (which
|
||||
* is `nullptr` for new allocations and a valid pointer for reallocations
|
||||
* and frees). Then follows the original size of the object (zero for new
|
||||
* allocations, a valid value for reallocations and frees) and the new
|
||||
* size of the object (zero for frees, a valid value for reallocations
|
||||
* and new allocations).
|
||||
*
|
||||
* It must return the new pointer (`nullptr` when freeing) and does not have
|
||||
* to throw (the library will throw `std::bad_alloc` itself if it receives
|
||||
* a `nullptr` upon allocation).
|
||||
*
|
||||
* A typical allocation function will look like this:
|
||||
*
|
||||
* ```
|
||||
* void *my_alloc(void *, void *p, std::size_t, std::size_t ns) {
|
||||
* if (!ns) {
|
||||
* std::free(p);
|
||||
* return nullptr;
|
||||
* }
|
||||
* return std::realloc(p, ns);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
using alloc_func = void *(*)(void *, void *, size_t, size_t);
|
||||
|
||||
using hook_func = internal::callable<void, state &>;
|
||||
/** @brief A call hook function
|
||||
*
|
||||
* It is possible to set up a call hook for each thread, which is called
|
||||
* upon entering the VM. The hook returns nothing and receives the thread
|
||||
* reference.
|
||||
*/
|
||||
using hook_func = internal::callable<void, state &>;
|
||||
|
||||
/** @brief A command function
|
||||
*
|
||||
* This is how every command looks. It returns nothing and takes the thread
|
||||
* reference, a span of input arguments, and a reference to return value.
|
||||
*/
|
||||
using command_func = internal::callable<
|
||||
void, state &, span_type<any_value>, any_value &
|
||||
>;
|
||||
|
||||
/** @brief The loop state
|
||||
*
|
||||
* This is returned by state::run_loop().
|
||||
*/
|
||||
enum class loop_state {
|
||||
NORMAL = 0, BREAK, CONTINUE
|
||||
NORMAL = 0, /**< @brief The iteration ended normally. */
|
||||
BREAK, /**< @brief The iteration was broken out of. */
|
||||
CONTINUE /**< @brief The iteration ended early. */
|
||||
};
|
||||
|
||||
/** @brief The Cubescript thread
|
||||
*
|
||||
* Represents a Cubescript thread, either the main thread or a side thread
|
||||
* depending on how it's created. The state is what you create first and
|
||||
* also what you should always destroy last.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT state {
|
||||
/** @brief Create a new Cubescript main thread
|
||||
*
|
||||
* This creates a main thread without specifying an allocation function,
|
||||
* using a simple, builtin implementation. Otherwise it is the same.
|
||||
*/
|
||||
state();
|
||||
state(alloc_func func, void *data);
|
||||
|
||||
/** @brief Create a new Cubescript main thread
|
||||
*
|
||||
* For this variant you have to specify a function used to allocate memory.
|
||||
* The optional data will be passed to allocation every time and is your
|
||||
* only way to pass custom data to it, since unlike other kinds of hooks,
|
||||
* the allocation function is a plain function pointer to ensure it never
|
||||
* allocates by itself.
|
||||
*/
|
||||
state(alloc_func func, void *data = nullptr);
|
||||
|
||||
/** @brief Destroy the thread
|
||||
*
|
||||
* If the thread is a main thread, all state is destroyed. That means
|
||||
* main threads should always be destroyed last.
|
||||
*/
|
||||
virtual ~state();
|
||||
|
||||
/** @brief Cubescript threads are not copyable */
|
||||
state(state const &) = delete;
|
||||
|
||||
/** @brief Move-construct the Cubescript thread
|
||||
*
|
||||
* Keep in mind that you should never use `s` after this is done.
|
||||
*/
|
||||
state(state &&s);
|
||||
|
||||
/** @brief Cubescript threads are not copy assignable */
|
||||
state &operator=(state const &) = delete;
|
||||
|
||||
/** @brief Move-assign the Cubescript thread
|
||||
*
|
||||
* Keep in mind that you should never use `s` after this is done.
|
||||
* The original `this` is destroyed in the process.
|
||||
*/
|
||||
state &operator=(state &&s);
|
||||
|
||||
/** @brief Swap two Cubescript threads */
|
||||
void swap(state &s);
|
||||
|
||||
/** @brief Create a non-main thread
|
||||
*
|
||||
* This creates a non-main thread. You can also create non-main threads
|
||||
* using other non-main threads, but they will always all be dependent
|
||||
* on the main thread they originally came from.
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
state new_thread();
|
||||
|
||||
/** @brief Attach a call hook to the thread
|
||||
*
|
||||
* The call hook is called every time the VM is entered. You can use
|
||||
* this for debugging and other tracking, or say, as a means of
|
||||
* interrupting execution from the side in an interactive interpreter.
|
||||
*/
|
||||
template<typename F>
|
||||
hook_func set_call_hook(F &&f) {
|
||||
return set_call_hook(
|
||||
hook_func{std::forward<F>(f), callable_alloc, this}
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Get a reference to the call hook */
|
||||
hook_func const &get_call_hook() const;
|
||||
|
||||
/** @brief Get a reference to the call hook */
|
||||
hook_func &get_call_hook();
|
||||
|
||||
/** @brief Clear override state for the given ident
|
||||
*
|
||||
* If the ident is overridden, clear the flag. Global variables will have
|
||||
* their value restored to the original, and the changed hook will be
|
||||
* triggered. Aliases will be set to an empty string.
|
||||
*
|
||||
* Other ident types will do nothing.
|
||||
*/
|
||||
void clear_override(ident &id);
|
||||
|
||||
/** @brief Clear override state for all idents.
|
||||
*
|
||||
* @see clear_override()
|
||||
*/
|
||||
void clear_overrides();
|
||||
|
||||
/** @brief Create a new integer var
|
||||
*
|
||||
* @param n the name
|
||||
* @param v the default value
|
||||
* @throw cubescript::error in case of redefinition or invalid name
|
||||
*/
|
||||
integer_var &new_var(
|
||||
std::string_view n, integer_type v, bool read_only = false,
|
||||
var_type vtp = var_type::DEFAULT
|
||||
);
|
||||
|
||||
/** @brief Create a new float var
|
||||
*
|
||||
* @param n the name
|
||||
* @param v the default value
|
||||
* @throw cubescript::error in case of redefinition or invalid name
|
||||
*/
|
||||
float_var &new_var(
|
||||
std::string_view n, float_type v, bool read_only = false,
|
||||
var_type vtp = var_type::DEFAULT
|
||||
);
|
||||
|
||||
/** @brief Create a new string var
|
||||
*
|
||||
* @param n the name
|
||||
* @param v the default value
|
||||
* @throw cubescript::error in case of redefinition or invalid name
|
||||
*/
|
||||
string_var &new_var(
|
||||
std::string_view n, std::string_view v, bool read_only = false,
|
||||
var_type vtp = var_type::DEFAULT
|
||||
);
|
||||
|
||||
/** @brief Create a new ident
|
||||
*
|
||||
* If such ident already exists, nothing will be done and a reference
|
||||
* will be returned. Otherwise, a new alias will be created and this
|
||||
* alias will be returned, however it will not be visible from the
|
||||
* language until actually assigned (it does not exist to the language
|
||||
* just as is).
|
||||
*
|
||||
* @param n the name
|
||||
* @throw cubescript::error in case of invalid name
|
||||
*/
|
||||
ident &new_ident(std::string_view n);
|
||||
|
||||
/** @brief Reset a variable or alias
|
||||
*
|
||||
* This is like clear_override() except it works by name and performs
|
||||
* extra checks.
|
||||
*
|
||||
* @throw cubescript::error if non-existent or read only
|
||||
*/
|
||||
void reset_var(std::string_view name);
|
||||
|
||||
/** @brief Touch a variable
|
||||
*
|
||||
* If an ident with the given name exists and is a global variable,
|
||||
* a changed hook will be triggered with it, acting like if a new
|
||||
* value was set, but without actually setting it.
|
||||
*/
|
||||
void touch_var(std::string_view name);
|
||||
|
||||
/** @brief Register a command
|
||||
*
|
||||
* This registers a builtin command. A command consists of a valid name,
|
||||
* a valid argument list, and a function to call.
|
||||
*
|
||||
* The argument list is a simple list of types. Currently the following
|
||||
* simple types are recognized:
|
||||
*
|
||||
* * `s` - a string
|
||||
* * `i` - an integer, default value 0
|
||||
* * `b` - an integer, default value `limits<integer_type>::min`
|
||||
* * `f` - a float, default value 0
|
||||
* * `F` - a float, default value is the preceeding value
|
||||
* * `t` - any (passed as is)
|
||||
* * `e` - bytecode
|
||||
* * `E` - condition (see below)
|
||||
* * `r` - ident
|
||||
* * `N` - number of real arguments passed up until now
|
||||
* * `$` - self ident (the command, except for special hooks)
|
||||
*
|
||||
* Commands also support variadics. Variadic commands have their type
|
||||
* list suffixed with `V` or `C`. A `V` variadic is a traditional variadic
|
||||
* function, while `C` will concatenate all inputs into a single big
|
||||
* string.
|
||||
*
|
||||
* If either `C` or `V` is used alone, the inputs are any arbitrary
|
||||
* values. However, they can also be used with repetition. Repetition
|
||||
* works for example like `if2V`. The `2` is the number of types to
|
||||
* repeat; it must be at most the number of simple types preceeding
|
||||
* it. It must be followed by `V` or `C`. This specific example means
|
||||
* that the variadic arguments are a sequence of integer, float, integer,
|
||||
* float, integer, float and so on.
|
||||
*
|
||||
* The resulting command stores the number of arguments it takes. The
|
||||
* variadic part is not a part of it (neither is the part subject to
|
||||
* repetition), while all simple types are a part of it (including
|
||||
* 'fake' ones like argument count).
|
||||
*
|
||||
* It is also possible to register special commands. Special commands work
|
||||
* like normal ones but are special-purpose. The currently allowed special
|
||||
* commands are `//ivar`, `//fvar`, `//svar` and `//var_changed`. These
|
||||
* are the only commands where the name can be in this format.
|
||||
*
|
||||
* The first three are handlers for for global variables, used when either
|
||||
* printing or setting them using syntax `varname optional_vals` or using
|
||||
* `varname = value`. Their type signature must always start with `$`
|
||||
* and can be followed by any user types, generally you will also want
|
||||
* to terminate the list with `N` to find out whether any values were
|
||||
* passed.
|
||||
*
|
||||
* This way you can have custom handlers for printing as well as custom
|
||||
* syntaxes for setting (e.g. your custom integer var handler may want to
|
||||
* take up to 4 values to allow setting of RGBA color channels). When no
|
||||
* arguments are passed (checked using `N`) you will want to print the
|
||||
* value using a format you want. When using the `=` assignment syntax,
|
||||
* one value is passed.
|
||||
*
|
||||
* There are builtin default handlers that take at most one arg (`i`, `f`
|
||||
* and `s`) which also print to standard output (`name = value`).
|
||||
*
|
||||
* For `//var_changed`, there is no default handler. The arg list must be
|
||||
* just `$`. This will be called whenever a value of an integer, float
|
||||
* or string builtin variable changes.
|
||||
*
|
||||
* For these builtins, `$` will refer to the variable ident, not to the
|
||||
* builtin command.
|
||||
*
|
||||
* @throw cubescript::error upon redefinition, invalid name or arg list
|
||||
*/
|
||||
template<typename F>
|
||||
command &new_command(
|
||||
std::string_view name, std::string_view args, F &&f
|
||||
|
@ -88,30 +319,149 @@ struct LIBCUBESCRIPT_EXPORT state {
|
|||
);
|
||||
}
|
||||
|
||||
/** @brief Get a specific cubescript::ident (or `nullptr`) */
|
||||
ident *get_ident(std::string_view name);
|
||||
|
||||
/** @brief Get a specific cubescript::alias (or `nullptr`) */
|
||||
alias *get_alias(std::string_view name);
|
||||
|
||||
/** @brief Check if a cubescript::ident of the given name exists */
|
||||
bool have_ident(std::string_view name);
|
||||
|
||||
/** @brief Get a span of all idents */
|
||||
span_type<ident *> get_idents();
|
||||
|
||||
/** @brief Get a span of all idents */
|
||||
span_type<ident const *> get_idents() const;
|
||||
|
||||
/** @brief Execute the given bytecode reference
|
||||
*
|
||||
* @return the return value
|
||||
*/
|
||||
any_value run(bcode_ref const &code);
|
||||
|
||||
/** @brief Execute the given string as code
|
||||
*
|
||||
* @return the return value
|
||||
*/
|
||||
any_value run(std::string_view code);
|
||||
|
||||
/** @brief Execute the given string as code
|
||||
*
|
||||
* This variant takes a file name to be included in debug information.
|
||||
* While the library provides no way to deal with file I/O, this is a
|
||||
* support function to make implementing these better.
|
||||
*
|
||||
* @param source a source file name
|
||||
*
|
||||
* @return the return value
|
||||
*/
|
||||
any_value run(std::string_view code, std::string_view source);
|
||||
|
||||
/** @brief Execute the given ident
|
||||
*
|
||||
* If a command, it will simply be executed with the given arguments,
|
||||
* ensuring that missing ones are filled in and types are set properly.
|
||||
* If a builtin variable, the appropriate handler will be called. If
|
||||
* an alias, the value of it will be compiled and executed. Any other
|
||||
* ident type will simply do nothing.
|
||||
*
|
||||
* @return the return value
|
||||
*/
|
||||
any_value run(ident &id, span_type<any_value> args);
|
||||
|
||||
/** @brief Execute a loop body
|
||||
*
|
||||
* This exists to implement custom loop commands. A loop command will
|
||||
* consist of your desired loop and will take a body as an argument
|
||||
* (with bytecode type); this body will be run using this API. The
|
||||
* return value can be used to check if the loop was broken out of
|
||||
* or continued, and take steps accordingly.
|
||||
*
|
||||
* Some loops may evaluate to values, while others may not.
|
||||
*/
|
||||
loop_state run_loop(bcode_ref const &code, any_value &ret);
|
||||
|
||||
/** @brief Execute a loop body
|
||||
*
|
||||
* This version ignores the return value of the body.
|
||||
*/
|
||||
loop_state run_loop(bcode_ref const &code);
|
||||
|
||||
/** @brief Get if the thread is in override mode
|
||||
*
|
||||
* If the thread is in override mode, any assigned alias or variable will
|
||||
* be given the overridden flag, with variables also saving their old
|
||||
* value. Upon clearing the flag (using clear_override() or similar)
|
||||
* the old value will be restored (aliases will be set to an empty
|
||||
* string).
|
||||
*
|
||||
* Overridable variables will always act like if the thread is in override
|
||||
* mode, even if it's not.
|
||||
*
|
||||
* Keep in mind that if an alias is pushed, its flags will be cleared once
|
||||
* popped.
|
||||
*
|
||||
* @see set_override_mode()
|
||||
*/
|
||||
bool get_override_mode() const;
|
||||
|
||||
/** @brief Set the thread's override mode
|
||||
*
|
||||
* @see get_override_mode()
|
||||
*/
|
||||
bool set_override_mode(bool v);
|
||||
|
||||
/** @brief Get if the thread is in persist most
|
||||
*
|
||||
* In persist mode, newly assigned aliases will have the persist flag
|
||||
* set on them, which is an indicator that they should be saved to disk
|
||||
* like persistent variables. The library does no saving, so by default
|
||||
* it works as an indicator for the user.
|
||||
*
|
||||
* Keep in mind that if an alias is pushed, its flags will be cleared once
|
||||
* popped.
|
||||
*
|
||||
* @see set_persist_mode()
|
||||
*/
|
||||
bool get_persist_mode() const;
|
||||
|
||||
/** @brief Set the thread's persist mode
|
||||
*
|
||||
* @see get_persist_mode()
|
||||
*/
|
||||
bool set_persist_mode(bool v);
|
||||
|
||||
/** @brief Get the maximum run depth of the VM
|
||||
*
|
||||
* If zero, it is unlimited, otherwise it specifies how much the VM is
|
||||
* allowed to recurse. By default, it is zero.
|
||||
*
|
||||
* @see set_max_run_depth()
|
||||
*/
|
||||
std::size_t get_max_run_depth() const;
|
||||
|
||||
/** @brief Set the maximum run depth ov the VM
|
||||
*
|
||||
* If zero, it is unlimited (this is the default). You can limit how much
|
||||
* the VM is allowed to recurse if you have specific constraints to adhere
|
||||
* to.
|
||||
*
|
||||
* @return the old value
|
||||
*/
|
||||
std::size_t set_max_run_depth(std::size_t v);
|
||||
|
||||
/** @brief Set a variable
|
||||
*
|
||||
* This will set something of the given name to the given value. The
|
||||
* something may be a variable or an alias.
|
||||
*
|
||||
* If no ident of such name exists, a new alias will be created and
|
||||
* set.
|
||||
*
|
||||
* @throw cubescript::error if `name` is a builtin ident (a registered
|
||||
* command or similar) or if it is invalid
|
||||
*/
|
||||
void set_alias(std::string_view name, any_value v);
|
||||
|
||||
private:
|
||||
|
@ -136,11 +486,78 @@ private:
|
|||
struct thread_state *p_tstate = nullptr;
|
||||
};
|
||||
|
||||
/** @brief Initialize the base library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The base library contains core constructs for things such as
|
||||
* error handling, conditionals, looping, and var/alias management.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_list()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_base(state &cs);
|
||||
|
||||
/** @brief Initialize the math library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The math library contains arithmetic and other math related
|
||||
* functions.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_list()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_math(state &cs);
|
||||
|
||||
/** @brief Initialize the string library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The string library contains commands to manipulate strings.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_list()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_string(state &cs);
|
||||
|
||||
/** @brief Initialize the list library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The list library contains commands to manipulate lists.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_list(state &cs);
|
||||
|
||||
/** @brief Initialize all standard libraries
|
||||
*
|
||||
* This is like calling each of the individual standard library init
|
||||
* functions and exists mostly just for convenience.
|
||||
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_list()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_all(state &cs);
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
|
|
@ -20,21 +20,80 @@
|
|||
|
||||
namespace cubescript {
|
||||
|
||||
/** @brief A safe alias handler for commands
|
||||
*
|
||||
* In general, when dealing with aliases in commands, you do not want to
|
||||
* set them directly, since this would set the alias globally. Instead, you
|
||||
* can use this to make aliases local to the command.
|
||||
*
|
||||
* Internally, each Cubescript thread has a mapping for alias state within
|
||||
* the thread. This mapping is stack based - which means you can push an
|
||||
* alias, and then anything affecting the value of the alias in that thread
|
||||
* will only be visible until the stack is popped. This structure provides
|
||||
* a safe means of handling the alias stack; constructing it will push the
|
||||
* alias, destroying it will pop it.
|
||||
*
|
||||
* Therefore, what you can do is something like this:
|
||||
*
|
||||
* ```
|
||||
* if (alias_local s{my_thread, &my_thread.new_ident("test")}; s) {
|
||||
* // branch taken when the alias was successfully pushed
|
||||
* // setting the alias will only be visible within this scope
|
||||
* s.set(some_value); // a convenient setter
|
||||
* my_thread.run(...);
|
||||
* } else {
|
||||
* // you can handle an error here
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The `else` branch can happen one case; either the given ident is `nullptr`
|
||||
* (which will never happen here) or it's not a cubescript::alias (which can
|
||||
* happen if an ident of such name already exists and is not an alias). If
|
||||
* it fails, obviously no push/pop happens.
|
||||
*
|
||||
* Since the goal is to interact tightly with RAII and ensure consistency at
|
||||
* all times, it is not possible to copy or move this object. That means you
|
||||
* should also not be storing it; it should be used purely as a scope based
|
||||
* alias stack manager.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT alias_local {
|
||||
/** @brief Construct the local handler */
|
||||
alias_local(state &cs, ident *a);
|
||||
|
||||
/** @brief Destroy the local handler */
|
||||
~alias_local();
|
||||
|
||||
/** @brief Local handlers are not copyable */
|
||||
alias_local(alias_local const &) = delete;
|
||||
|
||||
/** @brief Local handlers are not movable */
|
||||
alias_local(alias_local &&) = delete;
|
||||
|
||||
/** @brief Local handlers are not copy assignable */
|
||||
alias_local &operator=(alias_local const &) = delete;
|
||||
|
||||
/** @brief Local handlers are not move assignable */
|
||||
alias_local &operator=(alias_local &&v) = delete;
|
||||
|
||||
/** @brief Get the contained alias
|
||||
*
|
||||
* @return the alias or `nullptr` if none set
|
||||
*/
|
||||
alias *get_alias() noexcept { return p_alias; }
|
||||
|
||||
/** @brief Get the contained alias
|
||||
*
|
||||
* @return the alias or `nullptr` if none set
|
||||
*/
|
||||
alias const *get_alias() const noexcept { return p_alias; }
|
||||
|
||||
/** @brief Set the contained alias's value
|
||||
*
|
||||
* @return `true` if the alias is valid, `false` otherwise
|
||||
*/
|
||||
bool set(any_value val);
|
||||
|
||||
/** @brief Get if there is an alias associated with this handler */
|
||||
explicit operator bool() const noexcept;
|
||||
|
||||
private:
|
||||
|
@ -42,34 +101,105 @@ private:
|
|||
void *p_sp;
|
||||
};
|
||||
|
||||
/** @brief A list parser
|
||||
*
|
||||
* Cubescript does not have data structures and everything is a string.
|
||||
* However, you can represent lists as strings; there is a standard syntax
|
||||
* to them.
|
||||
*
|
||||
* A list in Cubescript is simply a bunch of items separated by whitespace.
|
||||
* The items can take the form of any literal value Cubescript has. That means
|
||||
* they can be number literals, they can be words, and they can be strings.
|
||||
* Strings can be quoted either with double quotes, square brackets or even
|
||||
* parenthesis; basically any syntax representing a value.
|
||||
*
|
||||
* Comments (anything following two slashes, inclusive) are skipped. As far
|
||||
* as allowed whitespace consisting an item delimiter goes, this is either
|
||||
* regular spaces, horizontal tabs, or newlines.
|
||||
*
|
||||
* Keep in mind that it does not own the string it is parsing. Therefore,
|
||||
* you have to make sure to keep it alive for as long as the parser is.
|
||||
*
|
||||
* The input string by itself should not be quoted.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT list_parser {
|
||||
/** @brief Construct a list parser.
|
||||
*
|
||||
* Nothing is done until you actually start parsing.
|
||||
*
|
||||
* @param cs the thread
|
||||
* @param s the string representing the list
|
||||
*/
|
||||
list_parser(state &cs, std::string_view s = std::string_view{}):
|
||||
p_state{&cs}, p_input_beg{s.data()}, p_input_end{s.data() + s.size()}
|
||||
{}
|
||||
|
||||
/** @brief Reset the input string for the list */
|
||||
void set_input(std::string_view s) {
|
||||
p_input_beg = s.data();
|
||||
p_input_end = s.data() + s.size();
|
||||
}
|
||||
|
||||
/** @brief Get the current input string in the parser
|
||||
*
|
||||
* The already read items will not be contained in the result.
|
||||
*/
|
||||
std::string_view get_input() const {
|
||||
return std::string_view{
|
||||
p_input_beg, std::size_t(p_input_end - p_input_beg)
|
||||
};
|
||||
}
|
||||
|
||||
/** @brief Attempt to parse an item
|
||||
*
|
||||
* This will first skip whitespace and then attempt to read an element.
|
||||
*
|
||||
* @return `true` if an element was found, `false` otherwise
|
||||
*/
|
||||
bool parse();
|
||||
|
||||
/** @brief Get the number of items in the current list
|
||||
*
|
||||
* This will not contain items that are already parsed out, and will
|
||||
* parse the list itself, i.e. the final state will be an empty list.
|
||||
*/
|
||||
std::size_t count();
|
||||
|
||||
/** @brief Get the currently parsed item
|
||||
*
|
||||
* If the item was quoted with double quotes, the contents will be run
|
||||
* through cubescript::unescape_string() first.
|
||||
*
|
||||
* @see get_raw_item()
|
||||
* @see get_quoted_item()
|
||||
*/
|
||||
string_ref get_item() const;
|
||||
|
||||
/** @brief Get the currently parsed raw item
|
||||
*
|
||||
* Unlike get_item(), this will not unescape the string under any
|
||||
* circumstances and represents simply a slice of the original input.
|
||||
*
|
||||
* @see get_item()
|
||||
* @see get_quoted_item()
|
||||
*/
|
||||
std::string_view get_raw_item() const {
|
||||
return std::string_view{p_ibeg, std::size_t(p_iend - p_ibeg)};
|
||||
}
|
||||
|
||||
/** @brief Get the currently parsed raw item
|
||||
*
|
||||
* Like get_raw_item(), but contains the quotes too, if there were any.
|
||||
* Likewise, the resulting view is just a slice of the original input.
|
||||
*
|
||||
* @see get_item()
|
||||
* @see get_raw_item()
|
||||
*/
|
||||
std::string_view get_quoted_item() const {
|
||||
return std::string_view{p_qbeg, std::size_t(p_qend - p_qbeg)};
|
||||
}
|
||||
|
||||
/** @brief Skip whitespace in the input until a value is reached. */
|
||||
void skip_until_item();
|
||||
|
||||
private:
|
||||
|
@ -80,11 +210,36 @@ private:
|
|||
char const *p_qbeg{}, *p_qend{};
|
||||
};
|
||||
|
||||
|
||||
/** @brief Parse a double quoted Cubescript string
|
||||
*
|
||||
* This parses double quoted strings according to the Cubescript syntax. The
|
||||
* string has to begin with a double quote; if it does not for any reason,
|
||||
* `str.data()` is returned.
|
||||
*
|
||||
* Escape sequences are not expanded and have the syntax `^X` where X is the
|
||||
* specific escape character (e.g. `^n` for newline). It is possible to make
|
||||
* the string multiline; the line needs to end with `\\`.
|
||||
*
|
||||
* Strings must be terminated again with double quotes.
|
||||
*
|
||||
* @param cs the thread
|
||||
* @param str the input string
|
||||
* @param[out] nlines the number of lines in the string
|
||||
*
|
||||
* @return a pointer to the character after the last double quotes
|
||||
* @throw cubescript::error if the string is started but not finished
|
||||
*
|
||||
* @see cubescript::parse_word()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT char const *parse_string(
|
||||
state &cs, std::string_view str, size_t &nlines
|
||||
);
|
||||
|
||||
/** @brief Parse a double quoted Cubescript string
|
||||
*
|
||||
* This overload has the same semantics but it does not return the number
|
||||
* of lines.
|
||||
*/
|
||||
inline char const *parse_string(
|
||||
state &cs, std::string_view str
|
||||
) {
|
||||
|
@ -92,15 +247,50 @@ inline char const *parse_string(
|
|||
return parse_string(cs, str, nlines);
|
||||
}
|
||||
|
||||
/** @brief Parse a Cubescript word.
|
||||
*
|
||||
* A Cubescript word is a sequence of any characters that are not whitespace
|
||||
* (spaces, newlines, tabs) or a comment (two consecutive slashes). It is
|
||||
* allowed to have parenthesis and square brackets as long a they are balanced.
|
||||
*
|
||||
* Examples of valid words: `foo`, `test123`, `125.4`, `[foo]`, `hi(bar)`.
|
||||
*
|
||||
* If a non-word character is encountered immediately, the resulting pointer
|
||||
* will be `str.data()`.
|
||||
*
|
||||
* Keep in mind that a valid word may not be a valid ident name (e.g. numbers
|
||||
* are valid words but not valid ident names).
|
||||
*
|
||||
* @return a pointer to the first character after the word
|
||||
* @throw cubescript::error if there is unbalanced `[` or `(`
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT char const *parse_word(
|
||||
state &cs, std::string_view str
|
||||
);
|
||||
|
||||
/** @brief Concatenate a span of values
|
||||
*
|
||||
* The input values are concatenated by `sep`. Non-integer/float/string
|
||||
* input values are considered empty strings. Integers and floats are
|
||||
* converted to strings. The input list is not affected, however.
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT string_ref concat_values(
|
||||
state &cs, span_type<any_value> vals,
|
||||
std::string_view sep = std::string_view{}
|
||||
);
|
||||
|
||||
/** @brief Escape a Cubescript string
|
||||
*
|
||||
* This reads and input string and writes it into `writer`, treating special
|
||||
* characters as escape sequences. Newlines are turned into `^n`, tabs are
|
||||
* turned into `^t`, vertical tabs into `^f`; double quotes are prefixed
|
||||
* with a caret, carets are duplicated. All other characters are passed
|
||||
* through.
|
||||
*
|
||||
* @return `writer` after writing into it
|
||||
*
|
||||
* @see cubescript::unescape_string()
|
||||
*/
|
||||
template<typename R>
|
||||
inline R escape_string(R writer, std::string_view str) {
|
||||
*writer++ = '"';
|
||||
|
@ -118,6 +308,21 @@ inline R escape_string(R writer, std::string_view str) {
|
|||
return writer;
|
||||
}
|
||||
|
||||
/** @brief Unscape a Cubescript string
|
||||
*
|
||||
* If a caret is encountered, it is skipped. If the following character is `n`,
|
||||
* it is turned into a newline; `t` is turned into a tab, `f` into a vertical
|
||||
* tab, double quote is written as is, as is a second caret. Any others are
|
||||
* written as they are.
|
||||
*
|
||||
* If a backslash is encountered and followed by a newline, the sequence is
|
||||
* skipped, otherwise the backslash is written out. Any other character is
|
||||
* written out as is.
|
||||
*
|
||||
* @return `writer` after writing into it
|
||||
*
|
||||
* @see cubescript::unescape_string()
|
||||
*/
|
||||
template<typename R>
|
||||
inline R unescape_string(R writer, std::string_view str) {
|
||||
for (auto it = str.begin(); it != str.end(); ++it) {
|
||||
|
@ -128,7 +333,7 @@ inline R unescape_string(R writer, std::string_view str) {
|
|||
}
|
||||
switch (*it) {
|
||||
case 'n': *writer++ = '\n'; break;
|
||||
case 't': *writer++ = '\r'; break;
|
||||
case 't': *writer++ = '\t'; break;
|
||||
case 'f': *writer++ = '\f'; break;
|
||||
case '"': *writer++ = '"'; break;
|
||||
case '^': *writer++ = '^'; break;
|
||||
|
@ -156,6 +361,18 @@ inline R unescape_string(R writer, std::string_view str) {
|
|||
return writer;
|
||||
}
|
||||
|
||||
/** @brief Print a Cubescript stack
|
||||
*
|
||||
* This prints out the Cubescript stack as stored in cubescript::error, into
|
||||
* the `writer`. Each level is written on its own line. The line starts with
|
||||
* two spaces. If there is a gap in the stack and we've reached index 1,
|
||||
* the two spaces are followed with two periods. Following that is the index
|
||||
* followed by a right parenthesis, a space, and the name of the ident.
|
||||
*
|
||||
* The last line is not terminated with a newline.
|
||||
*
|
||||
* @return `writer` after writing into it
|
||||
*/
|
||||
template<typename R>
|
||||
inline R print_stack(R writer, stack_state const &st) {
|
||||
char buf[32] = {0};
|
||||
|
@ -172,6 +389,7 @@ inline R print_stack(R writer, stack_state const &st) {
|
|||
char const *p = buf;
|
||||
std::copy(p, p + strlen(p), writer);
|
||||
*writer++ = ')';
|
||||
*writer++ = ' ';
|
||||
std::copy(name.begin(), name.end(), writer);
|
||||
nd = nd->next;
|
||||
if (nd) {
|
||||
|
|
Loading…
Reference in New Issue