quakeforge/libs/gamecode/pr_strings.c
Bill Currie be447fc9d6 [gamecode] Add optional support for %@ strings
If no handler has been registered, then the corresponding parameter is
printed as a pointer but with surrounding brackets (eg, [0xfc48]). This
will allow the ruamoko runtime to implement object printing.
2022-09-09 14:48:03 +09:00

1298 lines
30 KiB
C

/*
pr_strings.c
progs string managment
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <inttypes.h>
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/progs.h"
// format adjustments
#define FMT_ALTFORM (1<<0)
#define FMT_LJUSTIFY (1<<1)
#define FMT_ZEROPAD (1<<2)
#define FMT_ADDSIGN (1<<3)
#define FMT_ADDBLANK (1<<4)
#define FMT_HEX (1<<5)
#define FMT_LONG (1<<6)
#define FMT_WIDTH (1<<7)
typedef struct fmt_item_s {
byte type;
unsigned flags;
int minFieldWidth;
int precision;
union {
const char *string_var;
pr_int_t int_var;
pr_uint_t uint_var;
float float_var;
double double_var;
pr_long_t long_var;
pr_ulong_t ulong_var;
} data;
struct fmt_item_s *next;
} fmt_item_t;
typedef struct strref_slot_s {
struct strref_slot_s *next;
struct strref_slot_s **prev;
strref_t *strref;
} strref_slot_t;
typedef struct prstr_resources_s {
progs_t *pr;
dstring_mem_t ds_mem;
strref_t *free_string_refs;
strref_t *static_strings;
strref_t **string_map;
strref_slot_t return_strings[PR_RS_SLOTS];
strref_slot_t *rs_slot;
unsigned dyn_str_size;
struct hashtab_s *strref_hash;
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 {
str_free,
str_static,
str_dynamic,
str_mutable,
str_temp,
str_return,
} str_e;
struct strref_s {
strref_t *next;
strref_slot_t *rs_slot;
str_e type;
union {
char *string;
dstring_t *dstring;
} s;
};
static void *
pr_strings_alloc (void *_pr, size_t size)
{
progs_t *pr = (progs_t *) _pr;
return PR_Zone_Malloc (pr, size);
}
static void
pr_strings_free (void *_pr, void *ptr)
{
progs_t *pr = (progs_t *) _pr;
PR_Zone_Free (pr, ptr);
}
static void *
pr_strings_realloc (void *_pr, void *ptr, size_t size)
{
progs_t *pr = (progs_t *) _pr;
return PR_Zone_Realloc (pr, ptr, size);
}
static strref_t *
new_string_ref (prstr_resources_t *res)
{
strref_t *sr;
if (!res->free_string_refs) {
int i;
size_t size;
res->dyn_str_size++;
size = res->dyn_str_size * sizeof (strref_t *);
res->string_map = realloc (res->string_map, size);
if (!res->string_map)
PR_Error (res->pr, "out of memory");
if (!(res->free_string_refs = calloc (1024, sizeof (strref_t))))
PR_Error (res->pr, "out of memory");
res->string_map[res->dyn_str_size - 1] = res->free_string_refs;
for (i = 0, sr = res->free_string_refs; i < 1023; i++, sr++)
sr->next = sr + 1;
sr->next = 0;
}
sr = res->free_string_refs;
res->free_string_refs = sr->next;
sr->next = 0;
sr->rs_slot = 0;
return sr;
}
static void
free_string_ref (prstr_resources_t *res, strref_t *sr)
{
sr->type = str_free;
sr->next = res->free_string_refs;
res->free_string_refs = sr;
}
static __attribute__((pure)) pr_string_t
string_index (prstr_resources_t *res, strref_t *sr)
{
long o = (long) (sr - res->static_strings);
unsigned i;
if (o >= 0 && o < res->num_strings)
return sr->s.string - res->pr->pr_strings;
for (i = 0; i < res->dyn_str_size; i++) {
int d = sr - res->string_map[i];
if (d >= 0 && d < 1024)
return ~(i * 1024 + d);
}
return 0;
}
static const char *
strref_get_key (const void *_sr, void *notused)
{
strref_t *sr = (strref_t*)_sr;
// only static strings will ever be in the hash table
return sr->s.string;
}
static void
strref_free (void *_sr, void *_res)
{
__auto_type res = (prstr_resources_t *) _res;
__auto_type sr = (strref_t *) _sr;
// Since this is called only by Hash_FlushTable, the memory pointed
// to by sr->string or sr->dstring has already been lost in the progs
// load/reload and thus there's no need to free it.
// free the string and ref only if it's not a static string
if (sr < res->static_strings
|| sr >= res->static_strings + res->num_strings) {
free_string_ref (res, sr);
}
}
static void
pr_strings_clear (progs_t *pr, void *data)
{
__auto_type res = (prstr_resources_t *) data;
int i;
for (i = 0; i < PR_RS_SLOTS; i++) {
if (res->return_strings[i].strref)
free_string_ref (res, res->return_strings[i].strref);
res->return_strings[i].strref = 0;
}
if (!res->rs_slot) {
strref_slot_t * const rs = res->return_strings;
for (i = 0; i < PR_RS_SLOTS; i++) {
rs[i].next = &rs[(i + 1) % PR_RS_SLOTS];
rs[i].prev = &rs[(i - 1 + PR_RS_SLOTS) % PR_RS_SLOTS].next;
}
res->rs_slot = rs;
}
pr->pr_xtstr = 0;
}
static void
pr_strings_destroy (progs_t *pr, void *_res)
{
__auto_type res = (prstr_resources_t *) _res;
dstring_delete (res->print_str);
Hash_DelTable (res->strref_hash);
pr->pr_string_resources = 0;
}
VISIBLE int
PR_LoadStrings (progs_t *pr)
{
prstr_resources_t *res = PR_Resources_Find (pr, "Strings");
char *end = pr->pr_strings + pr->progs->strings.count;
char *str = pr->pr_strings;
int count = 0;
pr->float_promoted = 0;
while (str < end) {
count++;
if (*str == '@' && pr->progs->version == PROG_V6P_VERSION) {
if (!strcmp (str, "@float_promoted@")) {
pr->float_promoted = 1;
}
}
str += strlen (str) + 1;
}
if (pr->progs->version == PROG_VERSION) {
pr->float_promoted = 1;
}
res->ds_mem.alloc = pr_strings_alloc;
res->ds_mem.free = pr_strings_free;
res->ds_mem.realloc = pr_strings_realloc;
res->ds_mem.data = pr;
Hash_FlushTable (res->strref_hash);
if (res->static_strings)
free (res->static_strings);
res->static_strings = calloc (count, sizeof (strref_t));
count = 0;
str = pr->pr_strings;
while (str < end) {
if (!Hash_Find (res->strref_hash, str)) {
res->static_strings[count].type = str_static;
res->static_strings[count].s.string = str;
Hash_Add (res->strref_hash, &res->static_strings[count]);
count++;
}
str += strlen (str) + 1;
}
res->num_strings = count;
return 1;
}
static void
requeue_strref (prstr_resources_t *res, strref_t *sr)
{
strref_slot_t *rs_slot = sr->rs_slot;
if (rs_slot->next != res->rs_slot) {
// this is the oldest slot, so advance res->rs_slot to the
// next oldest slot so this slot does not get reused just yet
if (res->rs_slot == rs_slot) {
res->rs_slot = rs_slot->next;
}
// unlink this slot from the chain
rs_slot->next->prev = rs_slot->prev;
*rs_slot->prev = rs_slot->next;
// link this slot just before the oldest slot: all the slots
// form a doubly linked circular list
rs_slot->prev = res->rs_slot->prev;
rs_slot->next = res->rs_slot;
*res->rs_slot->prev = rs_slot;
res->rs_slot->prev = &rs_slot->next;
}
}
static inline strref_t *
get_strref (prstr_resources_t *res, pr_string_t num)
{
if (num < 0) {
strref_t *ref;
unsigned row = ~num / 1024;
num = ~num % 1024;
if (row >= res->dyn_str_size)
return 0;
ref = &res->string_map[row][num];
if (ref->type == str_free)
return 0;
return ref;
}
return 0;
}
static inline __attribute__((pure)) const char *
get_string (progs_t *pr, pr_string_t num)
{
__auto_type res = pr->pr_string_resources;
if (num < 0) {
strref_t *ref = get_strref (res, num);
if (!ref)
return 0;
switch (ref->type) {
case str_return:
requeue_strref (res, ref);
case str_static:
case str_temp:
case str_dynamic:
return ref->s.string;
case str_mutable:
return ref->s.dstring->str;
case str_free:
break;
}
PR_Error (pr, "internal string error: line:%d", __LINE__);
} else {
if (num >= pr->pr_stringsize)
return 0;
return pr->pr_strings + num;
}
}
VISIBLE qboolean
PR_StringValid (progs_t *pr, pr_string_t num)
{
if (num >= 0) {
return num < pr->pr_stringsize;
}
return get_strref (pr->pr_string_resources, num) != 0;
}
VISIBLE qboolean
PR_StringMutable (progs_t *pr, pr_string_t num)
{
strref_t *sr;
if (num >= 0) {
return 0;
}
sr = get_strref (pr->pr_string_resources, num);
return sr && sr->type == str_mutable;
}
VISIBLE const char *
PR_GetString (progs_t *pr, pr_string_t num)
{
const char *str;
str = get_string (pr, num);
if (str)
return str;
PR_RunError (pr, "Invalid string offset %d", num);
}
VISIBLE dstring_t *
PR_GetMutableString (progs_t *pr, pr_string_t num)
{
strref_t *ref = get_strref (pr->pr_string_resources, num);
if (ref) {
if (ref->type == str_mutable)
return ref->s.dstring;
PR_RunError (pr, "not a dstring: %d", num);
}
PR_RunError (pr, "Invalid string offset: %d", num);
}
static inline void *
pr_strmalloc (progs_t *pr, size_t size)
{
return PR_Zone_Malloc (pr, size);
}
static inline char *
pr_stralloc (progs_t *pr, size_t len)
{
return pr_strmalloc (pr, len + 1);
}
static inline void
pr_strfree (progs_t *pr, char *s)
{
PR_Zone_Free (pr, s);
}
static inline char *
pr_strdup (progs_t *pr, const char *s)
{
char *new = pr_stralloc (pr, strlen (s));
strcpy (new, s);
return new;
}
VISIBLE pr_string_t
PR_SetString (progs_t *pr, const char *s)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr;
if (!s)
s = "";
sr = Hash_Find (res->strref_hash, s);
if (__builtin_expect (!sr, 1)) {
sr = new_string_ref (res);
sr->type = str_static;
sr->s.string = pr_strdup(pr, s);
Hash_Add (res->strref_hash, sr);
}
return string_index (res, sr);
}
VISIBLE pr_string_t
PR_FindString (progs_t *pr, const char *s)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr;
if (!s)
s = "";
sr = Hash_Find (res->strref_hash, s);
if (sr) {
return string_index (res, sr);
}
return 0;
}
VISIBLE pr_string_t
PR_SetReturnString (progs_t *pr, const char *s)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr;
if (!s)
s = "";
if ((sr = Hash_Find (res->strref_hash, s))) {
if (sr->type == str_return && sr->rs_slot) {
requeue_strref (res, sr);
} else if ((sr->type == str_return && !sr->rs_slot)
|| (sr->type != str_return && sr->rs_slot)) {
PR_Error (pr, "internal string error: line:%d %d %p", __LINE__,
sr->type, sr->rs_slot);
}
return string_index (res, sr);
}
// grab the string ref from the oldest slot, or make a new one if the
// slot is empty
if ((sr = res->rs_slot->strref)) {
if (sr->type != str_return || sr->rs_slot != res->rs_slot) {
PR_Error (pr, "internal string error: line:%d", __LINE__);
}
pr_strfree (pr, sr->s.string);
} else {
sr = new_string_ref (res);
res->rs_slot->strref = sr;
}
sr->type = str_return;
sr->rs_slot = res->rs_slot;
sr->s.string = pr_strdup(pr, s);
// the oldest slot just became the newest, so advance to the next oldest
res->rs_slot = res->rs_slot->next;
return string_index (res, sr);
}
static inline pr_string_t
pr_settempstring (progs_t *pr, prstr_resources_t *res, char *s)
{
strref_t *sr;
sr = new_string_ref (res);
sr->type = str_temp;
sr->s.string = s;
sr->next = pr->pr_xtstr;
pr->pr_xtstr = sr;
return string_index (res, sr);
}
VISIBLE pr_string_t
PR_CatStrings (progs_t *pr, const char *a, const char *b)
{
size_t lena;
char *c;
lena = strlen (a);
c = pr_stralloc (pr, lena + strlen (b));
strcpy (c, a);
strcpy (c + lena, b);
return pr_settempstring (pr, pr->pr_string_resources, c);
}
VISIBLE pr_string_t
PR_SetTempString (progs_t *pr, const char *s)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr;
if (!s)
return PR_SetString (pr, "");
if ((sr = Hash_Find (res->strref_hash, s))) {
return string_index (res, sr);
}
return pr_settempstring (pr, res, pr_strdup (pr, s));
}
VISIBLE pr_string_t
PR_AllocTempBlock (progs_t *pr, size_t size)
{
prstr_resources_t *res = pr->pr_string_resources;
return pr_settempstring (pr, res, pr_strmalloc (pr, size));
}
VISIBLE void
PR_PushTempString (progs_t *pr, pr_string_t num)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *ref = get_strref (res, num);
strref_t **temp_ref;
if (!ref || ref->type != str_temp) {
PR_Error (pr, "attempt to push a non-temp string");
}
for (temp_ref = &pr->pr_xtstr; *temp_ref; temp_ref = &(*temp_ref)->next) {
if (*temp_ref == ref) {
*temp_ref = ref->next;
ref->next = pr->pr_pushtstr;
pr->pr_pushtstr = ref;
return;
}
}
PR_Error (pr, "attempt to push stale temp string");
}
VISIBLE pr_string_t
PR_SetDynamicString (progs_t *pr, const char *s)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr;
if (!s)
return PR_SetString (pr, "");
if ((sr = Hash_Find (res->strref_hash, s))) {
return string_index (res, sr);
}
sr = new_string_ref (res);
sr->type = str_dynamic;
sr->s.string = pr_strdup (pr, s);
return string_index (res, sr);
}
VISIBLE void
PR_MakeTempString (progs_t *pr, pr_string_t str)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr = get_strref (res, str);
if (!sr)
PR_RunError (pr, "invalid string %d", str);
if (sr->type != str_mutable)
PR_RunError (pr, "not a dstring: %d", str);
if (sr->s.dstring->str) {
sr->s.string = dstring_freeze (sr->s.dstring);
} else {
dstring_delete (sr->s.dstring);
}
if (!sr->s.string)
sr->s.string = pr_strdup (pr, "");
sr->type = str_temp;
sr->next = pr->pr_xtstr;
pr->pr_xtstr = sr;
}
VISIBLE pr_string_t
PR_NewMutableString (progs_t *pr)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr = new_string_ref (res);
sr->type = str_mutable;
sr->s.dstring = _dstring_newstr (&res->ds_mem);
return string_index (res, sr);
}
VISIBLE void
PR_HoldString (progs_t *pr, pr_string_t str)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr = get_strref (res, str);
if (sr) {
switch (sr->type) {
case str_temp:
break;
case str_return:
sr->rs_slot->strref = 0;
sr->rs_slot = 0;
break;
case str_static:
case str_mutable:
case str_dynamic:
// non-ephemeral string, no-op
return;
default:
PR_Error (pr, "internal string error: line:%d", __LINE__);
}
sr->type = str_dynamic;
return;
}
if (!PR_StringValid (pr, str)) {
PR_RunError (pr, "attempt to hold invalid string %d", str);
}
}
VISIBLE void
PR_FreeString (progs_t *pr, pr_string_t str)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr = get_strref (res, str);
if (sr) {
switch (sr->type) {
case str_static:
case str_temp:
case str_return:
return;
case str_mutable:
dstring_delete (sr->s.dstring);
break;
case str_dynamic:
pr_strfree (pr, sr->s.string);
break;
default:
PR_Error (pr, "internal string error: line:%d", __LINE__);
}
free_string_ref (res, sr);
return;
}
if (!PR_StringValid (pr, str)) {
PR_RunError (pr, "attempt to free invalid string %d", str);
}
}
VISIBLE void
PR_FreeTempStrings (progs_t *pr)
{
prstr_resources_t *res = pr->pr_string_resources;
strref_t *sr, *t;
for (sr = pr->pr_xtstr; sr; sr = t) {
t = sr->next;
if (sr->type == str_dynamic) {
// the string has been held, so simply remove the ref from the
// queue
continue;
}
if (sr->type != str_temp)
PR_Error (pr, "internal string error: line:%d", __LINE__);
if (R_STRING (pr) < 0 && string_index (res, sr) == R_STRING (pr)
&& pr->pr_depth) {
// It looks like the temp string is being returned. While this
// may be a false positive (just a random integer with the same
// value), it is better to hold onto the temp string a little
// longer than to remove it prematurely. This allows functions
// to return the result of "str a" + "str b"
prstack_t *frame = pr->pr_stack + pr->pr_depth - 1;
sr->next = frame->tstr;
frame->tstr = sr;
} else {
pr_strfree (pr, sr->s.string);
free_string_ref (res, sr);
}
}
pr->pr_xtstr = 0;
}
#define hasprintf ((char *(*)(dstring_t *, const char *, ...))dasprintf)
#define PRINT(t) \
switch ((doWidth << 1) | doPrecision) { \
case 3: \
hasprintf (result, tmp->str, current->minFieldWidth, \
current->precision, current->data.t##_var); \
break; \
case 2: \
hasprintf (result, tmp->str, current->minFieldWidth, \
current->data.t##_var); \
break; \
case 1: \
hasprintf (result, tmp->str, current->precision, \
current->data.t##_var); \
break; \
case 0: \
hasprintf (result, tmp->str, current->data.t##_var); \
break; \
}
/*
This function takes as input a linked list of fmt_item_t representing
EVERYTHING to be printed. This includes text that is not affected by
formatting. A string without any formatting would wind up with only one
list item.
*/
static void
I_DoPrint (dstring_t *tmp, dstring_t *result, fmt_item_t *formatting)
{
fmt_item_t *current = formatting;
while (current) {
qboolean doPrecision, doWidth;
doPrecision = -1 != current->precision;
doWidth = 0 != (current->flags & FMT_WIDTH);
dsprintf (tmp, "%%%s%s%s%s%s%s%s",
(current->flags & FMT_ALTFORM) ? "#" : "", // hash
(current->flags & FMT_ZEROPAD) ? "0" : "", // zero padding
(current->flags & FMT_LJUSTIFY) ? "-" : "", // left justify
(current->flags & FMT_ADDBLANK) ? " " : "", // add space for +ve
(current->flags & FMT_ADDSIGN) ? "+" : "", // add sign always
doWidth ? "*" : "",
doPrecision ? ".*" : "");
switch (current->type) {
case 's':
dstring_appendstr (tmp, "s");
PRINT (string);
break;
case 'c':
dstring_appendstr (tmp, "c");
PRINT (int);
break;
case 'i':
case 'd':
if (current->flags & FMT_LONG) {
dstring_appendstr (tmp, PRId64);
PRINT (ulong);
} else {
dstring_appendstr (tmp, PRId32);
PRINT (uint);
}
break;
case 'x':
if (current->flags & FMT_LONG) {
dstring_appendstr (tmp, PRIx64);
PRINT (ulong);
} else {
dstring_appendstr (tmp, PRIx32);
PRINT (uint);
}
break;
case 'u':
if (current->flags & FMT_LONG) {
dstring_appendstr (tmp, PRIu64);
PRINT (ulong);
} else {
dstring_appendstr (tmp, PRIu32);
PRINT (uint);
}
break;
case 'f':
dstring_appendstr (tmp, "f");
if (current->flags & FMT_LONG) {
PRINT (double);
} else {
PRINT (float);
}
break;
case 'g':
dstring_appendstr (tmp, "g");
if (current->flags & FMT_LONG) {
PRINT (double);
} else {
PRINT (float);
}
break;
default:
break;
}
current = current->next;
}
}
static fmt_item_t *
new_fmt_item (prstr_resources_t *res)
{
int i;
fmt_item_t *fi;
if (!res->free_fmt_items) {
res->free_fmt_items = malloc (16 * sizeof (fmt_item_t));
for (i = 0; i < 15; i++)
res->free_fmt_items[i].next = res->free_fmt_items + i + 1;
res->free_fmt_items[i].next = 0;
}
fi = res->free_fmt_items;
res->free_fmt_items = fi->next;
memset (fi, 0, sizeof (*fi));
fi->precision = -1;
return fi;
}
static void
free_fmt_item (prstr_resources_t *res, fmt_item_t *fi)
{
fi->next = res->free_fmt_items;
res->free_fmt_items = fi;
}
struct fmt_state_s;
typedef void (*fmt_state_f) (struct fmt_state_s *);
typedef struct fmt_state_s {
fmt_state_f state;
prstr_resources_t *res;
progs_t *pr;
pr_type_t **args;
const char *c;
const char *msg;
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
fmt_append_item (fmt_state_t *state)
{
(*state->fi)->next = new_fmt_item (state->res);
state->fi = &(*state->fi)->next;
}
#undef P_var
#define P_var(p,n,t) PR_PTR (t, state->args[n])
/** State machine for PR_Sprintf
*
* Parsing of the format string is implemented via the following state
* machine. Note that in all states, end-of-string terminates the machine.
* If the machine terminates in any state other than format or conversion,
* an error is generated.
* \dot
* digraph PR_Sprintf_fmt_state_machine {
* format -> flags [label="{%}"];
* flogs -> format [label="{%}"];
* flags -> flags [label="{#+0 -}"];
* flags -> var_field_width [label="{*}"];
* flags -> precision [label="{.}"];
* flags -> field_width [label="{[1-9]}"];
* flags -> modifiers [label="other"];
* var_field_width -> precision [label="{.}"];
* var_field_width -> modifiers [label="other"];
* field_width -> field_width [label="{[0-9]}"];
* field_width -> precision [label="{.}"];
* field_width -> modifiers [label="other"];
* precision -> var_precision [label="{*}"];
* precision -> fixed_precision [label="{[0-9]}"];
* precision -> modifiers [label="other"];
* var_precision -> modifiers [label="instant"];
* fixed_precision -> fixed_precision [label="{[0-9]}"];
* fixed_precision -> modifiers [label="other"];
* modifiers -> conversion [label="instant/other"];
* conversion -> format [label="other"];
* }
* \enddot
*/
///@{
static void fmt_state_format (fmt_state_t *state);
static void fmt_state_flags (fmt_state_t *state);
static void fmt_state_var_field_width (fmt_state_t *state);
static void fmt_state_field_width (fmt_state_t *state);
static void fmt_state_precision (fmt_state_t *state);
static void fmt_state_var_precision (fmt_state_t *state);
static void fmt_state_fixed_precision (fmt_state_t *state);
static void fmt_state_modifiers (fmt_state_t *state);
static void fmt_state_conversion (fmt_state_t *state);
static void
fmt_state_flags (fmt_state_t *state)
{
state->c++; // skip over %
while (1) {
switch (*state->c) {
case '%':
state->c++;
(*state->fi)->flags = 0;
(*state->fi)->precision = 1;
(*state->fi)->minFieldWidth = 0;
(*state->fi)->type = 's';
(*state->fi)->data.string_var = "%";
fmt_append_item (state);
state->state = fmt_state_format;
return;
case '0':
(*state->fi)->flags |= FMT_ZEROPAD;
break;
case '#':
(*state->fi)->flags |= FMT_ALTFORM;
break;
case ' ':
(*state->fi)->flags |= FMT_ADDBLANK;
break;
case '-':
(*state->fi)->flags |= FMT_LJUSTIFY;
break;
case '+':
(*state->fi)->flags |= FMT_ADDSIGN;
break;
case '*':
state->state = fmt_state_var_field_width;
return;
case '.':
state->state = fmt_state_precision;
return;
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
state->state = fmt_state_field_width;
return;
default:
state->state = fmt_state_modifiers;
return;
}
state->c++;
}
}
static void
fmt_state_var_field_width (fmt_state_t *state)
{
(*state->fi)->flags |= FMT_WIDTH;
(*state->fi)->minFieldWidth = P_INT (pr, state->fmt_count);
state->fmt_count++;
if (*++state->c == '.') {
state->state = fmt_state_precision;
} else {
state->state = fmt_state_modifiers;
}
}
static void
fmt_state_field_width (fmt_state_t *state)
{
(*state->fi)->flags |= FMT_WIDTH;
while (isdigit ((byte )*state->c)) {
(*state->fi)->minFieldWidth *= 10;
(*state->fi)->minFieldWidth += *state->c++ - '0';
}
if (*state->c == '.') {
state->state = fmt_state_precision;
} else {
state->state = fmt_state_modifiers;
}
}
static void
fmt_state_precision (fmt_state_t *state)
{
state->c++; // skip over .
(*state->fi)->precision = 0;
if (isdigit ((byte )*state->c)) {
state->state = fmt_state_fixed_precision;
} else if (*state->c == '*') {
state->state = fmt_state_var_precision;
} else {
state->state = fmt_state_modifiers;
}
}
static void
fmt_state_var_precision (fmt_state_t *state)
{
state->c++; // skip over *
(*state->fi)->precision = P_INT (pr, state->fmt_count);
state->fmt_count++;
state->state = fmt_state_modifiers;
}
static void
fmt_state_fixed_precision (fmt_state_t *state)
{
while (isdigit ((byte )*state->c)) {
(*state->fi)->precision *= 10;
(*state->fi)->precision += *state->c++ - '0';
}
state->state = fmt_state_modifiers;
}
static void
fmt_state_modifiers (fmt_state_t *state)
{
// no modifiers supported
if (state->c[0] == 'l'
&& (state->c[1] == 'i' || state->c[1] == 'd' || state->c[1] == 'x'
|| state->c[1] == 'u')) {
(*state->fi)->flags |= FMT_LONG;
state->c++;
}
state->state = fmt_state_conversion;
}
static void
fmt_state_conversion (fmt_state_t *state)
{
progs_t *pr = state->pr;
char conv;
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;
case 'e':
// entity
(*state->fi)->type = 'i';
(*state->fi)->data.int_var = P_EDICTNUM (pr, state->fmt_count);
state->fmt_count++;
fmt_append_item (state);
break;
case 'i':
case 'd':
case 'c':
// int
(*state->fi)->type = conv;
if ((*state->fi)->flags & FMT_LONG) {
(*state->fi)->data.long_var = P_LONG (pr, state->fmt_count);
} else {
(*state->fi)->data.int_var = P_INT (pr, state->fmt_count);
}
state->fmt_count++;
fmt_append_item (state);
break;
case 'f':
// float or double
case 'g':
// float or double, no trailing zeroes, trim "."
// if nothing after
(*state->fi)->type = conv;
if (pr->float_promoted) {
(*state->fi)->flags |= FMT_LONG;
(*state->fi)->data.double_var
= P_DOUBLE (pr, state->fmt_count);
} else {
(*state->fi)->data.float_var
= P_FLOAT (pr, state->fmt_count);
}
state->fmt_count++;
fmt_append_item (state);
break;
case 'p':
// pointer
(*state->fi)->flags |= FMT_ALTFORM;
(*state->fi)->type = 'x';
(*state->fi)->data.uint_var = P_UINT (pr, state->fmt_count);
state->fmt_count++;
fmt_append_item (state);
break;
case 's':
// string
(*state->fi)->type = conv;
(*state->fi)->data.string_var = P_GSTRING (pr, state->fmt_count);
state->fmt_count++;
fmt_append_item (state);
break;
case 'v':
case 'q':
// vector
{
int i, count = 3;
int flags = (*state->fi)->flags;
int precision = (*state->fi)->precision;
unsigned minWidth = (*state->fi)->minFieldWidth;
(*state->fi)->flags = 0;
(*state->fi)->precision = -1;
(*state->fi)->minFieldWidth = 0;
if (conv == 'q')
count = 4;
for (i = 0; i < count; i++) {
if (i == 0) {
(*state->fi)->type = 's';
(*state->fi)->data.string_var = "'";
} else {
(*state->fi)->type = 's';
(*state->fi)->data.string_var = " ";
}
fmt_append_item (state);
(*state->fi)->flags = flags;
(*state->fi)->precision = precision;
(*state->fi)->minFieldWidth = minWidth;
(*state->fi)->type = 'g';
(*state->fi)->data.float_var =
P_VECTOR (pr, state->fmt_count)[i];
fmt_append_item (state);
}
}
(*state->fi)->type = 's';
(*state->fi)->data.string_var = "'";
state->fmt_count++;
fmt_append_item (state);
break;
case 'u':
case 'x':
// int, unsigned or hex notation
(*state->fi)->type = conv;
if ((*state->fi)->flags & FMT_LONG) {
(*state->fi)->data.ulong_var = P_ULONG (pr, state->fmt_count);
} else {
(*state->fi)->data.uint_var = P_UINT (pr, state->fmt_count);
}
state->fmt_count++;
fmt_append_item (state);
break;
}
if (*state->c) {
state->state = fmt_state_format;
} else {
state->state = 0; // finished
}
}
static void
fmt_state_format (fmt_state_t *state)
{
const char *l = state->c;
while (*state->c && *state->c != '%') {
state->c++;
}
if (state->c != l) {
// have some unformatted text to print
(*state->fi)->precision = state->c - l;
(*state->fi)->type = 's';
(*state->fi)->data.string_var = l;
if (*state->c) {
fmt_append_item (state);
}
}
if (*state->c) {
state->state = fmt_state_flags;
} else {
state->state = 0; // finished
}
}
///@}
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)
{
prstr_resources_t *res = pr->pr_string_resources;
fmt_state_t state = { };
state.pr = pr;
state.res = res;
state.args = args;
state.fi = &state.fmt_items;
*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";
while (*state.c && state.state) {
state.state (&state);
}
if (state.state) {
state.msg = "Unexpected end of format string";
} else if (state.fmt_count != count) {
if (state.fmt_count > count)
state.msg = "Not enough arguments for format string.";
else
state.msg = "Too many arguments for format string.";
}
if (state.msg) {
dsprintf (res->print_str, "%s: %d %d", state.msg, state.fmt_count,
count);
state.msg = res->print_str->str;
goto error;
}
dstring_clear (res->print_str);
I_DoPrint (res->print_str, result, state.fmt_items);
while (state.fmt_items) {
fmt_item_t *t = state.fmt_items->next;
free_fmt_item (res, state.fmt_items);
state.fmt_items = t;
}
return;
error:
PR_RunError (pr, "%s: %s", name, state.msg);
}
void
PR_Strings_Init (progs_t *pr)
{
prstr_resources_t *res = calloc (1, sizeof (*res));
res->pr = pr;
res->print_str = dstring_new ();
res->strref_hash = Hash_NewTable (1021, strref_get_key, strref_free,
res, pr->hashctx);
PR_Resources_Register (pr, "Strings", res, pr_strings_clear,
pr_strings_destroy);
pr->pr_string_resources = res;
}