diff --git a/include/QF/progs.h b/include/QF/progs.h index 233659dd2..76b8e0000 100644 --- a/include/QF/progs.h +++ b/include/QF/progs.h @@ -1628,6 +1628,32 @@ void PR_FreeString (progs_t *pr, pr_string_t str); */ void PR_FreeTempStrings (progs_t *pr); +/** Callback for handling %@ in PR_Sprintf format strings. + + \param pr pointer to ::progs_t VM struct + \param at_param Parameter to be converted to a string. Usually a progs + pointer value. It is the callee's responsibility to + interpret the value as a pointer. + \param data User data pointer as passed to PR_Sprintf_SetAtHandler + \return String to be printed in place of %@ +*/ +typedef const char *(*prstr_at_handler_t) (progs_t *pr, pr_ptr_t at_param, + void *data); + +/** Set the at_handler callback and its user data pointer. + + The at_handler callback defaults to null resulting in [ptr] in the output + string. Setting the callback allows the at_handler to interpret the value + (nominally pr_ptr_t, but always a 32-bit value) as it chooses. + + \param pr pointer to ::progs_t VM struct + \param at_handler Function pointer for callback to interpret the parameter + corresponding to the %@ format specifier. + \param data User data pointer, passed to \a at_handler. +*/ +void PR_Sprintf_SetAtHandler (progs_t *pr, prstr_at_handler_t at_handler, + void *data); + /** Formatted printing similar to C's vsprintf, but using QC types. The format string is a string of characters (other than \c \%) to be printed that includes optional format specifiers, one for each arg to be diff --git a/libs/gamecode/pr_strings.c b/libs/gamecode/pr_strings.c index 840e82662..d4f423c4e 100644 --- a/libs/gamecode/pr_strings.c +++ b/libs/gamecode/pr_strings.c @@ -90,6 +90,8 @@ typedef struct prstr_resources_s { int num_strings; fmt_item_t *free_fmt_items; dstring_t *print_str; + prstr_at_handler_t at_handler; + void *at_handler_data; } prstr_resources_t; typedef enum { @@ -866,6 +868,8 @@ typedef struct fmt_state_s { fmt_item_t *fmt_items; fmt_item_t **fi; int fmt_count; + prstr_at_handler_t at_handler; + void *at_handler_data; } fmt_state_t; static inline void @@ -1052,6 +1056,25 @@ fmt_state_conversion (fmt_state_t *state) switch ((conv = *state->c++)) { case '@': // object + pr_ptr_t at_param = P_UINT (pr, state->fmt_count); + if (state->at_handler) { + const char *at_str = state->at_handler (pr, at_param, + state->at_handler_data); + (*state->fi)->type = 's'; + (*state->fi)->data.string_var = at_str; + } else { + (*state->fi)->type = 's'; + (*state->fi)->data.string_var = "["; + fmt_append_item (state); + + (*state->fi)->flags |= FMT_ALTFORM; + (*state->fi)->type = 'x'; + (*state->fi)->data.uint_var = at_param; + fmt_append_item (state); + + (*state->fi)->type = 's'; + (*state->fi)->data.string_var = "]"; + } state->fmt_count++; fmt_append_item (state); break; @@ -1200,6 +1223,15 @@ fmt_state_format (fmt_state_t *state) } ///@} +VISIBLE void +PR_Sprintf_SetAtHandler (progs_t *pr, prstr_at_handler_t at_handler, + void *data) +{ + prstr_resources_t *res = pr->pr_string_resources; + res->at_handler = at_handler; + res->at_handler_data = data; +} + VISIBLE void PR_Sprintf (progs_t *pr, dstring_t *result, const char *name, const char *format, int count, pr_type_t **args) @@ -1214,6 +1246,8 @@ PR_Sprintf (progs_t *pr, dstring_t *result, const char *name, *state.fi = new_fmt_item (res); state.c = format; state.state = fmt_state_format; + state.at_handler = res->at_handler; + state.at_handler_data = res->at_handler_data; if (!name) name = "PR_Sprintf";