From 62829b856d9c6b054f3c6338a20f9d5c04d79d42 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 18 Oct 2001 14:22:21 +0000 Subject: allow arguments to be specified in printf format specifications (16080) --- ChangeLog | 5 ++ Doc/Zsh/builtins.yo | 13 +++-- Src/builtin.c | 134 +++++++++++++++++++++++++++++++++++++--------------- Test/B03print.ztst | 58 ++++++++++++++++++++++- 4 files changed, 168 insertions(+), 42 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0238ee0ba..4b9012a8f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2001-10-18 Oliver Kiddle + + * 16080: Src/builtin.c, Doc/Zsh/builtins.yo, Test/B03print.ztst: + allow arguments to be specified in printf format specifications + 2001-10-17 Clint Adams * 16078: Completion/Unix/Command/_zip: diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 91df46c06..ef82f851e 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -725,9 +725,9 @@ findex(printf) item(tt(printf) var(format) [ var(arg) ... ])( Print the arguments according to the format specification. Formatting rules are the same as used in C. The same escape sequences as for tt(echo) -are recognised in the format. All C format specifications ending in one of -csdiouxXeEfgGn are handled. In addition to this, `tt(%b)' can be used -instead of `tt(%s)' to cause escape sequences in the argument to be +are recognised in the format. All C conversion specifications ending in +one of csdiouxXeEfgGn are handled. In addition to this, `tt(%b)' can be +used instead of `tt(%s)' to cause escape sequences in the argument to be recognised and `tt(%q)' can be used to quote the argument in such a way that allows it to be reused as shell input. With the numeric format specifiers, if the corresponding argument starts with a quote character, @@ -737,6 +737,13 @@ noderef(Arithmetic Evaluation) for a description of arithmetic expressions. With `tt(%n)', the corresponding argument is taken as an identifier which is created as an integer parameter. +Normally, conversion specifications are applied to each argument in order +but they can explicitly specify the var(n)th argument is to be used by +replacing `tt(%)' by `tt(%)var(n)tt($)' and `tt(*)' by `tt(*)var(n)tt($)'. +It is recommended that you do not mix references of this explicit style +with the normal style and the handling of such mixed styles may be subject +to future change. + If arguments remain unused after formatting, the format string is reused until all arguments have been consumed. If more arguments are required by the format than have been specified, the behaviour is as if zero or an diff --git a/Src/builtin.c b/Src/builtin.c index 113c5931e..038e1a199 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2892,10 +2892,11 @@ mod_export LinkList bufstack; int bin_print(char *name, char **args, char *ops, int func) { - int flen, width, prec, type, argc, n, nnl = 0, ret = 0; + int flen, width, prec, type, argc, n, narg; + int nnl = 0, ret = 0, maxarg = 0; int flags[5], *len; char *start, *endptr, *c, *d, *flag, spec[11], *fmt = NULL; - char **first, *flagch = "0+- #", save, nullstr = '\0'; + char **first, *curarg, *flagch = "0+- #", save, nullstr = '\0'; zlong count = 0; FILE *fout = stdout; @@ -3095,6 +3096,11 @@ bin_print(char *name, char **args, char *ops, int func) /* printf style output */ *spec='%'; do { + if (maxarg) { + first += maxarg; + argc -= maxarg; + maxarg = 0; + } for (c = fmt;c-fmt < flen;c++) { if (*c != '%') { putc(*c, fout); @@ -3111,11 +3117,29 @@ bin_print(char *name, char **args, char *ops, int func) type = prec = -1; width = 0; + curarg = NULL; d = spec + 1; + if (*c >= '1' && *c <= '9') { + narg = strtoul(c, &endptr, 0); + if (*endptr == '$') { + c = endptr + 1; + DPUTS(narg <= 0, "specified zero or negative arg"); + if (narg > argc) { + zwarnnam(name, "%d: argument specifier out of range", + 0, narg); + return 1; + } else { + if (narg > maxarg) maxarg = narg; + curarg = *(first + narg - 1); + } + } + } + + /* copy only one of each flag as spec has finite size */ memset(flags, 0, sizeof(flags)); - while (flag = strchr(flagch, *c)) { + while ((flag = strchr(flagch, *c))) { if (!flags[flag - flagch]) { flags[flag - flagch] = 1; *d++ = *c; @@ -3123,28 +3147,60 @@ bin_print(char *name, char **args, char *ops, int func) c++; } - if (*c == '*') { - if (*args) width = (int)mathevali(*args++); - if (errflag) { - errflag = 0; - ret = 1; - } - c++; - } else if (idigit(*c)) { + if (idigit(*c)) { width = strtoul(c, &endptr, 0); c = endptr; + } else if (*c == '*') { + if (idigit(*++c)) { + narg = strtoul(c, &endptr, 0); + if (*endptr == '$') { + c = endptr + 1; + if (narg > argc || narg <= 0) { + zwarnnam(name, + "%d: argument specifier out of range", + 0, narg); + return 1; + } else { + if (narg > maxarg) maxarg = narg; + args = first + narg - 1; + } + } + } + if (*args) { + width = (int)mathevali(*args++); + if (errflag) { + errflag = 0; + ret = 1; + } + } } *d++ = '*'; if (*c == '.') { - c++; - if (*c == '*') { - prec = (*args) ? (int)mathevali(*args++) : 0; - if (errflag) { - errflag = 0; - ret = 1; + if (*++c == '*') { + if (idigit(*++c)) { + narg = strtoul(c, &endptr, 0); + if (*endptr == '$') { + c = endptr + 1; + if (narg > argc || narg <= 0) { + zwarnnam(name, + "%d: argument specifier out of range", + 0, narg); + return 1; + } else { + if (narg > maxarg) maxarg = narg; + args = first + narg - 1; + } + } + } + + if (*args) { + prec = (int)mathevali(*args++); + if (errflag) { + errflag = 0; + ret = 1; + } } - c++; } else if (idigit(*c)) { prec = strtoul(c, &endptr, 0); c = endptr; @@ -3155,30 +3211,30 @@ bin_print(char *name, char **args, char *ops, int func) /* ignore any size modifier */ if (*c == 'l' || *c == 'L' || *c == 'h') c++; + if (!curarg && *args) curarg = *args++; d[1] = '\0'; switch (*d = *c) { case 'c': - if (*args) { - intval = **args; - args++; + if (curarg) { + intval = *curarg; } else intval = 0; print_val(intval); break; case 's': - stringval = *args ? *args++ : &nullstr; + stringval = curarg ? curarg : &nullstr; print_val(stringval); break; case 'b': - if (*args) { + if (curarg) { int l; - char *b = getkeystring(*args++, &l, ops['b'] ? 2 : 0, &nnl); + char *b = getkeystring(curarg, &l, ops['b'] ? 2 : 0, &nnl); fwrite(b, l, 1, fout); count += l; } break; case 'q': - stringval = *args ? bslashquote(*args++, NULL, 0) : &nullstr; + stringval = curarg ? bslashquote(curarg, NULL, 0) : &nullstr; *d = 's'; print_val(stringval); break; @@ -3200,7 +3256,7 @@ bin_print(char *name, char **args, char *ops, int func) type=3; break; case 'n': - if (*args) setiparam(*args++, count); + if (curarg) setiparam(curarg, count); break; default: if (*c) { @@ -3208,20 +3264,21 @@ bin_print(char *name, char **args, char *ops, int func) c[1] = '\0'; } zwarnnam(name, "%s: invalid directive", start, 0); - ret = 1; if (*c) c[1] = save; + if (fout != stdout) + fclose(fout); + return 1; } if (type > 0) { - if (*args && (**args == '\'' || **args == '"' )) { + if (curarg && (*curarg == '\'' || *curarg == '"' )) { if (type == 2) { - doubleval = (unsigned char)(*args)[1]; + doubleval = (unsigned char)curarg[1]; print_val(doubleval); } else { - intval = (unsigned char)(*args)[1]; + intval = (unsigned char)curarg[1]; print_val(intval); } - args++; } else { switch (type) { case 1: @@ -3229,7 +3286,7 @@ bin_print(char *name, char **args, char *ops, int func) *d++ = 'l'; #endif *d++ = 'l', *d++ = *c, *d = '\0'; - zlongval = (*args) ? mathevali(*args++) : 0; + zlongval = (curarg) ? mathevali(curarg) : 0; if (errflag) { zlongval = 0; errflag = 0; @@ -3238,8 +3295,8 @@ bin_print(char *name, char **args, char *ops, int func) print_val(zlongval) break; case 2: - if (*args) { - mnumval = matheval(*args++); + if (curarg) { + mnumval = matheval(curarg); doubleval = (mnumval.type & MN_FLOAT) ? mnumval.u.d : (double)mnumval.u.l; } else doubleval = 0; @@ -3255,9 +3312,9 @@ bin_print(char *name, char **args, char *ops, int func) *d++ = 'l'; #endif *d++ = 'l', *d++ = *c, *d = '\0'; - zulongval = (*args) ? mathevali(*args++) : 0; + zulongval = (curarg) ? mathevali(curarg) : 0; if (errflag) { - doubleval = 0; + zulongval = 0; errflag = 0; ret = 1; } @@ -3265,10 +3322,13 @@ bin_print(char *name, char **args, char *ops, int func) } } } + if (maxarg && (args - first > maxarg)) + maxarg = args - first; } + if (maxarg) args = first + maxarg; /* if there are remaining args, reuse format string */ - } while (*args && args != first); + } while (*args && args != first && !ops['r']); if (fout != stdout) fclose(fout); diff --git a/Test/B03print.ztst b/Test/B03print.ztst index 56c202ef4..3968faec3 100644 --- a/Test/B03print.ztst +++ b/Test/B03print.ztst @@ -50,7 +50,9 @@ 0:test b format specifier > \ -# test %q here - it doesn't quite work yet + printf '%q\n' '=a=b \ c!' +0: test q format specifier +>\=a=b\ \\\ c! printf '%c\n' char 0:test c format specifier @@ -108,6 +110,19 @@ ?(eval):1: bad math expression: operator expected at `a' >0 + printf '%12$s' 1 2 3 +1:out of range argument specifier +?(eval):printf:1: 12: argument specifier out of range + + printf '%2$s\n' 1 2 3 +1:out of range argument specifier on format reuse +?(eval):printf:1: 2: argument specifier out of range +>2 + + printf '%*0$d' +1:out of range argument specifier on width +?(eval):printf:1: 0: argument specifier out of range + print -m -f 'format - %s.\n' 'z' a b c 0:format not printed if no arguments left after -m removal @@ -129,7 +144,8 @@ >two b:0x2% >three c:0x3% - printf '%0+- #-08.5dx\n' 123 +# this should fill spec string with '%0+- #*.*d\0' - 11 characters + printf '%1$0+- #-08.5dx\n' 123 0:maximal length format specification >+00123 x @@ -140,3 +156,41 @@ printf '%.*g\n' -1 .1 0:negative precision specified >0.1 + + printf '%2$s %1$d\n' 1 2 +0:specify argument to output explicitly +>2 1 + + printf '%3$.*1$d\n' 4 0 3 +0:specify output and precision arguments explicitly +>0003 + + printf '%2$d%1$d\n' 1 2 3 4 +0:reuse format where arguments are explictly specified +>21 +>43 + + printf '%1$*2$d' 1 2 3 4 5 6 7 8 9 10;echo +0:reuse of specified arguments +> 1 3 5 7 9 + + printf '%1$0+.3d\n' 3 +0:flags mixed with specified argument +>+003 + +# The following usage, as stated in the manual, is not recommended and the +# results are undefined. Tests are here anyway to ensure some form of +# half-sane behaviour. + + printf '%2$s %s %3$s\n' Morning Good World +0:mixed style of argument selection +>Good Morning World + + printf '%*1$.*d\n' 1 2 +0:argument specified for width only +>00 + + print -f '%*.*1$d\n' 1 2 3 +0:argument specified for precision only +>2 +>000 -- cgit 1.4.1