1
0
mirror of https://git.zap.org.au/git/trader.git synced 2024-12-04 14:46:45 -05:00

Do parse of format string in prepstr_parse(); add the "%c" specifier

This commit is contained in:
John Zaitseff 2011-08-15 15:53:56 +10:00
parent 746155078b
commit 3ad62688da
2 changed files with 254 additions and 228 deletions

View File

@ -48,6 +48,7 @@ typedef struct txwin {
enum argument_type {
TYPE_NONE, // No type yet assigned
TYPE_CHAR, // char
TYPE_INT, // int
TYPE_LONGINT, // long int
TYPE_DOUBLE, // double
@ -57,6 +58,7 @@ enum argument_type {
struct argument {
enum argument_type a_type;
union a {
char a_char;
int a_int;
long int a_longint;
double a_double;
@ -65,6 +67,18 @@ struct argument {
};
#define MAXFMTSPECS 16 // Maximum number of conversion specifiers
struct convspec {
int len; // Length of conversion specifier, 0 = unused
int arg_num; // Which variable argument to use
char spec; // Conversion specifier: c d N s
bool flag_group; // Flag "'" (thousands grouping)
bool flag_nosym; // Flag "!" (omit currency symbol)
bool flag_long; // Length modifier "l" (long)
};
/************************************************************************
* Global variable definitions *
************************************************************************/
@ -189,6 +203,23 @@ static int prepstr_addch (chtype *restrict *restrict chbuf,
const char *restrict *restrict str);
/*
Function: prepstr_parse - Parse the format string for prepstr()
Parameters: format - Format string as described for prepstr()
format_arg - Pointer to variable arguments array
format_spec - Pointer to conversion specifiers array
args - Variable argument list passed to prepstr()
Returns: int - 0 if OK, -1 if error (with errno set)
This helper function parses the format string passed to prepstr(),
setting the format_arg and format_spec arrays appropriately.
*/
static int prepstr_parse (const char *restrict format,
struct argument *restrict format_arg,
struct convspec *restrict format_spec,
va_list args);
/*
Function: txinput_fixup - Copy strings with fixup
Parameters: dest - Destination buffer of size BUFSIZE
@ -716,48 +747,29 @@ int prepstr_addch (chtype *restrict *restrict chbuf, int *restrict chbufsize,
/***********************************************************************/
// vprepstr: Prepare a string for printing to screen
// prepstr_parse: Parse the format string for prepstr()
int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth,
int *restrict widthbuf, int widthbufsize,
const char *restrict format, va_list args)
int prepstr_parse (const char *restrict format,
struct argument *restrict format_arg,
struct convspec *restrict format_spec, va_list args)
{
struct argument format_arg[MAXFMTARGS];
int num_format_args, arg_num;
const char *orig_format;
int line, width;
chtype *lastspc;
int widthspc;
chtype curattr;
int saved_errno;
int num_args = 0; // 0 .. MAXFMTARGS
int arg_num = 0; // Current index into format_arg[]
int specs_left = MAXFMTSPECS; // MAXFMTSPECS .. 0 (counting down)
assert(chbuf != NULL);
assert(chbufsize > 0);
assert(maxlines > 0);
assert(maxwidth > 0);
assert(widthbuf != NULL);
assert(widthbufsize >= maxlines);
assert(format != NULL);
/* Do a preliminary scan through the format parameter to determine
the types of each positional argument (conversion specifier). If
we did not support "%m$"-style specifiers, this would not be
necessary. */
orig_format = format;
memset(format_arg, 0, sizeof(format_arg));
num_format_args = 0;
arg_num = 0;
memset(format_arg, 0, MAXFMTARGS * sizeof(format_arg[0]));
memset(format_spec, 0, MAXFMTSPECS * sizeof(format_spec[0]));
while (*format != '\0') {
switch (*format++) {
case '^':
// Switch to a different character rendition
if (*format == '\0') {
goto error_inval;
errno = EINVAL;
return -1;
} else {
// Ignore next character for now
format++;
}
break;
@ -765,14 +777,17 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
case '%':
// Process a conversion specifier
if (*format == '\0') {
goto error_inval;
errno = EINVAL;
return -1;
} else if (*format == '%') {
// Ignore "%%" specifier
format++;
} else {
enum argument_type arg_type = TYPE_NONE;
const char *start = format;
enum argument_type arg_type;
bool inspec = true;
bool flag_posn = false;
bool flag_long = false;
bool flag_posn = false; // Have we already seen "$"?
bool flag_other = false; // Have we seen something else?
int count = 0;
while (inspec && *format != '\0') {
@ -780,8 +795,11 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
switch (c) {
case '0':
// Zero flag, or part of numeric count
if (count == 0)
goto error_inval;
if (count == 0) {
// Zero flag is NOT supported
errno = EINVAL;
return -1;
}
count *= 10;
break;
@ -801,12 +819,14 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
case '$':
// Fixed-position argument
if (flag_posn || count == 0)
goto error_inval;
if (flag_posn || flag_other || count == 0) {
errno = EINVAL;
return -1;
}
if (count > MAXFMTARGS) {
errno = E2BIG;
goto error;
return -1;
}
flag_posn = true;
@ -815,61 +835,116 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
break;
case '\'':
// Use locale-specific thousands separator
// Use locale-specific thousands group separator
if (format_spec->flag_group) {
errno = EINVAL;
return -1;
}
format_spec->flag_group = true;
flag_other = true;
break;
case '!':
// Omit the locale-specific currency symbol
if (format_spec->flag_nosym) {
errno = EINVAL;
return -1;
}
format_spec->flag_nosym = true;
flag_other = true;
break;
case 'l':
// Long length modifier
if (flag_long)
goto error_inval;
if (format_spec->flag_long) {
// "ll" is NOT supported
errno = EINVAL;
return -1;
}
flag_long = true;
format_spec->flag_long = true;
flag_other = true;
break;
case 'c':
// Insert a character (char)
if (format_spec->flag_group || format_spec->flag_nosym
|| format_spec->flag_long || count != 0) {
errno = EINVAL;
return -1;
}
arg_type = TYPE_CHAR;
goto handlefmt;
case 'd':
// Insert an integer (int or long int)
arg_type = flag_long ? TYPE_LONGINT : TYPE_INT;
if (count != 0) {
errno = EINVAL;
return -1;
}
arg_type = format_spec->flag_long ?
TYPE_LONGINT : TYPE_INT;
goto handlefmt;
case 'N':
// Insert a monetary amount (double)
if (flag_long)
goto error_inval;
if (format_spec->flag_group || format_spec->flag_long
|| count != 0) {
errno = EINVAL;
return -1;
}
arg_type = TYPE_DOUBLE;
goto handlefmt;
case 's':
// Insert a string (const char *)
if (flag_long)
goto error_inval;
if (format_spec->flag_group || format_spec->flag_nosym
|| format_spec->flag_long || count != 0) {
errno = EINVAL;
return -1;
}
arg_type = TYPE_STRING;
handlefmt:
if (arg_num >= MAXFMTARGS) {
if (arg_num >= MAXFMTARGS || specs_left == 0) {
errno = E2BIG;
goto error;
return -1;
}
if (format_arg[arg_num].a_type == TYPE_NONE) {
format_arg[arg_num].a_type = arg_type;
} else if (format_arg[arg_num].a_type != arg_type) {
goto error_inval;
errno = EINVAL;
return -1;
}
format_spec->len = format - start;
format_spec->arg_num = arg_num;
format_spec->spec = c;
arg_num++;
num_format_args = MAX(num_format_args, arg_num);
num_args = MAX(num_args, arg_num);
format_spec++;
specs_left--;
inspec = false;
break;
default:
goto error_inval;
errno = EINVAL;
return -1;
}
}
if (inspec) {
goto error_inval;
errno = EINVAL;
return -1;
}
}
break;
@ -880,36 +955,73 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
}
}
for (int i = 0; i < num_format_args; i++) {
switch (format_arg[i].a_type) {
for (int i = 0; i < num_args; format_arg++, i++) {
switch (format_arg->a_type) {
case TYPE_CHAR:
format_arg->a.a_char = (char) va_arg(args, int);
break;
case TYPE_INT:
format_arg[i].a.a_int = va_arg(args, int);
format_arg->a.a_int = va_arg(args, int);
break;
case TYPE_LONGINT:
format_arg[i].a.a_longint = va_arg(args, long int);
format_arg->a.a_longint = va_arg(args, long int);
break;
case TYPE_DOUBLE:
format_arg[i].a.a_double = va_arg(args, double);
format_arg->a.a_double = va_arg(args, double);
break;
case TYPE_STRING:
format_arg[i].a.a_string = va_arg(args, const char *);
format_arg->a.a_string = va_arg(args, const char *);
break;
default:
/* Cannot allow unused arguments, as we have no way of
knowing how much space they take (cf. int vs. long long
int). */
goto error_inval;
errno = EINVAL;
return -1;
}
}
// Actually process the format parameter string
return 0;
}
format = orig_format;
arg_num = 0;
/***********************************************************************/
// vprepstr: Prepare a string for printing to screen
int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth,
int *restrict widthbuf, int widthbufsize,
const char *restrict format, va_list args)
{
const char *orig_format = format;
struct argument format_arg[MAXFMTARGS];
struct convspec format_spec[MAXFMTSPECS];
struct convspec *spec;
int line, width;
chtype *lastspc;
int widthspc;
chtype curattr;
int saved_errno;
assert(chbuf != NULL);
assert(chbufsize > 0);
assert(maxlines > 0);
assert(maxwidth > 0);
assert(widthbuf != NULL);
assert(widthbufsize >= maxlines);
assert(format != NULL);
if (prepstr_parse(format, format_arg, format_spec, args) < 0) {
goto error;
}
spec = format_spec;
curattr = attr_norm;
line = -1; // Current line number (0 = first)
@ -967,104 +1079,40 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
goto error;
}
} else {
bool inspec = true;
bool flag_posn = false;
bool flag_long = false;
bool flag_thou = false;
int count = 0;
const char *str;
assert(spec->len != 0);
const char *str;
char *buf = xmalloc(BUFSIZE);
while (inspec && *format != '\0') {
char c = *format++;
switch (c) {
case '0':
// Zero flag, or part of numeric count
if (count == 0) {
// Zero flag is not supported
switch (spec->spec) {
case 'c':
// Insert a character (char) into the output
if (snprintf(buf, BUFSIZE, "%c",
format_arg[spec->arg_num].a.a_char) < 0) {
saved_errno = errno;
free(buf);
goto error_inval;
}
count *= 10;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// Part of some numeric count
count = count * 10 + (c - '0');
break;
case '$':
// Fixed-position argument
if (flag_posn || count == 0) {
free(buf);
goto error_inval;
}
if (count > MAXFMTARGS) {
free(buf);
errno = E2BIG;
errno = saved_errno;
goto error;
}
flag_posn = true;
arg_num = count - 1;
count = 0;
break;
case '\'':
// Use locale-specific thousands separator
if (flag_thou) {
free(buf);
goto error_inval;
}
flag_thou = true;
break;
case 'l':
// Long length modifier
if (flag_long) {
free(buf);
goto error_inval;
}
flag_long = true;
break;
str = buf;
goto insertstr;
case 'd':
// Insert an integer (int or long int) into the output
if (count != 0) {
free(buf);
goto error_inval;
}
if (arg_num >= MAXFMTARGS) {
free(buf);
errno = E2BIG;
goto error;
}
if (flag_long) {
if (snprintf(buf, BUFSIZE, flag_thou ? "%'ld" : "%ld",
format_arg[arg_num].a.a_longint) < 0) {
if (spec->flag_long) {
if (snprintf(buf, BUFSIZE, spec->flag_group ?
"%'ld" : "%ld",
format_arg[spec->arg_num].a.a_longint) < 0) {
saved_errno = errno;
free(buf);
errno = saved_errno;
goto error;
}
} else {
if (snprintf(buf, BUFSIZE, flag_thou ? "%'d" : "%d",
format_arg[arg_num].a.a_int) < 0) {
if (snprintf(buf, BUFSIZE, spec->flag_group ?
"%'d" : "%d",
format_arg[spec->arg_num].a.a_int) < 0) {
saved_errno = errno;
free(buf);
errno = saved_errno;
@ -1077,19 +1125,8 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
case 'N':
// Insert a monetary amount (double) into the output
if (count != 0 || flag_thou || flag_long) {
free(buf);
goto error_inval;
}
if (arg_num >= MAXFMTARGS) {
free(buf);
errno = E2BIG;
goto error;
}
if (l_strfmon(buf, BUFSIZE, "%n",
format_arg[arg_num].a.a_double) < 0) {
if (l_strfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n",
format_arg[spec->arg_num].a.a_double) < 0) {
saved_errno = errno;
free(buf);
errno = saved_errno;
@ -1101,18 +1138,7 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
case 's':
// Insert a string (const char *) into the output
if (count != 0 || flag_thou || flag_long) {
free(buf);
goto error_inval;
}
if (arg_num >= MAXFMTARGS) {
free(buf);
errno = E2BIG;
goto error;
}
str = format_arg[arg_num].a.a_string;
str = format_arg[spec->arg_num].a.a_string;
if (str == NULL) {
str = "(null)"; // As per GNU printf()
@ -1132,19 +1158,15 @@ int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
}
}
arg_num++;
inspec = false;
format += spec->len;
spec++;
break;
default:
assert(spec->spec);
}
free(buf);
goto error_inval;
}
}
free(buf);
if (inspec) {
goto error_inval;
}
}
break;

View File

@ -314,13 +314,17 @@ extern int txdlgbox (int maxlines, int ncols, int begin_y, int begin_x,
%% - Print the ASCII percent sign (ASCII code U+0025)
%s - Insert the next parameter as a string
%c - Insert the next parameter as a character (type char)
%s - Insert the next parameter as a string (type char *)
%d - Insert the next parameter as an integer (type int)
%'d - Insert as an int, using the locale's thousands separator
%ld - Insert the next parameter as a long int
%'ld - Insert as a long int, using the locale's thousands separator
%N - Insert the next parameter as a double, using the locale's
national currency format (extension to printf())
%!N - Insert the next parameter as a double, using the locale's
national currency format without the actual currency symbol
(extension to printf())
Instead of using "%" to convert the next parameter, "%m$" can be used
to indicate fixed parameter m (where m is an integer from 1 to 8). For