Commit d7a46093 authored by Jonathan Wilkes's avatar Jonathan Wilkes
Browse files

Merge branch 'fix-makefilename'

parents 731f1a77 1a6b0f5c
......@@ -1449,50 +1449,251 @@ static void until_setup(void)
static t_class *makefilename_class;
typedef enum {
NONE = 0,
INT,
FLOAT,
STRING,
POINTER,
} t_printtype;
typedef struct _makefilename
{
t_object x_obj;
t_symbol *x_format;
t_atomtype x_accept;
int x_intconvert;
t_printtype x_accept;
} t_makefilename;
static void makefilename_scanformat(t_makefilename *x)
static const char* makefilename_doscanformat(const char *str, t_printtype *typ,
char *errormsg)
{
int infmt=0;
char *str;
if (!x->x_format) return;
x->x_accept = A_NULL;
for (str=x->x_format->s_name; *str; str++)
int infmt = 0, i, hashflag = 0, zeroflag = 0;
for (; *str; str++)
{
if (!infmt && *str=='%')
if (!infmt && *str == '%')
{
infmt=1;
/* A single '%' character isn't a valid. It needs a conversion
specifier ('g', 's', etc.) to follow it */
if (*(str+1) == '\0')
{
str++;
sprintf(errormsg, "specifier missing");
*typ = NONE;
return str;
}
infmt = 1;
continue;
}
if (infmt)
{
if (strchr("-.#0123456789",*str)!=0)
/* 1) flags
Check for "%%" which produces a literal '%' in the output */
if (*str == '%')
{
/* Not in a format specifier after all, so let's reset
infmt and continue searching... */
infmt = 0;
continue;
if (*str=='s')
}
for (i = 0; *str && strchr("-+#0", *str) != 0; str++, i++)
{
/* Check for flags. While a space is a legal flag, Pd's
parser would split it off into a separate arg. And since
makefilename has always truncated extra args we don't
support spaces here. We also don't support the single
quote flag.
Some flag/specifier combinations can cause undefined
behavior so we need to track them. */
if (*str == '#') hashflag++;
if (*str == '0') zeroflag++;
/* Since we're dealing with arbitrary input let's keep
the total number of flags sane. */
if (i > 15)
{
sprintf(errormsg, "too many flags");
*typ = NONE;
return str;
}
}
/* 2) width field
Consecutive digits. Technically a width field may also be
'*' to use a variable to set the value. But makefilename has
never supported that, either generating a memory error or crash
upon use. So we exclude it here. */
if (*str == '*')
{
x->x_accept = A_SYMBOL;
x->x_intconvert = 0;
break;
sprintf(errormsg, "variable width value not supported");
*typ = NONE;
return str;
}
if (strchr("fgGeE",*str)!=0)
int maxwidth = 3;
for (i = 0; *str && strchr("0123456789", *str) != 0; str++, i++)
{
x->x_accept = A_FLOAT;
x->x_intconvert = 0;
break;
/* Limit width length to prevent out-of-memory errors. */
if (i >= maxwidth)
{
sprintf(errormsg, "width field cannot be greater than "
"%d digits", maxwidth);
*typ = NONE;
return str;
}
}
if (strchr("xXdiouc",*str)!=0)
/* 3) precision field
A '.' followed by consecutive digits. Can also have a '*' for
variable, so we need to check and exclude as with the width
field above. */
if (*str == '.')
{
x->x_accept = A_FLOAT;
x->x_intconvert = 1;
break;
str++;
if (*str == '*')
{
sprintf(errormsg, "variable precision field not supported");
*typ = NONE;
return str;
}
int maxwidth = 3;
for (i = 0; *str && strchr("0123456789", *str) != 0; str++, i++)
{
if (i >= maxwidth)
{
sprintf(errormsg, "precision field cannot be greater "
"than %d digits", maxwidth);
*typ = NONE;
return str;
}
}
/* precision isn't defined for all conversion specifiers. */
if (*str && strchr("diouxXeEfFgGs", *str) == 0)
{
sprintf(errormsg, "precision field restricted to "
"[diouxXeEfFgGs] specifiers");
*typ = NONE;
return str;
}
}
infmt=0;
/* 4) length field
Fairly certain the length field doesn't make any sense
here. At least I've never seen it used, so let's skip it. */
/* 5) conversion specifier
The type of value we want to fill the slot with. */
/* First, check our flags against the allowed specifiers */
if (*str && hashflag && strchr("fFgGeExXo", *str) == 0)
{
sprintf(errormsg, "'#' flag restricted to [fFgGeExXo] "
"specifiers");
*typ = NONE;
return str;
}
if (*str && zeroflag && strchr("diouxXeEfFgG", *str) == 0)
{
sprintf(errormsg, "'0' flag restricted to [diouxXeEfFgG] "
"specifiers");
*typ = NONE;
return str;
}
/* a C string */
if (*str == 's')
{
*typ = STRING;
return str;
}
/* a float. There's also 'a' and 'A' from C99, but let's stick
to the old school ones for now... */
if (*str && strchr("fFgGeE", *str) != 0)
{
*typ = FLOAT;
return str;
}
/* an int */
if (*str && strchr("xXdiouc", *str) != 0)
{
*typ = INT;
return str;
}
/* a pointer. We don't suppor this in Purr Data because of the
possibility of both undefined and implementation-specific
behavior. */
if (*str && strchr("p", *str) != 0)
{
/* *typ = POINTER; */
/* return str; */
sprintf(errormsg, "p specifier not supported");
*typ = NONE;
return str;
}
/* if we've gotten here it means we are missing a type field.
Undefined behavior would result, so we will bail here */
/* First, let's check for any remaining type fields which
we don't support. That way we can give a better error message. */
if (*str == 'a' || *str == 'A')
sprintf(errormsg, "hexfloat specifier not supported. If you "
"need this feature make a case on the "
"mailing list and we'll consider adding it");
else if (*str == 'n')
sprintf(errormsg, "n specifier not supported");
else if (*str == 'm')
sprintf(errormsg, "m specifier not supported");
else if (*str)
sprintf(errormsg, "bad specifier type '%c'", *str);
else
sprintf(errormsg, "specifier missing");
*typ = NONE;
return str;
}
}
*typ = NONE;
return str;
}
static void makefilename_scanformat(t_makefilename *x)
{
char errorbuf[MAXPDSTRING];
errorbuf[0] = '\0';
const char *str;
t_printtype typ;
if (!x->x_format) return;
str = x->x_format->s_name;
/* First attempt at parsing the format string for the field type. */
str = makefilename_doscanformat(str, &typ, errorbuf);
/* If we got any errors we will have some content in our errorbuf... */
if (*errorbuf)
{
pd_error(x, "makefilename: invalid format string '%s' "
"(%s). Supressing output.",
x->x_format->s_name, errorbuf);
/* set the format string to zero. It would be great to refuse to
create the object here, but we also have to deal with new format
strings with the 'set' message. So for consistency we zero out
the format string and make it a runtimer error. */
x->x_format = 0;
return;
}
x->x_accept = typ;
if (str && (typ != NONE))
{
/* try again, to see if there's another format specifier
(which we forbid) */
str = makefilename_doscanformat(str, &typ, errorbuf);
/* If we've got a type other than none-- OR if we've got something
in our errorbuf-- we've got a syntax error. */
if (typ != NONE || *errorbuf)
{
pd_error(x, "makefilename: invalid format string '%s' "
"(too many specifiers). "
"Suppressing output.",
x->x_format->s_name);
x->x_format = 0;
return;
}
}
}
......@@ -1504,40 +1705,129 @@ static void *makefilename_new(t_symbol *s)
s = gensym("file.%d");
outlet_new(&x->x_obj, &s_symbol);
x->x_format = s;
x->x_accept = A_NULL;
x->x_intconvert = 0;
x->x_accept = NONE;
makefilename_scanformat(x);
return (x);
}
static void makefilename_snprintf(t_makefilename *x, char *buf, char *fmt, ...)
{
int length_minus_null_terminator;
va_list ap;
va_start(ap, fmt);
length_minus_null_terminator =
vsnprintf(buf, MAXPDSTRING, fmt, ap);
va_end(ap);
if (length_minus_null_terminator >= MAXPDSTRING)
{
/* Just don't trust snprintf... */
buf[MAXPDSTRING-1] = '\0';
pd_error(x, "makefilename: output truncated to %d characters",
MAXPDSTRING);
}
}
static void makefilename_float(t_makefilename *x, t_floatarg f)
{
char buf[MAXPDSTRING];
if (x->x_accept == A_FLOAT)
if (!x->x_format)
{
if (x->x_intconvert)
sprintf(buf, x->x_format->s_name, (int)f);
else sprintf(buf, x->x_format->s_name, f);
pd_error(x, "makefilename: no format specifier given");
return;
}
else
switch(x->x_accept)
{
case FLOAT:
makefilename_snprintf(x, buf, x->x_format->s_name, f);
break;
case INT:
makefilename_snprintf(x, buf, x->x_format->s_name, (int)f);
break;
case STRING: {
char buf2[MAXPDSTRING];
sprintf(buf2, "%g", f);
sprintf(buf, x->x_format->s_name, buf2);
makefilename_snprintf(x, buf2, "%g", f);
makefilename_snprintf(x, buf, x->x_format->s_name, buf2);
break;
case NONE:
makefilename_snprintf(x, buf, x->x_format->s_name);
break;
}
if (buf[0]!=0)
outlet_symbol(x->x_obj.ob_outlet, gensym(buf));
default:
/* POINTER type would fall here. We probably don't want to expose
implementation-specific output for whatever rando float values
the user wants to hurl at makefilename. */
pd_error(x, "cannot convert float with specifier: %s",
x->x_format->s_name);
return;
}
if (buf[0] != 0)
outlet_symbol(x->x_obj.ob_outlet, gensym(buf));
}
static void makefilename_symbol(t_makefilename *x, t_symbol *s)
{
char buf[MAXPDSTRING];
if (x->x_accept == A_SYMBOL)
sprintf(buf, x->x_format->s_name, s->s_name);
else
sprintf(buf, x->x_format->s_name, 0);
if (buf[0]!=0)
outlet_symbol(x->x_obj.ob_outlet, gensym(buf));
if (!x->x_format)
{
pd_error(x, "makefilename: no format specifier given");
return;
}
switch(x->x_accept)
{
case STRING:
makefilename_snprintf(x, buf, x->x_format->s_name, s->s_name);
break;
case INT:
makefilename_snprintf(x, buf, x->x_format->s_name, 0);
break;
case FLOAT:
makefilename_snprintf(x, buf, x->x_format->s_name, 0.);
break;
case NONE:
makefilename_snprintf(x, buf, x->x_format->s_name);
break;
default:
/* POINTER case falls here. Technically we could print out
symbol addys using whatever the compiler's implementation-specific
output format happens to be. But that probably shouldn't be exposed
within the patch like this. */
pd_error(x, "cannot convert symbol with specifier: %s",
x->x_format->s_name);
return;
}
if (buf[0] != 0)
outlet_symbol(x->x_obj.ob_outlet, gensym(buf));
}
static void makefilename_bang(t_makefilename *x)
{
char buf[MAXPDSTRING];
if(!x->x_format)
{
pd_error(x, "makefilename: no format specifier given");
return;
}
switch(x->x_accept)
{
case INT:
makefilename_snprintf(x, buf, x->x_format->s_name, 0);
break;
case FLOAT:
makefilename_snprintf(x, buf, x->x_format->s_name, 0.);
break;
case STRING:
makefilename_snprintf(x, buf, x->x_format->s_name, "");
break;
case NONE:
makefilename_snprintf(x, buf, x->x_format->s_name);
break;
default:
pd_error(x, "cannot convert bang with specifier: %s",
x->x_format->s_name);
return;
}
if (buf[0] != 0)
outlet_symbol(x->x_obj.ob_outlet, gensym(buf));
}
static void makefilename_set(t_makefilename *x, t_symbol *s)
......@@ -1553,6 +1843,7 @@ static void makefilename_setup(void)
sizeof(t_makefilename), 0, A_DEFSYM, 0);
class_addfloat(makefilename_class, makefilename_float);
class_addsymbol(makefilename_class, makefilename_symbol);
class_addbang(makefilename_class, makefilename_bang);
class_addmethod(makefilename_class, (t_method)makefilename_set,
gensym("set"), A_SYMBOL, 0);
}
......
#N canvas 3 60 749 617 12;
#X obj 345 301 r \$0-result;
#X obj 345 326 route 0;
#X obj 453 470 print failure;
#X obj 430 336 tgl 28 0 empty empty Print_All_Results 31 11 0 12 -262144
-1 -1 1 1;
#X obj 475 301 r \$0-result;
#X obj 475 326 route 0;
#X obj 583 470 print failure;
#X obj 560 336 tgl 28 0 empty empty Print_All_Results 31 11 0 12 -262144
-1 -1 0 1;
#X obj 159 149 bng 31 250 50 0 empty empty Run_all 39 13 0 12 -262144
-1 -1;
#X obj 56 25 r init;
#X obj 345 191 route dollarzero;
#X obj 345 411 t b a;
#X obj 345 541 s pd;
#X obj 475 411 t b a;
#X obj 475 541 s pd;
#X obj 56 120 trigger bang bang anything;
#X msg 56 145 gui;
#X obj 56 170 pdinfo;
#X obj 56 195 sel 0;
#X obj 56 245 s pd;
#X msg 345 516 quit 1;
#X msg 475 516 quit 1;
#X msg 56 220 quit;
#X obj 145 191 rtest msg_dollarzero;
#X obj 145 246 rtest msg_dollarzero_semi;
#X obj 145 302 rtest msg_click;
#X obj 345 216 rtest binbuf_dollarzero;
#X msg 345 440 gui;
#X obj 345 465 pdinfo;
#X obj 345 490 sel 0;
#X msg 475 440 gui;
#X obj 475 465 pdinfo;
#X obj 475 490 sel 0;
#X text 117 25 <- we start Pd with the -send "init etc." flag. This
will automatically start the tests and allow us to send a comma-separated
list of messages which will be evaluated by Pd without a target. This
......@@ -34,13 +34,22 @@ is handy for some binbuf tests.;
#X text 536 150 <- we have to escape the arg;
#X text 556 190 escape it in a comment.;
#X text 556 170 in bash but we can't;
#X obj 391 374 spigot;
#X obj 407 440 route 1;
#X obj 407 495 print success;
#X obj 521 374 spigot;
#X obj 537 440 route 1;
#X obj 537 495 print success;
#X obj 145 358 rtest unpost_sanity;
#X obj 145 414 rtest unpost_error;
#X obj 145 465 rtest unpost_print;
#X obj 145 516 rtest unpost_long_message;
#X obj 145 569 rtest makefilename_double_percent;
#X obj 145 620 rtest makefilename_code_coverage;
#N canvas 461 242 450 323 (subpatch) 0;
#X restore 148 1105 pd;
#X obj 145 671 rtest makefilename_default;
#X obj 145 722 rtest makefilename_default_bang;
#X obj 145 773 rtest makefilename_float;
#X obj 145 824 rtest makefilename_symbol;
#X obj 145 875 rtest makefilename_bang;
#X connect 0 0 1 0;
#X connect 1 0 7 0;
#X connect 1 1 29 0;
......@@ -70,3 +79,10 @@ is handy for some binbuf tests.;
#X connect 32 0 33 0;
#X connect 33 0 34 0;
#X connect 34 0 35 0;
#X connect 35 0 36 0;
#X connect 36 0 37 0;
#X connect 37 0 39 0;
#X connect 39 0 40 0;
#X connect 40 0 41 0;
#X connect 41 0 42 0;
#X connect 42 0 43 0;
#N canvas 3 60 1105 621 12;
#X obj 41 8 inlet;
#X obj 41 794 outlet;
#X msg 908 211 set %5.5c;
#X obj 876 183 t a b;
#X obj 795 157 unpost error;
#X obj 876 246 makefilename;
#X text 890 157 supress error;
#X obj 761 283 list;
#X obj 761 132 t b a;
#X obj 761 308 route bang symbol;
#X obj 761 333 f 1;
#X obj 819 333 b;
#X msg 819 358 0;
#X text 325 138 "INT" branch;
#X obj 391 327 symbol;
#X obj 391 168 t b a b;
#X obj 437 201 symbol;
#X obj 414 271 makefilename %i_test;
#X obj 475 407 b;
#X msg 475 432 0;
#X obj 391 399 f 1;
#X obj 41 337 symbol;
#X obj 41 178 t b a b;
#X obj 87 211 symbol;
#X obj 125 417 b;
#X msg 125 442 0;
#X obj 41 409 f 1;
#X obj 64 281 makefilename %g_test;
#X text 56 136 "FLOAT" branch;
#X text 789 109 trigger a type "NONE" branch with broken specifier
;
#X obj 571 327 symbol;
#X obj 571 168 t b a b;
#X obj 617 201 symbol;
#X obj 655 407 b;
#X msg 655 432 0;
#X obj 571 399 f 1;
#X text 563 141 well-formed "NONE" branch;
#X obj 594 271 makefilename test;
#X obj 571 374 select test;
#X obj 41 66 trigger anything anything anything anything anything;
#X obj 221 367 symbol;
#X obj 221 208 t b a b;
#X obj 267 241 symbol;
#X obj 305 447 b;
#X msg 305 472 0;
#X obj 221 439 f 1;
#X text 185 168 "STRING" branch;
#X obj 391 374 select 0_test;
#X obj 41 384 select 0_test;
#X obj 571 492 list append incoming symbol correctly triggers constant
string;
#X obj 391 602 list append incoming symbol correctly converted to int
;
#X obj 221 642 list append incoming symbol correctly converted to symbol
;
#X obj 41 692 list append incoming symbol correctly formatted in outgoing
symbol;
#X obj 761 395 list append broken format string should suppress output
for incoming bang;
#X msg 41 36 bang;
#X obj 244 311 makefilename %s_test;
#X obj 221 414 select _test;
#X connect 0 0 54 0;
#X connect 2 0 5 0;
#X connect 3 0 5 0;
#X connect 3 1 2 0;
#X connect 3 1 7 1;
#X connect 4 1 3 0;
#X connect 5 0 7 1;
#X connect 7 0 9 0;
#X connect 8 0 7 0;
#X connect 8 1 4 0;
#X connect 9 0 10 0;
#X connect 9 1 11 0;
#X connect 10 0 53 0;
#X connect 11 0 12 0;
#X connect 12 0 53 0;
#X connect 14 0 47 0;
#X connect 15 0 14 0;
#X connect 15 1 17 0;
#X connect 15 2 16 0;
#X connect 16 0 14 1;
#X connect 17 0 14 1;
#X connect 18 0 19 0;
#X connect 19 0 50 0;
#X connect 20 0 50 0;
#X connect 21 0 48 0;
#X connect 22 0 21 0;
#X connect 22 1 27 0;
#X connect 22 2 23 0;
#X connect 23 0 21 1;
#X connect 24 0 25 0;
#X connect 25 0 52 0;
#X connect 26 0 52 0;
#X connect 27 0 21 1;
#X connect 30 0 38 0;
#X connect 31 0 30 0;
#X connect 31 1 37 0;
#X connect 31 2 32 0;
#X connect 32 0 30 1;
#X connect 33 0 34 0;
#X connect 34 0 49 0;
#X connect 35 0 49 0;
#X connect 37 0 30 1;
#X connect 38 0 35 0;
#X connect 38 1 33 0;
#X connect 39 0 22 0;
#X connect 39 1 41 0;
#X connect 39 2 15 0;
#X connect 39 3 31 0;