Annotation of src/usr.bin/m4/eval.c, Revision 1.15
1.15 ! espie 1: /* $OpenBSD: eval.c,v 1.14 1999/09/06 13:24:59 espie Exp $ */
1.7 millert 2: /* $NetBSD: eval.c,v 1.7 1996/11/10 21:21:29 pk Exp $ */
1.1 deraadt 3:
4: /*
5: * Copyright (c) 1989, 1993
6: * The Regents of the University of California. All rights reserved.
7: *
8: * This code is derived from software contributed to Berkeley by
9: * Ozan Yigit at York University.
10: *
11: * Redistribution and use in source and binary forms, with or without
12: * modification, are permitted provided that the following conditions
13: * are met:
14: * 1. Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * 2. Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: * 3. All advertising materials mentioning features or use of this software
20: * must display the following acknowledgement:
21: * This product includes software developed by the University of
22: * California, Berkeley and its contributors.
23: * 4. Neither the name of the University nor the names of its contributors
24: * may be used to endorse or promote products derived from this software
25: * without specific prior written permission.
26: *
27: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37: * SUCH DAMAGE.
38: */
39:
40: #ifndef lint
41: #if 0
42: static char sccsid[] = "@(#)eval.c 8.2 (Berkeley) 4/27/95";
43: #else
1.15 ! espie 44: static char rcsid[] = "$OpenBSD: eval.c,v 1.14 1999/09/06 13:24:59 espie Exp $";
1.1 deraadt 45: #endif
46: #endif /* not lint */
47:
48: /*
49: * eval.c
50: * Facility: m4 macro processor
51: * by: oz
52: */
53:
54: #include <sys/types.h>
55: #include <errno.h>
56: #include <unistd.h>
57: #include <stdio.h>
58: #include <stdlib.h>
1.13 espie 59: #include <stddef.h>
1.1 deraadt 60: #include <string.h>
1.6 millert 61: #include <fcntl.h>
1.12 espie 62: #include <err.h>
1.1 deraadt 63: #include "mdef.h"
64: #include "stdd.h"
65: #include "extern.h"
66: #include "pathnames.h"
67:
68: /*
69: * eval - evaluate built-in macros.
70: * argc - number of elements in argv.
71: * argv - element vector :
72: * argv[0] = definition of a user
73: * macro or nil if built-in.
74: * argv[1] = name of the macro or
75: * built-in.
76: * argv[2] = parameters to user-defined
77: * . macro or built-in.
78: * .
79: *
80: * Note that the minimum value for argc is 3. A call in the form
81: * of macro-or-builtin() will result in:
82: * argv[0] = nullstr
83: * argv[1] = macro-or-builtin
84: * argv[2] = nullstr
85: */
86:
87: void
88: eval(argv, argc, td)
89: register char *argv[];
90: register int argc;
91: register int td;
92: {
93: register int c, n;
94: static int sysval = 0;
95:
96: #ifdef DEBUG
97: printf("argc = %d\n", argc);
98: for (n = 0; n < argc; n++)
99: printf("argv[%d] = %s\n", n, argv[n]);
100: #endif
101: /*
102: * if argc == 3 and argv[2] is null, then we
103: * have macro-or-builtin() type call. We adjust
104: * argc to avoid further checking..
105: */
106: if (argc == 3 && !*(argv[2]))
107: argc--;
108:
109: switch (td & ~STATIC) {
110:
111: case DEFITYPE:
112: if (argc > 2)
113: dodefine(argv[2], (argc > 3) ? argv[3] : null);
114: break;
115:
116: case PUSDTYPE:
117: if (argc > 2)
118: dopushdef(argv[2], (argc > 3) ? argv[3] : null);
119: break;
120:
121: case DUMPTYPE:
122: dodump(argv, argc);
123: break;
124:
125: case EXPRTYPE:
126: /*
127: * doexpr - evaluate arithmetic
128: * expression
129: */
130: if (argc > 2)
131: pbnum(expr(argv[2]));
132: break;
133:
134: case IFELTYPE:
135: if (argc > 4)
136: doifelse(argv, argc);
137: break;
138:
139: case IFDFTYPE:
140: /*
141: * doifdef - select one of two
142: * alternatives based on the existence of
143: * another definition
144: */
145: if (argc > 3) {
146: if (lookup(argv[2]) != nil)
147: pbstr(argv[3]);
148: else if (argc > 4)
149: pbstr(argv[4]);
150: }
151: break;
152:
153: case LENGTYPE:
154: /*
155: * dolen - find the length of the
156: * argument
157: */
158: if (argc > 2)
159: pbnum((argc > 2) ? strlen(argv[2]) : 0);
160: break;
161:
162: case INCRTYPE:
163: /*
164: * doincr - increment the value of the
165: * argument
166: */
167: if (argc > 2)
168: pbnum(atoi(argv[2]) + 1);
169: break;
170:
171: case DECRTYPE:
172: /*
173: * dodecr - decrement the value of the
174: * argument
175: */
176: if (argc > 2)
177: pbnum(atoi(argv[2]) - 1);
178: break;
179:
180: case SYSCTYPE:
181: /*
182: * dosys - execute system command
183: */
184: if (argc > 2)
185: sysval = system(argv[2]);
186: break;
187:
188: case SYSVTYPE:
189: /*
190: * dosysval - return value of the last
191: * system call.
192: *
193: */
194: pbnum(sysval);
195: break;
196:
197: case INCLTYPE:
198: if (argc > 2)
199: if (!doincl(argv[2]))
1.12 espie 200: err(1, "%s", argv[2]);
1.1 deraadt 201: break;
202:
203: case SINCTYPE:
204: if (argc > 2)
205: (void) doincl(argv[2]);
206: break;
207: #ifdef EXTENDED
208: case PASTTYPE:
209: if (argc > 2)
210: if (!dopaste(argv[2]))
1.12 espie 211: err(1, "%s", argv[2]);
1.1 deraadt 212: break;
213:
214: case SPASTYPE:
215: if (argc > 2)
216: (void) dopaste(argv[2]);
217: break;
218: #endif
219: case CHNQTYPE:
220: dochq(argv, argc);
221: break;
222:
223: case CHNCTYPE:
224: dochc(argv, argc);
225: break;
226:
227: case SUBSTYPE:
228: /*
229: * dosub - select substring
230: *
231: */
232: if (argc > 3)
233: dosub(argv, argc);
234: break;
235:
236: case SHIFTYPE:
237: /*
238: * doshift - push back all arguments
239: * except the first one (i.e. skip
240: * argv[2])
241: */
242: if (argc > 3) {
243: for (n = argc - 1; n > 3; n--) {
1.10 deraadt 244: pbstr(rquote);
1.1 deraadt 245: pbstr(argv[n]);
1.10 deraadt 246: pbstr(lquote);
1.7 millert 247: putback(COMMA);
1.1 deraadt 248: }
1.10 deraadt 249: pbstr(rquote);
1.1 deraadt 250: pbstr(argv[3]);
1.10 deraadt 251: pbstr(lquote);
1.1 deraadt 252: }
253: break;
254:
255: case DIVRTYPE:
256: if (argc > 2 && (n = atoi(argv[2])) != 0)
257: dodiv(n);
258: else {
259: active = stdout;
260: oindex = 0;
261: }
262: break;
263:
264: case UNDVTYPE:
265: doundiv(argv, argc);
266: break;
267:
268: case DIVNTYPE:
269: /*
270: * dodivnum - return the number of
271: * current output diversion
272: */
273: pbnum(oindex);
274: break;
275:
276: case UNDFTYPE:
277: /*
278: * doundefine - undefine a previously
279: * defined macro(s) or m4 keyword(s).
280: */
281: if (argc > 2)
282: for (n = 2; n < argc; n++)
283: remhash(argv[n], ALL);
284: break;
285:
286: case POPDTYPE:
287: /*
288: * dopopdef - remove the topmost
289: * definitions of macro(s) or m4
290: * keyword(s).
291: */
292: if (argc > 2)
293: for (n = 2; n < argc; n++)
294: remhash(argv[n], TOP);
295: break;
296:
297: case MKTMTYPE:
298: /*
299: * dotemp - create a temporary file
300: */
301: if (argc > 2)
302: pbstr(mktemp(argv[2]));
303: break;
304:
305: case TRNLTYPE:
306: /*
307: * dotranslit - replace all characters in
308: * the source string that appears in the
309: * "from" string with the corresponding
310: * characters in the "to" string.
311: */
312: if (argc > 3) {
1.8 deraadt 313: char temp[STRSPMAX+1];
1.1 deraadt 314: if (argc > 4)
315: map(temp, argv[2], argv[3], argv[4]);
316: else
317: map(temp, argv[2], argv[3], null);
318: pbstr(temp);
1.8 deraadt 319: } else if (argc > 2)
1.1 deraadt 320: pbstr(argv[2]);
321: break;
322:
323: case INDXTYPE:
324: /*
325: * doindex - find the index of the second
326: * argument string in the first argument
327: * string. -1 if not present.
328: */
329: pbnum((argc > 3) ? indx(argv[2], argv[3]) : -1);
330: break;
331:
332: case ERRPTYPE:
333: /*
334: * doerrp - print the arguments to stderr
335: * file
336: */
337: if (argc > 2) {
338: for (n = 2; n < argc; n++)
339: fprintf(stderr, "%s ", argv[n]);
340: fprintf(stderr, "\n");
341: }
342: break;
343:
344: case DNLNTYPE:
345: /*
346: * dodnl - eat-up-to and including
347: * newline
348: */
349: while ((c = gpbc()) != '\n' && c != EOF)
350: ;
351: break;
352:
353: case M4WRTYPE:
354: /*
355: * dom4wrap - set up for
356: * wrap-up/wind-down activity
357: */
358: m4wraps = (argc > 2) ? xstrdup(argv[2]) : null;
359: break;
360:
361: case EXITTYPE:
362: /*
363: * doexit - immediate exit from m4.
364: */
365: killdiv();
366: exit((argc > 2) ? atoi(argv[2]) : 0);
367: break;
368:
369: case DEFNTYPE:
370: if (argc > 2)
371: for (n = 2; n < argc; n++)
372: dodefn(argv[n]);
373: break;
374:
375: default:
1.12 espie 376: errx(1, "eval: major botch.");
1.1 deraadt 377: break;
378: }
379: }
380:
381: char *dumpfmt = "`%s'\t`%s'\n"; /* format string for dumpdef */
382:
383: /*
384: * expand - user-defined macro expansion
385: */
386: void
387: expand(argv, argc)
388: register char *argv[];
389: register int argc;
390: {
391: register char *t;
392: register char *p;
393: register int n;
394: register int argno;
395:
396: t = argv[0]; /* defn string as a whole */
397: p = t;
398: while (*p)
399: p++;
400: p--; /* last character of defn */
401: while (p > t) {
402: if (*(p - 1) != ARGFLAG)
403: putback(*p);
404: else {
405: switch (*p) {
406:
407: case '#':
408: pbnum(argc - 2);
409: break;
410: case '0':
411: case '1':
412: case '2':
413: case '3':
414: case '4':
415: case '5':
416: case '6':
417: case '7':
418: case '8':
419: case '9':
420: if ((argno = *p - '0') < argc - 1)
421: pbstr(argv[argno + 1]);
422: break;
423: case '*':
424: for (n = argc - 1; n > 2; n--) {
425: pbstr(argv[n]);
1.7 millert 426: putback(COMMA);
1.1 deraadt 427: }
428: pbstr(argv[2]);
429: break;
1.7 millert 430: case '@':
431: for (n = argc - 1; n > 2; n--) {
432: pbstr(rquote);
433: pbstr(argv[n]);
434: pbstr(lquote);
435: putback(COMMA);
436: }
437: pbstr(rquote);
438: pbstr(argv[2]);
439: pbstr(lquote);
440: break;
1.1 deraadt 441: default:
442: putback(*p);
443: putback('$');
444: break;
445: }
446: p--;
447: }
448: p--;
449: }
450: if (p == t) /* do last character */
451: putback(*p);
452: }
453:
454: /*
455: * dodefine - install definition in the table
456: */
457: void
458: dodefine(name, defn)
459: register char *name;
460: register char *defn;
461: {
462: register ndptr p;
463:
464: if (!*name)
1.12 espie 465: errx(1, "null definition.");
1.1 deraadt 466: if (STREQ(name, defn))
1.12 espie 467: errx(1, "%s: recursive definition.", name);
1.1 deraadt 468: if ((p = lookup(name)) == nil)
469: p = addent(name);
470: else if (p->defn != null)
471: free((char *) p->defn);
472: if (!*defn)
473: p->defn = null;
474: else
475: p->defn = xstrdup(defn);
476: p->type = MACRTYPE;
477: }
478:
479: /*
480: * dodefn - push back a quoted definition of
481: * the given name.
482: */
483: void
484: dodefn(name)
485: char *name;
486: {
487: register ndptr p;
488:
489: if ((p = lookup(name)) != nil && p->defn != null) {
1.10 deraadt 490: pbstr(rquote);
1.1 deraadt 491: pbstr(p->defn);
1.10 deraadt 492: pbstr(lquote);
1.1 deraadt 493: }
494: }
495:
496: /*
497: * dopushdef - install a definition in the hash table
498: * without removing a previous definition. Since
499: * each new entry is entered in *front* of the
500: * hash bucket, it hides a previous definition from
501: * lookup.
502: */
503: void
504: dopushdef(name, defn)
505: register char *name;
506: register char *defn;
507: {
508: register ndptr p;
509:
510: if (!*name)
1.12 espie 511: errx(1, "null definition");
1.1 deraadt 512: if (STREQ(name, defn))
1.12 espie 513: errx(1, "%s: recursive definition.", name);
1.1 deraadt 514: p = addent(name);
515: if (!*defn)
516: p->defn = null;
517: else
518: p->defn = xstrdup(defn);
519: p->type = MACRTYPE;
520: }
521:
522: /*
523: * dodumpdef - dump the specified definitions in the hash
524: * table to stderr. If nothing is specified, the entire
525: * hash table is dumped.
526: */
527: void
528: dodump(argv, argc)
529: register char *argv[];
530: register int argc;
531: {
532: register int n;
533: ndptr p;
534:
535: if (argc > 2) {
536: for (n = 2; n < argc; n++)
537: if ((p = lookup(argv[n])) != nil)
538: fprintf(stderr, dumpfmt, p->name,
539: p->defn);
1.8 deraadt 540: } else {
1.1 deraadt 541: for (n = 0; n < HASHSIZE; n++)
542: for (p = hashtab[n]; p != nil; p = p->nxtptr)
543: fprintf(stderr, dumpfmt, p->name,
544: p->defn);
545: }
546: }
547:
548: /*
549: * doifelse - select one of two alternatives - loop.
550: */
551: void
552: doifelse(argv, argc)
553: register char *argv[];
554: register int argc;
555: {
556: cycle {
557: if (STREQ(argv[2], argv[3]))
558: pbstr(argv[4]);
559: else if (argc == 6)
560: pbstr(argv[5]);
561: else if (argc > 6) {
562: argv += 3;
563: argc -= 3;
564: continue;
565: }
566: break;
567: }
568: }
569:
570: /*
571: * doinclude - include a given file.
572: */
573: int
574: doincl(ifile)
575: char *ifile;
576: {
577: if (ilevel + 1 == MAXINP)
1.12 espie 578: errx(1, "too many include files.");
1.15 ! espie 579: if ((infile[ilevel + 1] = fopen_trypath(ifile)) != NULL) {
1.1 deraadt 580: ilevel++;
581: bbase[ilevel] = bufbase = bp;
582: return (1);
1.8 deraadt 583: } else
1.1 deraadt 584: return (0);
585: }
586:
587: #ifdef EXTENDED
588: /*
589: * dopaste - include a given file without any
590: * macro processing.
591: */
592: int
593: dopaste(pfile)
594: char *pfile;
595: {
596: FILE *pf;
597: register int c;
598:
599: if ((pf = fopen(pfile, "r")) != NULL) {
600: while ((c = getc(pf)) != EOF)
601: putc(c, active);
602: (void) fclose(pf);
603: return (1);
1.8 deraadt 604: } else
1.1 deraadt 605: return (0);
606: }
607: #endif
608:
609: /*
610: * dochq - change quote characters
611: */
612: void
613: dochq(argv, argc)
614: register char *argv[];
615: register int argc;
616: {
617: if (argc > 2) {
1.9 deraadt 618: if (*argv[2])
1.2 deraadt 619: strncpy(lquote, argv[2], MAXCCHARS);
1.9 deraadt 620: else {
621: lquote[0] = LQUOTE;
1.14 espie 622: lquote[1] = EOS;
1.9 deraadt 623: }
1.1 deraadt 624: if (argc > 3) {
625: if (*argv[3])
1.2 deraadt 626: strncpy(rquote, argv[3], MAXCCHARS);
1.8 deraadt 627: } else
1.2 deraadt 628: strcpy(rquote, lquote);
1.8 deraadt 629: } else {
1.14 espie 630: lquote[0] = LQUOTE, lquote[1] = EOS;
631: rquote[0] = RQUOTE, rquote[1] = EOS;
1.1 deraadt 632: }
633: }
634:
635: /*
636: * dochc - change comment characters
637: */
638: void
639: dochc(argv, argc)
640: register char *argv[];
641: register int argc;
642: {
643: if (argc > 2) {
644: if (*argv[2])
1.2 deraadt 645: strncpy(scommt, argv[2], MAXCCHARS);
1.1 deraadt 646: if (argc > 3) {
647: if (*argv[3])
1.2 deraadt 648: strncpy(ecommt, argv[3], MAXCCHARS);
1.1 deraadt 649: }
650: else
1.14 espie 651: ecommt[0] = ECOMMT, ecommt[1] = EOS;
1.1 deraadt 652: }
653: else {
1.14 espie 654: scommt[0] = SCOMMT, scommt[1] = EOS;
655: ecommt[0] = ECOMMT, ecommt[1] = EOS;
1.1 deraadt 656: }
657: }
658:
659: /*
660: * dodivert - divert the output to a temporary file
661: */
662: void
663: dodiv(n)
664: register int n;
665: {
1.6 millert 666: int fd;
667:
1.8 deraadt 668: oindex = n;
1.1 deraadt 669: if (n < 0 || n >= MAXOUT)
670: n = 0; /* bitbucket */
671: if (outfile[n] == NULL) {
1.13 espie 672: char fname[] = _PATH_DIVNAME;
673:
674: if ((fd = mkstemp(fname)) < 0 ||
675: (outfile[n] = fdopen(fd, "w+")) == NULL)
676: err(1, "%s: cannot divert", fname);
677: if (unlink(fname) == -1)
678: err(1, "%s: cannot unlink", fname);
1.1 deraadt 679: }
680: active = outfile[n];
681: }
682:
683: /*
684: * doundivert - undivert a specified output, or all
685: * other outputs, in numerical order.
686: */
687: void
688: doundiv(argv, argc)
689: register char *argv[];
690: register int argc;
691: {
692: register int ind;
693: register int n;
694:
695: if (argc > 2) {
696: for (ind = 2; ind < argc; ind++) {
697: n = atoi(argv[ind]);
698: if (n > 0 && n < MAXOUT && outfile[n] != NULL)
699: getdiv(n);
700:
701: }
702: }
703: else
704: for (n = 1; n < MAXOUT; n++)
705: if (outfile[n] != NULL)
706: getdiv(n);
707: }
708:
709: /*
710: * dosub - select substring
711: */
712: void
713: dosub(argv, argc)
714: register char *argv[];
715: register int argc;
716: {
717: register char *ap, *fc, *k;
718: register int nc;
719:
720: if (argc < 5)
721: nc = MAXTOK;
722: else
723: #ifdef EXPR
724: nc = expr(argv[4]);
725: #else
726: nc = atoi(argv[4]);
727: #endif
728: ap = argv[2]; /* target string */
729: #ifdef EXPR
730: fc = ap + expr(argv[3]); /* first char */
731: #else
732: fc = ap + atoi(argv[3]); /* first char */
733: #endif
734: if (fc >= ap && fc < ap + strlen(ap))
735: for (k = fc + min(nc, strlen(fc)) - 1; k >= fc; k--)
736: putback(*k);
737: }
738:
739: /*
740: * map:
741: * map every character of s1 that is specified in from
742: * into s3 and replace in s. (source s1 remains untouched)
743: *
744: * This is a standard implementation of map(s,from,to) function of ICON
745: * language. Within mapvec, we replace every character of "from" with
746: * the corresponding character in "to". If "to" is shorter than "from",
747: * than the corresponding entries are null, which means that those
748: * characters dissapear altogether. Furthermore, imagine
749: * map(dest, "sourcestring", "srtin", "rn..*") type call. In this case,
750: * `s' maps to `r', `r' maps to `n' and `n' maps to `*'. Thus, `s'
751: * ultimately maps to `*'. In order to achieve this effect in an efficient
752: * manner (i.e. without multiple passes over the destination string), we
753: * loop over mapvec, starting with the initial source character. if the
754: * character value (dch) in this location is different than the source
755: * character (sch), sch becomes dch, once again to index into mapvec, until
756: * the character value stabilizes (i.e. sch = dch, in other words
757: * mapvec[n] == n). Even if the entry in the mapvec is null for an ordinary
758: * character, it will stabilize, since mapvec[0] == 0 at all times. At the
759: * end, we restore mapvec* back to normal where mapvec[n] == n for
760: * 0 <= n <= 127. This strategy, along with the restoration of mapvec, is
761: * about 5 times faster than any algorithm that makes multiple passes over
762: * destination string.
763: */
764: void
765: map(dest, src, from, to)
766: register char *dest;
767: register char *src;
768: register char *from;
769: register char *to;
770: {
771: register char *tmp;
772: register char sch, dch;
773: static char mapvec[128] = {
774: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
775: 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
776: 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
777: 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
778: 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
779: 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
780: 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
781: 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
782: 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
783: 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
784: 120, 121, 122, 123, 124, 125, 126, 127
785: };
786:
787: if (*src) {
788: tmp = from;
789: /*
790: * create a mapping between "from" and
791: * "to"
792: */
793: while (*from)
794: mapvec[*from++] = (*to) ? *to++ : (char) 0;
795:
796: while (*src) {
797: sch = *src++;
798: dch = mapvec[sch];
799: while (dch != sch) {
800: sch = dch;
801: dch = mapvec[sch];
802: }
1.11 millert 803: if ((*dest = dch))
1.1 deraadt 804: dest++;
805: }
806: /*
807: * restore all the changed characters
808: */
809: while (*tmp) {
810: mapvec[*tmp] = *tmp;
811: tmp++;
812: }
813: }
814: *dest = (char) 0;
815: }