Imported from ../bash-2.05.tar.gz.
[platform/upstream/bash.git] / examples / loadables / sprintf.c
1 /*
2  * Copyright (c) 1989, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #if !defined(BUILTIN) && !defined(SHELL)
35 #ifndef lint
36 static char copyright[] =
37 "@(#) Copyright (c) 1989, 1993\n\
38         The Regents of the University of California.  All rights reserved.\n";
39 #endif /* not lint */
40 #endif
41
42 #ifndef lint
43 static char sccsid[] = "@(#)printf.c    8.1 (Berkeley) 7/20/93";
44 #endif /* not lint */
45
46 #include <sys/types.h>
47
48 #include <errno.h>
49 #include <limits.h>
50 #include <stdio.h>
51
52 #include "bashansi.h"
53 #include "shell.h"
54 #include "builtins.h"
55 #include "stdc.h"
56
57 #if !defined (errno)
58 extern int errno;
59 #endif
60
61 static char     sbuf[1024];
62 static int      sblen;
63
64 /* Gee, I wish sprintf could be reliably counted upon to return the
65    number of characters written :-( */
66 #define PF(f, func) \
67 do { \
68         if (fieldwidth) \
69                 if (precision) \
70                         sprintf(sbuf, f, fieldwidth, precision, func); \
71                 else \
72                         sprintf(sbuf, f, fieldwidth, func); \
73         else if (precision) \
74                 sprintf(sbuf, f, precision, func); \
75         else \
76                 sprintf(sbuf, f, func); \
77         spaddstr (sbuf, strlen (sbuf)); \
78 } while (0)
79
80 static int       asciicode __P((void));
81 static void      escape __P((char *));
82 static int       getchr __P((void));
83 static double    getdouble __P((void));
84 static int       getint __P((int *));
85 static int       getlong __P((long *));
86 static char     *getstr __P((void));
87 static char     *mklong __P((char *, int));
88 static void      usage __P((void));
89
90 static char **gargv;
91
92 static char *outstr;
93 static int outsize;
94 static int outind;
95
96 int sprintf_builtin ();
97 static int sprintf_main ();
98 static void spaddstr ();
99
100 extern char *this_command_name;
101 extern char *single_quote ();
102 extern char **make_builtin_argv ();
103
104 static char *sprintf_doc[] = {
105   "sprintf formats and outputs its arguments, after the second, under control",
106   "of the format and assigns the result to the variable named by its first",
107   "argument. The format is a character string which contains three types",
108   "of objects: plain characters, which are simply copied to the output string,",
109   "character escape sequences which are converted and copied to the output",
110   "string, and format specifications, each of which causes printing of the",
111   "next successive argument.  In addition to the standard sprintf(3) formats,",
112   "%b means to expand escapes in the corresponding argument, and %q means",
113   "to quote the argument in a way that can be reused as shell input.  Each",
114   "one of the format specifications must not expand to more than 1024",
115   "characters, though there is no limit on the total size of the output",
116   "string.",
117   (char *)NULL
118 };
119
120 struct builtin sprintf_struct = {
121         "sprintf",
122         sprintf_builtin,
123         BUILTIN_ENABLED,
124         sprintf_doc,
125         "sprintf var format [arguments]",
126         (char *)0
127 };
128
129 int
130 sprintf_builtin (list)
131      WORD_LIST *list;
132 {
133   int c, r;
134   char **v, *varname;
135   WORD_LIST *l;
136   SHELL_VAR *var;
137
138   if (list == 0)
139     {
140       builtin_usage ();
141       return (EXECUTION_FAILURE);
142     }
143
144   varname = list->word->word;
145   list = list->next;
146
147   if (legal_identifier (varname) == 0)
148     {
149       builtin_error ("%s: not a legal variable name", varname);
150       return (EXECUTION_FAILURE);
151     }
152
153   outind = 0;
154   if (outstr == 0)
155     outstr = xmalloc (outsize = 64);
156   outstr[0] = '\0';
157
158   v = make_builtin_argv (list, &c);
159   r = sprintf_main (c, v);
160   free (v);
161
162   var = bind_variable (varname, outstr);
163   if (readonly_p (var))
164     {
165       builtin_error ("%s: readonly variable", varname);
166       return (EXECUTION_FAILURE);
167     }
168
169   return r;
170 }
171
172 static void
173 spaddstr(str, len)
174      char *str;
175      int len;
176 {
177   RESIZE_MALLOCED_BUFFER (outstr, outind, len, outsize, 64);
178   strcpy (outstr + outind, str);
179   outind += len;
180 }
181
182 static int
183 sprintf_main(argc, argv)
184         int argc;
185         char *argv[];
186 {
187         extern int optind;
188         static char *skip1, *skip2;
189         int ch, end, fieldwidth, precision;
190         char convch, nextch, *format, *fmt, *start;
191
192         while ((ch = getopt(argc, argv, "")) != EOF)
193                 switch (ch) {
194                 case '?':
195                 default:
196                         usage();
197                         return (1);
198                 }
199         argc -= optind;
200         argv += optind;
201
202         if (argc < 1) {
203                 usage();
204                 return (1);
205         }
206
207         /*
208          * Basic algorithm is to scan the format string for conversion
209          * specifications -- once one is found, find out if the field
210          * width or precision is a '*'; if it is, gather up value.  Note,
211          * format strings are reused as necessary to use up the provided
212          * arguments, arguments of zero/null string are provided to use
213          * up the format string.
214          */
215         skip1 = "#-+ 0";
216         skip2 = "*0123456789";
217
218         escape(fmt = format = *argv);           /* backslash interpretation */
219         gargv = ++argv;
220         for (;;) {
221                 end = 0;
222                 /* find next format specification */
223 next:           for (start = fmt;; ++fmt) {
224                         if (!*fmt) {
225                                 /* avoid infinite loop */
226                                 if (end == 1) {
227                                         warnx("missing format character",
228                                             NULL, NULL);
229                                         return (1);
230                                 }
231                                 end = 1;
232                                 if (fmt > start)
233                                         (void)printf("%s", start);
234                                 if (!*gargv)
235                                         return (0);
236                                 fmt = format;
237                                 goto next;
238                         }
239                         /* %% prints a % */
240                         if (*fmt == '%') {
241                                 if (*++fmt != '%')
242                                         break;
243                                 *fmt++ = '\0';
244                                 (void)printf("%s", start);
245                                 goto next;
246                         }
247                 }
248
249                 /* skip to field width */
250                 for (; strchr(skip1, *fmt); ++fmt);
251                 if (*fmt == '*') {
252                         if (getint(&fieldwidth))
253                                 return (1);
254                 } else
255                         fieldwidth = 0;
256
257                 /* skip to possible '.', get following precision */
258                 for (; strchr(skip2, *fmt); ++fmt);
259                 if (*fmt == '.')
260                         ++fmt;
261                 if (*fmt == '*') {
262                         if (getint(&precision))
263                                 return (1);
264                 } else
265                         precision = 0;
266
267                 /* skip to conversion char */
268                 for (; strchr(skip2, *fmt); ++fmt);
269                 if (!*fmt) {
270                         warnx("missing format character", NULL, NULL);
271                         return (1);
272                 }
273
274                 convch = *fmt;
275                 nextch = *++fmt;
276                 *fmt = '\0';
277                 switch(convch) {
278                 case 'c': {
279                         char p;
280
281                         p = getchr();
282                         PF(start, p);
283                         break;
284                 }
285                 case 's': {
286                         char *p;
287
288                         p = getstr();
289                         PF(start, p);
290                         break;
291                 }
292                 case 'b': {             /* expand escapes in argument */
293                         char *p;
294
295                         p = getstr();
296                         escape(p);
297                         PF("%s", p);
298                         break;
299                 }
300                 case 'q': {             /* print with shell single quoting */
301                         char *p, *p2;
302
303                         p = getstr();
304                         p2 = single_quote(p);
305                         PF("%s", p2);
306                         free(p2);
307                         break;
308                 }
309                 case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': {
310                         long p;
311                         char *f;
312                         
313                         if ((f = mklong(start, convch)) == NULL)
314                                 return (1);
315                         if (getlong(&p))
316                                 return (1);
317                         PF(f, p);
318                         break;
319                 }
320                 case 'e': case 'E': case 'f': case 'g': case 'G': {
321                         double p;
322
323                         p = getdouble();
324                         PF(start, p);
325                         break;
326                 }
327                 default:
328                         warnx("illegal format character", NULL, NULL);
329                         return (1);
330                 }
331                 *fmt = nextch;
332         }
333         /* NOTREACHED */
334 }
335
336 static char *
337 mklong(str, ch)
338         char *str;
339         int ch;
340 {
341         static char copy[64];
342         int len;
343
344         len = strlen(str) + 2;
345         memmove(copy, str, len - 3);
346         copy[len - 3] = 'l';
347         copy[len - 2] = ch;
348         copy[len - 1] = '\0';
349         return (copy);
350 }
351
352 static void
353 escape(fmt)
354         register char *fmt;
355 {
356         register char *store;
357         register int value, c;
358
359         for (store = fmt; c = *fmt; ++fmt, ++store) {
360                 if (c != '\\') {
361                         *store = c;
362                         continue;
363                 }
364                 switch (*++fmt) {
365                 case '\0':              /* EOS, user error */
366                         *store = '\\';
367                         *++store = '\0';
368                         return;
369                 case '\\':              /* backslash */
370                 case '\'':              /* single quote */
371                         *store = *fmt;
372                         break;
373                 case 'a':               /* bell/alert */
374                         *store = '\7';
375                         break;
376                 case 'b':               /* backspace */
377                         *store = '\b';
378                         break;
379                 case 'c':
380                         return;
381                 case 'e':
382                 case 'E':
383                         *store = '\033';
384                         break;
385                 case 'f':               /* form-feed */
386                         *store = '\f';
387                         break;
388                 case 'n':               /* newline */
389                         *store = '\n';
390                         break;
391                 case 'r':               /* carriage-return */
392                         *store = '\r';
393                         break;
394                 case 't':               /* horizontal tab */
395                         *store = '\t';
396                         break;
397                 case 'v':               /* vertical tab */
398                         *store = '\13';
399                         break;
400                                         /* octal constant */
401                 case '0': case '1': case '2': case '3':
402                 case '4': case '5': case '6': case '7':
403                         for (c = 3, value = 0;
404                             c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) {
405                                 value <<= 3;
406                                 value += *fmt - '0';
407                         }
408                         --fmt;
409                         *store = value;
410                         break;
411                 default:
412                         *store = *fmt;
413                         break;
414                 }
415         }
416         *store = '\0';
417 }
418
419 static int
420 getchr()
421 {
422         if (!*gargv)
423                 return ('\0');
424         return ((int)**gargv++);
425 }
426
427 static char *
428 getstr()
429 {
430         if (!*gargv)
431                 return ("");
432         return (*gargv++);
433 }
434
435 static char *Number = "+-.0123456789";
436 static int
437 getint(ip)
438         int *ip;
439 {
440         long val;
441
442         if (getlong(&val))
443                 return (1);
444         if (val > INT_MAX) {
445                 warnx("%s: %s", *gargv, strerror(ERANGE));
446                 return (1);
447         }
448         *ip = val;
449         return (0);
450 }
451
452 static int
453 getlong(lp)
454         long *lp;
455 {
456         long val;
457         char *ep;
458
459         if (!*gargv) {
460                 *lp = 0;
461                 return (0);
462         }
463         if (strchr(Number, **gargv)) {
464                 errno = 0;
465                 val = strtol(*gargv, &ep, 0);
466                 if (*ep != '\0') {
467                         warnx("%s: illegal number", *gargv, NULL);
468                         return (1);
469                 }
470                 if (errno == ERANGE)
471                         if (val == LONG_MAX) {
472                                 warnx("%s: %s", *gargv, strerror(ERANGE));
473                                 return (1);
474                         }
475                         if (val == LONG_MIN) {
476                                 warnx("%s: %s", *gargv, strerror(ERANGE));
477                                 return (1);
478                         }
479                         
480                 *lp = val;
481                 ++gargv;
482                 return (0);
483         }
484         *lp =  (long)asciicode();
485         return (0);
486 }
487
488 static double
489 getdouble()
490 {
491         if (!*gargv)
492                 return ((double)0);
493         if (strchr(Number, **gargv))
494                 return (atof(*gargv++));
495         return ((double)asciicode());
496 }
497
498 static int
499 asciicode()
500 {
501         register int ch;
502
503         ch = **gargv;
504         if (ch == '\'' || ch == '"')
505                 ch = (*gargv)[1];
506         ++gargv;
507         return (ch);
508 }
509
510 static void
511 usage()
512 {
513         (void)fprintf(stderr, "usage: printf format [arg ...]\n");
514 }