about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Zsh/builtins.yo13
-rw-r--r--Src/builtin.c134
-rw-r--r--Test/B03print.ztst58
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  <opk@zsh.org>
+
+	* 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  <clint@zsh.org>
 
 	* 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