/************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at https://graphviz.org *************************************************************************/ #include #include #include #include #include #include #include #include /* The engine for formatting data ** ** Written by Kiem-Phong Vo. */ #define HIGHBITI (~((~0u) >> 1)) #define HIGHBITL (~((~0ull) >> 1)) #define SFFMT_PREFIX (SFFMT_MINUS|SFFMT_SIGN|SFFMT_BLANK) #define FPRECIS 6 /* default precision for floats */ /** * @param f file to print to * @param format Structure describing how to print */ int sfprint(FILE *f, Sffmt_t *format) { int v = 0, n_s, base, fmt, flags; long long lv; char *sp, *ssp, *endsp, *ep, *endep; int dot, width, precis, n, n_output = 0; int sign, decpt; ssize_t size; double dval; char *tls[2], **ls; /* for %..[separ]s */ const char *t_str; /* stuff between () */ ssize_t n_str; /* its length */ Argv_t argv; /* for extf to return value */ Sffmt_t *ft; /* format environment */ int argp, argn; /* arg position and number */ #define SLACK 1024 char buf[SF_MAXDIGITS + SLACK]; char decimal = 0, thousand = 0; #define SFputc(f,c) do { \ if (putc((c), (f)) == EOF) { \ goto done; \ } \ } while (0) #define SFnputc(f,c,n) do { \ for (int i_ = 0; i_ < (n); ++i_) { \ if (putc((c), (f)) == EOF) { \ goto done; \ } \ } \ (n) = 0; \ } while (0) #define SFwrite(f,s,n) do { \ if ((n) > 0 && fwrite((s), (size_t)(n), 1, (f)) < 1) { \ goto done; \ } \ } while (0) tls[1] = NULL; ft = NULL; argn = -1; // stack a new environment argv.ft = format; assert(argv.ft != NULL); assert(argv.ft->form != NULL); const char *form = argv.ft->form; ft = argv.ft; while ((n = *form)) { if (n != '%') { /* collect the non-pattern chars */ sp = (char *) form++; while (*form && *form != '%') form += 1; n = form - sp; SFwrite(f, sp, n); continue; } else form += 1; flags = 0; size = width = precis = base = n_s = argp = -1; ssp = _Sfdigits; endep = ep = NULL; endsp = sp = buf + (sizeof(buf) - 1); t_str = NULL; n_str = dot = 0; loop_flags: /* LOOP FOR \0, %, FLAGS, WIDTH, PRECISION, BASE, TYPE */ switch ((fmt = *form++)) { case '\0': SFputc(f, '%'); goto done; case '%': SFputc(f, '%'); continue; case LEFTP: /* get the type enclosed in balanced parens */ t_str = form; for (v = 1;;) { switch (*form++) { case 0: /* not balancable, retract */ form = t_str; t_str = NULL; n_str = 0; goto loop_flags; case LEFTP: /* increasing nested level */ v += 1; continue; case RIGHTP: /* decreasing nested level */ if ((v -= 1) != 0) continue; if (*t_str != '*') n_str = (form - 1) - t_str; else { t_str = _Sffmtintf(t_str + 1, &n); FP_SET(-1, argn); FMTSET(ft, form, LEFTP, 0, 0, 0, 0, 0, NULL, 0); n = ft->extf(&argv, ft); if (n < 0) goto done; assert(ft->flags & SFFMT_VALUE); if ((t_str = argv.s) && (n_str = (int)ft->size) < 0) n_str = (ssize_t)strlen(t_str); } goto loop_flags; default: // skip over break; } } case '-': flags = (flags & ~SFFMT_ZERO) | SFFMT_LEFT; goto loop_flags; case '0': if (!(flags & SFFMT_LEFT)) flags |= SFFMT_ZERO; goto loop_flags; case ' ': if (!(flags & SFFMT_SIGN)) flags |= SFFMT_BLANK; goto loop_flags; case '+': flags = (flags & ~SFFMT_BLANK) | SFFMT_SIGN; goto loop_flags; case '#': flags |= SFFMT_ALTER; goto loop_flags; case QUOTE: SFSETLOCALE(decimal, thousand); if (thousand) flags |= SFFMT_THOUSAND; goto loop_flags; case '.': dot += 1; if (dot == 1) { /* so base can be defined without setting precis */ if (*form != '.') precis = 0; } else if (dot == 2) { base = 0; /* for %s,%c */ if (*form == 'c' || *form == 's') goto loop_flags; if (*form && !gv_isalnum(*form) && (form[1] == 'c' || form[1] == 's')) { if (*form == '*') goto do_star; else { base = *form++; goto loop_flags; } } } if (gv_isdigit(*form)) { fmt = *form++; goto dot_size; } else if (*form != '*') goto loop_flags; do_star: form += 1; /* fall through */ case '*': form = _Sffmtintf(form, &n); n = FP_SET(-1, argn); FMTSET(ft, form, '.', dot, 0, 0, 0, 0, NULL, 0); if (ft->extf(&argv, ft) < 0) goto done; assert(ft->flags & SFFMT_VALUE); v = argv.i; goto dot_set; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': dot_size: for (v = fmt - '0'; gv_isdigit(*form); ++form) v = v * 10 + (*form - '0'); dot_set: if (dot == 0) { if ((width = v) < 0) { width = -width; flags = (flags & ~SFFMT_ZERO) | SFFMT_LEFT; } } else if (dot == 1) precis = v; else if (dot == 2) base = v; goto loop_flags; case 'I': /* object length */ size = 0; flags = (flags & ~SFFMT_TYPES) | SFFMT_IFLAG; if (gv_isdigit(*form)) { for (n = *form; gv_isdigit(n); n = *++form) size = size * 10 + (n - '0'); } else if (*form == '*') { form = _Sffmtintf(form + 1, &n); n = FP_SET(-1, argn); FMTSET(ft, form, 'I', sizeof(int), 0, 0, 0, 0, NULL, 0); if (ft->extf(&argv, ft) < 0) goto done; assert(ft->flags & SFFMT_VALUE); size = argv.i; } goto loop_flags; case 'l': size = -1; flags &= ~SFFMT_TYPES; if (*form == 'l') { form += 1; flags |= SFFMT_LLONG; } else flags |= SFFMT_LONG; goto loop_flags; case 'h': size = -1; flags &= ~SFFMT_TYPES; if (*form == 'h') { form += 1; flags |= SFFMT_SSHORT; } else flags |= SFFMT_SHORT; goto loop_flags; case 'L': size = -1; flags = (flags & ~SFFMT_TYPES) | SFFMT_LDOUBLE; goto loop_flags; case 'j': size = -1; flags = (flags & ~SFFMT_TYPES) | SFFMT_JFLAG; goto loop_flags; case 'z': size = -1; flags = (flags & ~SFFMT_TYPES) | SFFMT_ZFLAG; goto loop_flags; case 't': size = -1; flags = (flags & ~SFFMT_TYPES) | SFFMT_TFLAG; goto loop_flags; default: // continue with logic below break; } /* set the correct size */ if (flags & (SFFMT_TYPES & ~SFFMT_IFLAG)) { if (_Sftype[fmt] & (SFFMT_INT | SFFMT_UINT)) { size = (flags & SFFMT_LLONG) ? (ssize_t)sizeof(long long) : (flags & SFFMT_LONG) ? (ssize_t)sizeof(long) : (flags & SFFMT_SHORT) ? (ssize_t)sizeof(short) : (flags & SFFMT_SSHORT) ? (ssize_t)sizeof(char) : (flags & SFFMT_JFLAG) ? (ssize_t)sizeof(long long) : (flags & SFFMT_TFLAG) ? (ssize_t)sizeof(ptrdiff_t) : (flags & SFFMT_ZFLAG) ? (ssize_t)sizeof(size_t) : -1; } else if (_Sftype[fmt] & SFFMT_FLOAT) { size = (flags & SFFMT_LDOUBLE) ? (ssize_t)sizeof(long double) : (flags & (SFFMT_LONG | SFFMT_LLONG)) ? (ssize_t)sizeof(double) : -1; } } argp = FP_SET(argp, argn); FMTSET(ft, form, fmt, size, flags, width, precis, base, t_str, n_str); v = ft->extf(&argv, ft); if (v < 0) goto done; else if (v == 0) { // extf did not output FMTGET(ft, form, fmt, size, flags, width, precis, base); assert(ft->flags & SFFMT_VALUE); } else if (v > 0) { // extf output v bytes n_output += v; continue; } switch (fmt) { /* PRINTF DIRECTIVES */ default: /* unknown directive */ form -= 1; argn -= 1; continue; case 's': if (base >= 0) { /* list of strings */ if (!(ls = argv.sp) || !ls[0]) continue; } else { if (!(sp = argv.s)) sp = "(null)"; ls = tls; tls[0] = sp; } for (sp = *ls;;) { if ((v = size) >= 0) { if (precis >= 0 && v > precis) v = precis; } else if (precis < 0) v = strlen(sp); else { /* precis >= 0 means min(strlen,precis) */ for (v = 0; v < precis; ++v) if (sp[v] == 0) break; } if ((n = width - v) > 0) { if (flags & SFFMT_ZERO) { SFnputc(f, '0', n); } else if (!(flags & SFFMT_LEFT)) { SFnputc(f, ' ', n); } } SFwrite(f, sp, v); if (n > 0) { SFnputc(f, ' ', n); } if (!(sp = *++ls)) break; else if (base > 0) { SFputc(f, base); } } continue; case 'c': /* an array of characters */ if (base >= 0) { if (!(sp = argv.s) || !sp[0]) continue; } else { fmt = (int) argv.c; sp = buf; buf[0] = fmt; buf[1] = 0; } if (precis <= 0) precis = 1; for (fmt = *sp;;) { if ((n = width - precis) > 0 && !(flags & SFFMT_LEFT)) { SFnputc(f, ' ', n); } v = precis; SFnputc(f, fmt, v); if (n > 0) { SFnputc(f, ' ', n); } if (!(fmt = *++sp)) break; else if (base > 0) { SFputc(f, base); } } continue; case 'p': /* pointer value */ fmt = 'x'; base = 16; n_s = 15; n = 4; flags = (flags & ~(SFFMT_SIGN | SFFMT_BLANK | SFFMT_ZERO)) | SFFMT_ALTER; if (sizeof(void*) > sizeof(int)) { lv = (long long)(intptr_t)argv.vp; goto long_cvt; } else { v = (int)(intptr_t)argv.vp; goto int_cvt; } case 'o': base = 8; n_s = 7; n = 3; flags &= ~(SFFMT_SIGN | SFFMT_BLANK); goto int_arg; case 'X': ssp = "0123456789ABCDEF"; // fall through case 'x': base = 16; n_s = 15; n = 4; flags &= ~(SFFMT_SIGN | SFFMT_BLANK); goto int_arg; case 'i': fmt = 'd'; goto d_format; case 'u': flags &= ~(SFFMT_SIGN | SFFMT_BLANK); // fall through case 'd': d_format: if (base < 2 || base > SF_RADIX) base = 10; if ((base & (n_s = base - 1)) == 0) { if (base < 8) n = base < 4 ? 1 : 2; else if (base < 32) n = base < 16 ? 3 : 4; else n = base < 64 ? 5 : 6; } else n_s = base == 10 ? -1 : 0; int_arg: if ((sizeof(long) > sizeof(int) || sizeof(void*) > sizeof(int)) && FMTCMP(size, long long, long long)) { lv = argv.ll; goto long_cvt; } else if ((sizeof(long) > sizeof(int) || sizeof(void*) > sizeof(int)) && FMTCMP(size, long, long long)) { if (fmt == 'd') lv = (long long)argv.l; else lv = (long long)(ulong)argv.l; long_cvt: if (lv == 0 && precis == 0) break; if (lv < 0 && fmt == 'd') { flags |= SFFMT_MINUS; if ((unsigned long long)lv == HIGHBITL) { // avoid overflow lv = (long long)(HIGHBITL / (unsigned long long)base); *--sp = _Sfdigits[HIGHBITL - (unsigned long long)lv * (unsigned long long)base]; } else lv = -lv; } if (n_s < 0) { /* base 10 */ long long nv; sfucvt(lv, sp, nv, ssp, long long, unsigned long long); } else if (n_s > 0) { /* base power-of-2 */ do { *--sp = ssp[lv & n_s]; } while ((lv = (unsigned long long)lv >> n)); } else { /* general base */ do { *--sp = ssp[(unsigned long long)lv % (unsigned long long)base]; } while ((lv = (unsigned long long)lv / (unsigned long long)base)); } } else if (sizeof(short) < sizeof(int) && FMTCMP(size, short, long long)) { if (ft->flags & SFFMT_VALUE) { if (fmt == 'd') v = (int) ((short) argv.h); else v = (int) ((ushort) argv.h); } else { if (fmt == 'd') v = (int) ((short) argv.i); else v = (int) ((ushort) argv.i); } goto int_cvt; } else if (size == sizeof(char)) { if (ft->flags & SFFMT_VALUE) { if (fmt == 'd') v = (int) ((char) argv.c); else v = (int) ((uchar) argv.c); } else { if (fmt == 'd') v = (int) ((char) argv.i); else v = (int) ((uchar) argv.i); } goto int_cvt; } else { v = argv.i; int_cvt: if (v == 0 && precis == 0) break; if (v < 0 && fmt == 'd') { flags |= SFFMT_MINUS; if ((unsigned)v == HIGHBITI) { // avoid overflow v = (int)(HIGHBITI / (unsigned)base); *--sp = _Sfdigits[HIGHBITI - (unsigned)v * (unsigned)base]; } else v = -v; } if (n_s < 0) { /* base 10 */ sfucvt(v, sp, n, ssp, int, unsigned); } else if (n_s > 0) { /* base power-of-2 */ do { *--sp = ssp[v & n_s]; } while ((v = (unsigned)v >> n)); } else { /* n_s == 0, general base */ do { *--sp = ssp[(unsigned)v % (unsigned)base]; } while ((v = (unsigned)v / (unsigned)base)); } } if (n_s < 0 && (flags & SFFMT_THOUSAND) && (n = endsp - sp) > 3) { if ((n %= 3) == 0) n = 3; for (ep = buf + SLACK, endep = ep + n;;) { while (ep < endep) *ep++ = *sp++; if (sp == endsp) break; if (sp <= endsp - 3) *ep++ = thousand; endep = ep + 3; } sp = buf + SLACK; endsp = ep; } /* zero padding for precision if have room in buffer */ if (precis > 0 && (precis -= (endsp - sp)) < (sp - buf) - 64) while (precis-- > 0) *--sp = '0'; if (flags & SFFMT_ALTER) { /* prefix */ if (fmt == 'o') { if (*sp != '0') *--sp = '0'; } else { if (width > 0 && (flags & SFFMT_ZERO)) { /* do 0 padding first */ if (fmt == 'x' || fmt == 'X') n = 0; else if (dot < 2) n = width; else n = base < 10 ? 2 : 3; n += (flags & (SFFMT_MINUS | SFFMT_SIGN)) ? 1 : 0; n = width - (n + (endsp - sp)); while (n-- > 0) *--sp = '0'; } if (fmt == 'x' || fmt == 'X') { *--sp = (char) fmt; *--sp = '0'; } else if (dot >= 2) { /* base#value notation */ *--sp = '#'; if (base < 10) *--sp = (char) ('0' + base); else { *--sp = _Sfdec[(base <<= 1) + 1]; *--sp = _Sfdec[base]; } } } } break; case 'g': case 'G': /* these ultimately become %e or %f */ case 'e': case 'E': case 'f': if (!(ft->flags & SFFMT_VALUE) || FMTCMP(size, double, long double)) dval = argv.d; else dval = (double) argv.f; if (fmt == 'e' || fmt == 'E') { n = (precis = precis < 0 ? FPRECIS : precis) + 1; { ep = _sfcvt(&dval, imin(n, SF_FDIGITS), &decpt, &sign, SFFMT_EFORMAT); } goto e_format; } else if (fmt == 'f' || fmt == 'F') { precis = precis < 0 ? FPRECIS : precis; { ep = _sfcvt(&dval, imin(precis, SF_FDIGITS), &decpt, &sign, 0); } goto f_format; } /* 'g' or 'G' format */ precis = precis < 0 ? FPRECIS : precis == 0 ? 1 : precis; { ep = _sfcvt(&dval, imin(precis, SF_FDIGITS), &decpt, &sign, SFFMT_EFORMAT); if (dval == 0.) decpt = 1; else if (*ep == 'I') goto infinite; } if (!(flags & SFFMT_ALTER)) { /* zap trailing 0s */ if ((n = sfslen()) > precis) n = precis; while ((n -= 1) >= 1 && ep[n] == '0'); n += 1; } else n = precis; if (decpt < -3 || decpt > precis) { precis = n - 1; goto e_format; } else { precis = n - decpt; goto f_format; } e_format: /* build the x.yyyy string */ if (gv_isalpha(*ep)) goto infinite; sp = endsp = buf + 1; /* reserve space for sign */ *endsp++ = *ep ? *ep++ : '0'; SFSETLOCALE(decimal, thousand); if (precis > 0 || (flags & SFFMT_ALTER)) *endsp++ = decimal; ssp = endsp; endep = ep + precis; while ((*endsp++ = *ep++) && ep <= endep); precis -= (endsp -= 1) - ssp; /* build the exponent */ ep = endep = buf + (sizeof(buf) - 1); if (dval != 0.) { if ((n = decpt - 1) < 0) n = -n; while (n > 9) { v = n; n /= 10; *--ep = (char) ('0' + (v - n * 10)); } } else n = 0; *--ep = (char) ('0' + n); if (endep - ep <= 1) /* at least 2 digits */ *--ep = '0'; /* the e/Exponent separator and sign */ *--ep = (decpt > 0 || dval == 0.) ? '+' : '-'; *--ep = gv_isupper(fmt) ? 'E' : 'e'; goto end_efg; f_format: /* data before the decimal point */ if (gv_isalpha(*ep)) { infinite: endsp = (sp = ep) + sfslen(); ep = endep; precis = 0; goto end_efg; } SFSETLOCALE(decimal, thousand); endsp = sp = buf + 1; /* save a space for sign */ endep = ep + decpt; if (decpt > 3 && (flags & SFFMT_THOUSAND)) { if ((n = decpt % 3) == 0) n = 3; while (ep < endep && (*endsp++ = *ep++)) { if (--n == 0 && (ep <= endep - 3)) { *endsp++ = thousand; n = 3; } } } else { while (ep < endep && (*endsp++ = *ep++)); } if (endsp == sp) *endsp++ = '0'; if (precis > 0 || (flags & SFFMT_ALTER)) *endsp++ = decimal; if ((n = -decpt) > 0) { /* output zeros for negative exponent */ ssp = endsp + imin(n, precis); precis -= n; while (endsp < ssp) *endsp++ = '0'; } ssp = endsp; endep = ep + precis; while ((*endsp++ = *ep++) && ep <= endep); precis -= (endsp -= 1) - ssp; ep = endep; end_efg: flags |= SFFMT_FLOAT; if (sign) flags |= SFFMT_MINUS; break; } if (flags == 0 && width <= 0) goto do_output; if (flags & SFFMT_PREFIX) fmt = (flags & SFFMT_MINUS) ? '-' : (flags & SFFMT_SIGN) ? '+' : ' '; n = (endsp - sp) + (endep - ep) + (precis <= 0 ? 0 : precis) + ((flags & SFFMT_PREFIX) ? 1 : 0); if ((v = width - n) <= 0) v = 0; else if (!(flags & SFFMT_ZERO)) { /* right padding */ if (flags & SFFMT_LEFT) v = -v; else if (flags & SFFMT_PREFIX) { /* blank padding, output prefix now */ *--sp = fmt; flags &= ~SFFMT_PREFIX; } } if (flags & SFFMT_PREFIX) { /* put out the prefix */ SFputc(f, fmt); if (fmt != ' ') flags |= SFFMT_ZERO; } if ((n = v) > 0) { /* left padding */ v = (flags & SFFMT_ZERO) ? '0' : ' '; SFnputc(f, v, n); } if ((n = precis) > 0 && !(flags & SFFMT_FLOAT)) { /* padding for integer precision */ SFnputc(f, '0', n); precis = 0; } do_output: if ((n = endsp - sp) > 0) SFwrite(f, sp, n); if (flags & (SFFMT_FLOAT | SFFMT_LEFT)) { /* SFFMT_FLOAT: right padding for float precision */ if ((n = precis) > 0) SFnputc(f, '0', n); /* SFFMT_FLOAT: the exponent of %eE */ if ((n = endep - (sp = ep)) > 0) SFwrite(f, sp, n); /* SFFMT_LEFT: right padding */ if ((n = -v) > 0) SFnputc(f, ' ', n); } } done: return n_output; }