/* Copyright (c) 2002-2003 krzYszcz and others. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ /* FIXME empty string as a default for %s */ #include #include #include "m_pd.h" #include "common/loud.h" #define SPRINTF_DEBUG 0 /* Pattern types. These are the parsing routine's return values. If returned value is >= SPRINTF_MINSLOTTYPE, then another slot is created (i.e. an inlet, and a proxy handling it). */ #define SPRINTF_UNSUPPORTED 0 #define SPRINTF_LITERAL 1 #define SPRINTF_MINSLOTTYPE 2 #define SPRINTF_INT 2 #define SPRINTF_FLOAT 3 #define SPRINTF_CHAR 4 #define SPRINTF_STRING 5 /* Numbers: assuming max 62 digits preceding a decimal point in any fixed-point representation of a t_float (39 in my system) -- need to be sure, that using max precision would never produce a representation longer than max width. If this is so, then no number representation would exceed max width (presumably...). Strings: for the time being, any string longer than max width would be truncated (somehow compatible with Str256, but LATER warn-and-allow). */ /* LATER rethink it all */ #define SPRINTF_MAXPRECISION 192 #define SPRINTF_MAXWIDTH 256 typedef struct _sprintf { t_object x_ob; int x_nslots; int x_nproxies; /* as requested (and allocated) */ t_pd **x_proxies; int x_fsize; /* as allocated (i.e. including a terminating 0) */ char *x_fstring; } t_sprintf; typedef struct _sprintf_proxy { t_object p_ob; t_sprintf *p_master; int p_id; int p_type; /* a value #defined above */ char *p_pattern; char *p_pattend; t_atom p_atom; /* current input */ int p_size; /* also an input validation flag */ } t_sprintf_proxy; static t_class *sprintf_class; static t_class *sprintf_proxy_class; /* CHECKED: 'symout' argument has no special meaning in max4.07, LATER investigate */ /* LATER use snprintf, if it is available on other systems (should be...) */ static void sprintf_proxy_checkit(t_sprintf_proxy *x, char *buf, int checkin) { int result = 0; char *pattend = x->p_pattend; if (pattend) { char tmp = *pattend; *pattend = 0; if (x->p_atom.a_type == A_FLOAT) { t_float f = x->p_atom.a_w.w_float; if (x->p_type == SPRINTF_INT) /* CHECKME large/negative values */ result = sprintf(buf, x->p_pattern, (int)f); else if (x->p_type == SPRINTF_FLOAT) result = sprintf(buf, x->p_pattern, f); else if (x->p_type == SPRINTF_CHAR) /* CHECKED: if 0 is input into a %c-slot, the whole output string is null-terminated */ /* CHECKED: float into a %c-slot is truncated, but CHECKME large/negative values */ result = sprintf(buf, x->p_pattern, (unsigned char)f); else if (x->p_type == SPRINTF_STRING) { /* CHECKED: any number input into a %s-slot is ok */ char tmp[64]; /* LATER rethink */ sprintf(tmp, "%g", f); result = sprintf(buf, x->p_pattern, tmp); } else /* LATER consider calling it a bug(), rather than error? */ loud_error((t_pd *)x->p_master, "can't convert float to type of argument %d", x->p_id + 1); } else if (x->p_atom.a_type == A_SYMBOL) { t_symbol *s = x->p_atom.a_w.w_symbol; if (x->p_type == SPRINTF_STRING) { if (strlen(s->s_name) > SPRINTF_MAXWIDTH) { strncpy(buf, s->s_name, SPRINTF_MAXWIDTH); buf[SPRINTF_MAXWIDTH] = 0; result = SPRINTF_MAXWIDTH; } else result = sprintf(buf, x->p_pattern, s->s_name); } else /* CHECKED */ loud_error((t_pd *)x->p_master, "can't convert symbol to type of argument %d", x->p_id + 1); } *pattend = tmp; } else bug("sprintf_proxy_checkit"); if (result > 0) { #if SPRINTF_DEBUG if (checkin) post("[%d in \"%s\"]", result, buf); #endif x->p_size = result; } else { #if SPRINTF_DEBUG if (checkin) post("checkit failed"); #endif x->p_size = 0; } } static void sprintf_dooutput(t_sprintf *x) { int i, outsize; char *outstring; outsize = x->x_fsize; /* this is strlen() + 1 */ /* LATER consider subtracting format pattern sizes */ for (i = 0; i < x->x_nslots; i++) { t_sprintf_proxy *y = (t_sprintf_proxy *)x->x_proxies[i]; if (y->p_size) outsize += y->p_size; else { /* slot i has received an invalid input -- CHECKME if this condition blocks all subsequent output requests? */ return; } } if (outsize > 0 && (outstring = getbytes(outsize))) { char *inp = x->x_fstring; char *outp = outstring; for (i = 0; i < x->x_nslots; i++) { t_sprintf_proxy *y = (t_sprintf_proxy *)x->x_proxies[i]; int len = y->p_pattern - inp; if (len > 0) { strncpy(outp, inp, len); outp += len; } sprintf_proxy_checkit(y, outp, 0); outp += y->p_size; /* p_size is never negative */ inp = y->p_pattend; } strcpy(outp, inp); outp = outstring; while (*outp == ' ' || *outp == '\t' || *outp == '\n' || *outp == '\r') outp++; if (*outp) { t_binbuf *bb = binbuf_new(); int ac; t_atom *av; binbuf_text(bb, outp, strlen(outp)); ac = binbuf_getnatom(bb); av = binbuf_getvec(bb); if (ac) { if (av->a_type == A_SYMBOL) outlet_anything(((t_object *)x)->ob_outlet, av->a_w.w_symbol, ac - 1, av + 1); else if (av->a_type == A_FLOAT) { if (ac > 1) outlet_list(((t_object *)x)->ob_outlet, &s_list, ac, av); else outlet_float(((t_object *)x)->ob_outlet, av->a_w.w_float); } } binbuf_free(bb); } freebytes(outstring, outsize); } } static void sprintf_proxy_bang(t_sprintf_proxy *x) { sprintf_dooutput(x->p_master); /* CHECKED (in any inlet) */ } static void sprintf_proxy_float(t_sprintf_proxy *x, t_float f) { char buf[SPRINTF_MAXWIDTH + 1]; /* LATER rethink */ SETFLOAT(&x->p_atom, f); sprintf_proxy_checkit(x, buf, 1); if (x->p_id == 0 && x->p_size) sprintf_dooutput(x->p_master); /* CHECKED: only first inlet */ } static void sprintf_proxy_symbol(t_sprintf_proxy *x, t_symbol *s) { char buf[SPRINTF_MAXWIDTH + 1]; /* LATER rethink */ if (s && *s->s_name) SETSYMBOL(&x->p_atom, s); else SETFLOAT(&x->p_atom, 0); sprintf_proxy_checkit(x, buf, 1); if (x->p_id == 0 && x->p_size) sprintf_dooutput(x->p_master); /* CHECKED: only first inlet */ } static void sprintf_dolist(t_sprintf *x, t_symbol *s, int ac, t_atom *av, int startid) { int cnt = x->x_nslots - startid; if (ac > cnt) ac = cnt; if (ac-- > 0) { int id; for (id = startid + ac, av += ac; id >= startid; id--, av--) { if (av->a_type == A_FLOAT) sprintf_proxy_float((t_sprintf_proxy *)x->x_proxies[id], av->a_w.w_float); else if (av->a_type == A_SYMBOL) sprintf_proxy_symbol((t_sprintf_proxy *)x->x_proxies[id], av->a_w.w_symbol); } } } static void sprintf_doanything(t_sprintf *x, t_symbol *s, int ac, t_atom *av, int startid) { if (s && s != &s_) { sprintf_dolist(x, 0, ac, av, startid + 1); sprintf_proxy_symbol((t_sprintf_proxy *)x->x_proxies[startid], s); } else sprintf_dolist(x, 0, ac, av, startid); } static void sprintf_proxy_list(t_sprintf_proxy *x, t_symbol *s, int ac, t_atom *av) { sprintf_dolist(x->p_master, s, ac, av, x->p_id); } static void sprintf_proxy_anything(t_sprintf_proxy *x, t_symbol *s, int ac, t_atom *av) { sprintf_doanything(x->p_master, s, ac, av, x->p_id); } static void sprintf_bang(t_sprintf *x) { if (x->x_nslots) sprintf_proxy_bang((t_sprintf_proxy *)x->x_proxies[0]); } static void sprintf_float(t_sprintf *x, t_float f) { if (x->x_nslots) sprintf_proxy_float((t_sprintf_proxy *)x->x_proxies[0], f); } static void sprintf_symbol(t_sprintf *x, t_symbol *s) { if (x->x_nslots) sprintf_proxy_symbol((t_sprintf_proxy *)x->x_proxies[0], s); } static void sprintf_list(t_sprintf *x, t_symbol *s, int ac, t_atom *av) { if (x->x_nslots) sprintf_dolist(x, s, ac, av, 0); } static void sprintf_anything(t_sprintf *x, t_symbol *s, int ac, t_atom *av) { if (x->x_nslots) sprintf_doanything(x, s, ac, av, 0); } /* adjusted binbuf_gettext(), LATER do it right */ static char *hammer_gettext(int ac, t_atom *av, int *sizep) { char *buf = getbytes(1); int size = 1; char atomtext[MAXPDSTRING]; while (ac--) { char *newbuf; int newsize; if (buf[size-1] == 0 || av->a_type == A_SEMI || av->a_type == A_COMMA) size--; atom_string(av, atomtext, MAXPDSTRING); newsize = size + strlen(atomtext) + 1; if (!(newbuf = resizebytes(buf, size, newsize))) { *sizep = 1; return (getbytes(1)); } buf = newbuf; strcpy(buf + size, atomtext); size = newsize; buf[size-1] = ' '; av++; } buf[size-1] = 0; *sizep = size; return (buf); } /* Called twice: 1st pass (with x == 0) is used for counting valid patterns; 2nd pass (after object allocation) -- for initializing the proxies. If there is a "%%" pattern, then the buffer is shrinked in the second pass (LATER rethink). */ static int sprintf_parsepattern(t_sprintf *x, char **patternp) { int type = SPRINTF_UNSUPPORTED; char errstring[MAXPDSTRING]; char *ptr; char modifier = 0; int width = 0; int precision = 0; int *numfield = &width; int dotseen = 0; *errstring = 0; for (ptr = *patternp; *ptr; ptr++) { if (*ptr >= '0' && *ptr <= '9') { if (!numfield) { if (x) sprintf(errstring, "extra number field"); break; } *numfield = 10 * *numfield + *ptr - '0'; if (dotseen) { if (precision > SPRINTF_MAXPRECISION) { if (x) sprintf(errstring, "precision field too large"); break; } } else { if (width > SPRINTF_MAXWIDTH) { if (x) sprintf(errstring, "width field too large"); break; } } continue; } if (*numfield) numfield = 0; if (strchr("diouxX", *ptr)) { type = SPRINTF_INT; break; } else if (strchr("aAeEfgG", *ptr)) { if (modifier) { if (x) sprintf(errstring, "\'%c\' modifier not supported", modifier); break; } type = SPRINTF_FLOAT; break; } else if (strchr("c", *ptr)) { if (modifier) { if (x) sprintf(errstring, "\'%c\' modifier not supported", modifier); break; } type = SPRINTF_CHAR; break; } else if (strchr("s", *ptr)) { if (modifier) { if (x) sprintf(errstring, "\'%c\' modifier not supported", modifier); break; } type = SPRINTF_STRING; break; } else if (*ptr == '%') { type = SPRINTF_LITERAL; if (x) { /* buffer-shrinking hack, LATER rethink */ char *p1 = ptr, *p2 = ptr + 1; do *p1++ = *p2; while (*p2++); ptr--; } break; } else if (strchr("CSnm", *ptr)) { if (x) sprintf(errstring, "\'%c\' type not supported", *ptr); break; } else if (strchr("l", *ptr)) { if (modifier) { if (x) sprintf(errstring, "only single modifier is supported"); break; } modifier = *ptr; } else if (strchr("hjLqtzZ", *ptr)) { if (x) sprintf(errstring, "\'%c\' modifier not supported", *ptr); break; } else if (*ptr == '.') { if (dotseen) { if (x) sprintf(errstring, "multiple dots"); break; } numfield = &precision; dotseen = 1; } else if (*ptr == '$') { if (x) sprintf(errstring, "parameter number field not supported"); break; } else if (*ptr == '*') { if (x) sprintf(errstring, "%s parameter not supported", (dotseen ? "precision" : "width")); break; } else if (!strchr("-+ #\'", *ptr)) { if (x) sprintf(errstring, "\'%c\' format character not supported", *ptr); break; } } if (*ptr) ptr++; /* LATER rethink */ else if (x) sprintf(errstring, "type not specified"); if (x && type == SPRINTF_UNSUPPORTED) { if (*errstring) loud_error((t_pd *)x, "slot skipped (%s %s)", errstring, "in a format pattern"); else loud_error((t_pd *)x, "slot skipped"); } *patternp = ptr; return (type); } static void sprintf_free(t_sprintf *x) { if (x->x_proxies) { int i = x->x_nslots; while (i--) { t_sprintf_proxy *y = (t_sprintf_proxy *)x->x_proxies[i]; pd_free((t_pd *)y); } freebytes(x->x_proxies, x->x_nproxies * sizeof(*x->x_proxies)); } if (x->x_fstring) freebytes(x->x_fstring, x->x_fsize); } static void *sprintf_new(t_symbol *s, int ac, t_atom *av) { t_sprintf *x; int fsize; char *fstring; char *p1, *p2; int i, nslots, nproxies = 0; t_pd **proxies; fstring = hammer_gettext(ac, av, &fsize); p1 = fstring; while (p2 = strchr(p1, '%')) { int type; p1 = p2 + 1; type = sprintf_parsepattern(0, &p1); if (type >= SPRINTF_MINSLOTTYPE) nproxies++; } if (!nproxies) { /* CHECKED: an object without arguments, if created in the editor, has no inlets/outlets, but it would have one inlet (no outlets) upon loading. Error message is printed in either case. */ x = (t_sprintf *)pd_new(sprintf_class); x->x_nslots = 0; x->x_nproxies = 0; x->x_proxies = 0; x->x_fsize = fsize; x->x_fstring = fstring; p1 = fstring; while (p2 = strchr(p1, '%')) { p1 = p2 + 1; sprintf_parsepattern(x, &p1); } loud_error((t_pd *)x, "an object created without valid format patterns..."); return (x); } #if SPRINTF_DEBUG post("%d slots:", nproxies); #endif /* CHECKED: max creates as many inlets, as there are %-signs, no matter if they are valid, or not -- if not, it prints ``can't convert'' errors for any input... */ if (!(proxies = (t_pd **)getbytes(nproxies * sizeof(*proxies)))) { freebytes(fstring, fsize); return (0); } for (nslots = 0; nslots < nproxies; nslots++) if (!(proxies[nslots] = pd_new(sprintf_proxy_class))) break; if (!nslots) { freebytes(fstring, fsize); freebytes(proxies, nproxies * sizeof(*proxies)); return (0); } x = (t_sprintf *)pd_new(sprintf_class); x->x_nslots = nslots; x->x_nproxies = nproxies; x->x_proxies = proxies; x->x_fsize = fsize; x->x_fstring = fstring; p1 = fstring; i = 0; while (p2 = strchr(p1, '%')) { int type; p1 = p2 + 1; type = sprintf_parsepattern(x, &p1); if (type >= SPRINTF_MINSLOTTYPE) { #if SPRINTF_DEBUG char tmp = *++p1; *p1 = 0; poststring(p2); endpost(); *p1 = tmp; #endif if (i < nslots) { char buf[SPRINTF_MAXWIDTH + 1]; /* LATER rethink */ t_sprintf_proxy *y = (t_sprintf_proxy *)proxies[i]; y->p_master = x; y->p_id = i; y->p_type = type; y->p_pattern = p2; y->p_pattend = p1; SETFLOAT(&y->p_atom, 0); y->p_size = 0; if (i) inlet_new((t_object *)x, (t_pd *)y, 0, 0); sprintf_proxy_checkit(y, buf, 1); i++; } } } #if SPRINTF_DEBUG post("printf(\"%s\", ...)", fstring); #endif outlet_new((t_object *)x, &s_anything); return (x); } void sprintf_setup(void) { sprintf_class = class_new(gensym("sprintf"), (t_newmethod)sprintf_new, (t_method)sprintf_free, sizeof(t_sprintf), 0, A_GIMME, 0); class_addbang(sprintf_class, sprintf_bang); class_addfloat(sprintf_class, sprintf_float); class_addsymbol(sprintf_class, sprintf_symbol); class_addlist(sprintf_class, sprintf_list); class_addanything(sprintf_class, sprintf_anything); sprintf_proxy_class = class_new(gensym("_sprintf_proxy"), 0, 0, sizeof(t_sprintf_proxy), CLASS_PD | CLASS_NOINLET, 0); class_addbang(sprintf_proxy_class, sprintf_proxy_bang); class_addfloat(sprintf_proxy_class, sprintf_proxy_float); class_addsymbol(sprintf_proxy_class, sprintf_proxy_symbol); class_addlist(sprintf_proxy_class, sprintf_proxy_list); class_addanything(sprintf_proxy_class, sprintf_proxy_anything); }