[gamecode] Move format state into a state struct

This is the first step in reworking PR_Sprintf to use a state machine.
The goal is to make it more robust against errors and easier to extend
(eg, * width and precision).
This commit is contained in:
Bill Currie 2020-04-01 11:19:56 +09:00
parent e9733416f4
commit a7956c7311

View file

@ -808,6 +808,14 @@ free_fmt_item (prstr_resources_t *res, fmt_item_t *fi)
res->free_fmt_items = fi; res->free_fmt_items = fi;
} }
typedef struct fmtstate_s {
const char *c;
const char *l;
fmt_item_t *fmt_items;
fmt_item_t **fi;
int fmt_count;
} fmt_state_t;
#undef P_var #undef P_var
#define P_var(p,n,t) (args[n]->t##_var) #define P_var(p,n,t) (args[n]->t##_var)
#undef P_DOUBLE #undef P_DOUBLE
@ -817,229 +825,228 @@ PR_Sprintf (progs_t *pr, dstring_t *result, const char *name,
const char *format, int count, pr_type_t **args) const char *format, int count, pr_type_t **args)
{ {
prstr_resources_t *res = pr->pr_string_resources; prstr_resources_t *res = pr->pr_string_resources;
const char *c, *l;
const char *msg = ""; const char *msg = "";
fmt_item_t *fmt_items = 0; fmt_state_t state = { };
fmt_item_t **fi = &fmt_items;
int fmt_count = 0; state.fi = &state.fmt_items;
if (!name) if (!name)
name = "PR_Sprintf"; name = "PR_Sprintf";
*fi = new_fmt_item (res); *state.fi = new_fmt_item (res);
c = l = format; state.c = state.l = format;
while (*c) { while (*state.c) {
if (*c++ == '%') { if (*state.c++ == '%') {
if (c != l + 1) { if (state.c != state.l + 1) {
// have some unformatted text to print // have some unformatted text to print
(*fi)->precision = c - l - 1; (*state.fi)->precision = state.c - state.l - 1;
(*fi)->type = 's'; (*state.fi)->type = 's';
(*fi)->data.string_var = l; (*state.fi)->data.string_var = state.l;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
} }
if (*c == '%') { if (*state.c == '%') {
(*fi)->type = 's'; (*state.fi)->type = 's';
(*fi)->data.string_var = "%"; (*state.fi)->data.string_var = "%";
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
} else { } else {
do { do {
switch (*c) { switch (*state.c) {
// format options // format options
case '\0': case '\0':
msg = "Unexpected end of format string"; msg = "Unexpected end of format string";
goto error; goto error;
case '0': case '0':
(*fi)->flags |= FMT_ZEROPAD; (*state.fi)->flags |= FMT_ZEROPAD;
c++; state.c++;
continue; continue;
case '#': case '#':
(*fi)->flags |= FMT_ALTFORM; (*state.fi)->flags |= FMT_ALTFORM;
c++; state.c++;
continue; continue;
case ' ': case ' ':
(*fi)->flags |= FMT_ADDBLANK; (*state.fi)->flags |= FMT_ADDBLANK;
c++; state.c++;
continue; continue;
case '-': case '-':
(*fi)->flags |= FMT_LJUSTIFY; (*state.fi)->flags |= FMT_LJUSTIFY;
c++; state.c++;
continue; continue;
case '+': case '+':
(*fi)->flags |= FMT_ADDSIGN; (*state.fi)->flags |= FMT_ADDSIGN;
c++; state.c++;
continue; continue;
case '.': case '.':
(*fi)->precision = 0; (*state.fi)->precision = 0;
c++; state.c++;
while (isdigit ((byte )*c)) { while (isdigit ((byte )*state.c)) {
(*fi)->precision *= 10; (*state.fi)->precision *= 10;
(*fi)->precision += *c++ - '0'; (*state.fi)->precision += *state.c++ - '0';
} }
continue; continue;
case '1': case '2': case '3': case '4': case '5': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': case '6': case '7': case '8': case '9':
while (isdigit ((byte )*c)) { while (isdigit ((byte )*state.c)) {
(*fi)->minFieldWidth *= 10; (*state.fi)->minFieldWidth *= 10;
(*fi)->minFieldWidth += *c++ - '0'; (*state.fi)->minFieldWidth += *state.c++ - '0';
} }
continue; continue;
// format types // format types
case '@': case '@':
// object // object
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 'e': case 'e':
// entity // entity
(*fi)->type = 'i'; (*state.fi)->type = 'i';
(*fi)->data.integer_var = (*state.fi)->data.integer_var =
P_EDICTNUM (pr, fmt_count); P_EDICTNUM (pr, state.fmt_count);
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 'i': case 'i':
case 'd': case 'd':
case 'c': case 'c':
// integer // integer
(*fi)->type = *c; (*state.fi)->type = *state.c;
(*fi)->data.integer_var = P_INT (pr, fmt_count); (*state.fi)->data.integer_var = P_INT (pr, state.fmt_count);
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 'f': case 'f':
// float or double // float or double
case 'g': case 'g':
// float or double, no trailing zeroes, trim "." // float or double, no trailing zeroes, trim "."
// if nothing after // if nothing after
(*fi)->type = *c; (*state.fi)->type = *state.c;
if (pr->float_promoted) { if (pr->float_promoted) {
(*fi)->flags |= FMT_LONG; (*state.fi)->flags |= FMT_LONG;
(*fi)->data.double_var (*state.fi)->data.double_var
= P_DOUBLE (pr, fmt_count); = P_DOUBLE (pr, state.fmt_count);
} else { } else {
(*fi)->data.float_var (*state.fi)->data.float_var
= P_FLOAT (pr, fmt_count); = P_FLOAT (pr, state.fmt_count);
} }
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 'p': case 'p':
// pointer // pointer
(*fi)->flags |= FMT_ALTFORM; (*state.fi)->flags |= FMT_ALTFORM;
(*fi)->type = 'x'; (*state.fi)->type = 'x';
(*fi)->data.uinteger_var = P_UINT (pr, fmt_count); (*state.fi)->data.uinteger_var = P_UINT (pr, state.fmt_count);
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 's': case 's':
// string // string
(*fi)->type = *c; (*state.fi)->type = *state.c;
(*fi)->data.string_var = P_GSTRING (pr, fmt_count); (*state.fi)->data.string_var = P_GSTRING (pr, state.fmt_count);
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 'v': case 'v':
case 'q': case 'q':
// vector // vector
{ {
int i, count = 3; int i, count = 3;
int flags = (*fi)->flags; int flags = (*state.fi)->flags;
int precision = (*fi)->precision; int precision = (*state.fi)->precision;
unsigned minWidth = (*fi)->minFieldWidth; unsigned minWidth = (*state.fi)->minFieldWidth;
(*fi)->flags = 0; (*state.fi)->flags = 0;
(*fi)->precision = -1; (*state.fi)->precision = -1;
(*fi)->minFieldWidth = 0; (*state.fi)->minFieldWidth = 0;
if (*c == 'q') if (*state.c == 'q')
count = 4; count = 4;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
if (i == 0) { if (i == 0) {
(*fi)->type = 's'; (*state.fi)->type = 's';
(*fi)->data.string_var = "'"; (*state.fi)->data.string_var = "'";
} else { } else {
(*fi)->type = 's'; (*state.fi)->type = 's';
(*fi)->data.string_var = " "; (*state.fi)->data.string_var = " ";
} }
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
(*fi)->flags = flags; (*state.fi)->flags = flags;
(*fi)->precision = precision; (*state.fi)->precision = precision;
(*fi)->minFieldWidth = minWidth; (*state.fi)->minFieldWidth = minWidth;
(*fi)->type = 'g'; (*state.fi)->type = 'g';
(*fi)->data.float_var = (*state.fi)->data.float_var =
P_VECTOR (pr, fmt_count)[i]; P_VECTOR (pr, state.fmt_count)[i];
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
} }
} }
(*fi)->type = 's'; (*state.fi)->type = 's';
(*fi)->data.string_var = "'"; (*state.fi)->data.string_var = "'";
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
case 'x': case 'x':
// integer, hex notation // integer, hex notation
(*fi)->type = *c; (*state.fi)->type = *state.c;
(*fi)->data.uinteger_var = P_UINT (pr, fmt_count); (*state.fi)->data.uinteger_var = P_UINT (pr, state.fmt_count);
fmt_count++; state.fmt_count++;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
fi = &(*fi)->next; state.fi = &(*state.fi)->next;
break; break;
} }
break; break;
} while (1); } while (1);
} }
l = ++c; state.l = ++state.c;
} }
} }
if (c != l) { if (state.c != state.l) {
// have some unformatted text to print // have some unformatted text to print
(*fi)->precision = c - l; (*state.fi)->precision = state.c - state.l;
(*fi)->type = 's'; (*state.fi)->type = 's';
(*fi)->data.string_var = l; (*state.fi)->data.string_var = state.l;
(*fi)->next = new_fmt_item (res); (*state.fi)->next = new_fmt_item (res);
} }
if (fmt_count != count) { if (state.fmt_count != count) {
if (fmt_count > count) if (state.fmt_count > count)
msg = "Not enough arguments for format string."; msg = "Not enough arguments for format string.";
else else
msg = "Too many arguments for format string."; msg = "Too many arguments for format string.";
dsprintf (res->print_str, "%s: %d %d", msg, fmt_count, count); dsprintf (res->print_str, "%s: %d %d", msg, state.fmt_count, count);
msg = res->print_str->str; msg = res->print_str->str;
goto error; goto error;
} }
dstring_clear (res->print_str); dstring_clear (res->print_str);
I_DoPrint (res->print_str, result, fmt_items); I_DoPrint (res->print_str, result, state.fmt_items);
while (fmt_items) { while (state.fmt_items) {
fmt_item_t *t = fmt_items->next; fmt_item_t *t = state.fmt_items->next;
free_fmt_item (res, fmt_items); free_fmt_item (res, state.fmt_items);
fmt_items = t; state.fmt_items = t;
} }
return; return;
error: error: