1 /***************************************************************************
2 JSPICE3 adaptation of Spice3e2 - Copyright (c) Stephen R. Whiteley 1992
3 Copyright 1990 Regents of the University of California.  All rights reserved.
4 Authors: 1985 Wayne A. Christopher, Norbert Jeske
5          1992 Stephen R. Whiteley
6 ****************************************************************************/
7 
8 /*
9  * Expand subcircuits. This is very spice-dependent.
10  */
11 
12 #include "spice.h"
13 #include "ftedefs.h"
14 #include "fteinp.h"
15 #include "kwords.h"
16 #include "spfteext.h"
17 
18 
19 #ifdef __STDC__
20 static struct line *doit(struct line*);
21 static bool translate(struct line*,char*,char*,char*,char*,char*);
22 static void finishLine(char*,char*,char*);
23 static bool settrans(char*,char*,char*,char*);
24 static bool aredups(void);
25 static char *gettrans(char*);
26 static void numrefs(char*,int*,int*);
27 static bool modtranslate(struct line*,char*);
28 static void devmodtranslate(struct line*,char*);
29 static void looksubmod(char**,char*,char*);
30 #else
31 static struct line *doit();
32 static bool translate();
33 static void finishLine();
34 static bool settrans();
35 static bool aredups();
36 static char *gettrans();
37 static void numrefs();
38 static bool modtranslate();
39 static void devmodtranslate();
40 static void looksubmod();
41 #endif
42 
43 struct subs {
44     char *su_name;          /* The name. */
45     char *su_args;          /* The arguments, space seperated. */
46     int su_numargs;
47     struct line *su_def;    /* The deck that is to be substituted. */
48     struct subs *su_next;
49 } ;
50 
51 static struct tab {
52     char *t_old;
53     char *t_new;
54 } table[512];   /* That had better be enough. */
55 
56 
57 /* Expand all subcircuits in the deck. This handles imbedded .subckt
58  * definitions. The variables substart, subend, and subinvoke can be used
59  * to redefine the controls used. The syntax is invariant though.
60  * NOTE: the deck must be passed without the title card.
61  * What we do is as follows: first make one pass through the circuit
62  * and collect all of the subcircuits. Then, whenever a card that starts
63  * with 'x' is found, copy the subcircuit associated with that name and
64  * splice it in. A few of the problems: the nodes in the spliced-in
65  * stuff must be unique, so when we copy it, append "subcktname:" to
66  * each node. If we are in a nested subcircuit, use foo:bar:...:node.
67  * Then we have to systematically change all references to the renamed
68  * nodes. On top of that, we have to know how many args BJT's have,
69  * so we have to keep track of model names.
70  */
71 
72 static wordlist *modnames, *submod;
73 static struct subs *subs;
74 static bool nobjthack = false;
75 
76 static char start[32], sbend[32], invoke[32], model[32];
77 
78 
79 struct line *
inp_subcktexpand(deck)80 inp_subcktexpand(deck)
81 
82 struct line *deck;
83 {
84     struct line *ll, *c;
85     char *s;
86     extern char *kw_substart, *kw_subend, *kw_subinvoke, *kw_modelcard;
87     extern char *kw_nobjthack;
88 
89     if (!cp_getvar(kw_substart, VT_STRING, start))
90         (void) strcpy(start, ".subckt");
91     if (!cp_getvar(kw_subend, VT_STRING, sbend))
92         (void) strcpy(sbend, ".ends");
93     if (!cp_getvar(kw_subinvoke, VT_STRING, invoke))
94         (void) strcpy(invoke, "X");
95     if (!cp_getvar(kw_modelcard, VT_STRING, model))
96         (void) strcpy(model, ".model");
97     (void) cp_getvar(kw_nobjthack, VT_BOOL, (char *) &nobjthack);
98 
99     /* Let's do a few cleanup things first... Get rid of ( ) around node
100      * lists...
101      */
102     for (c = deck; c; c = c->li_next) {
103         if (prefix(start, c->li_line)) {
104             for (s = c->li_line; *s && (*s != '('); s++)
105                 ;
106             if (*s) {
107                 while (s[0] && (s[1] != ')')) {
108                     s[0] = s[1];
109                     s++;
110                 }
111                 while (s[1]) {
112                     s[0] = s[2];
113                     s++;
114                 }
115             }
116         }
117         else {
118             for (s = c->li_line; *s && !isspace(*s); s++)
119                 ;
120             while (isspace(*s))
121                 s++;
122             if (*s == '(') {
123                 while (s[0] && (s[1] != ')')) {
124                     s[0] = s[1];
125                     s++;
126                 }
127                 while (s[1]) {
128                     s[0] = s[2];
129                     s++;
130                 }
131             }
132         }
133     }
134 
135     ll = doit(deck);
136 
137     /* Now check to see if there are still subckt instances undefined... */
138     if (ll) {
139         for (c = ll; c; c = c->li_next)
140             if (ciprefix(invoke, c->li_line)) {
141                 fprintf(cp_err, "Error: unknown subckt: %s\n",
142                         c->li_line);
143                 ll = NULL;
144             }
145     }
146 
147     return (ll);
148 }
149 
150 
151 #define MAXNEST 21
152 
153 static struct line *
doit(deck)154 doit(deck)
155 
156 struct line *deck;
157 {
158     struct line *c, *last, *lc, *lcc;
159     struct subs *sss;
160     char *s, *t, *tt, *scname, *subname;
161     int nest, numpasses = MAXNEST, i;
162     bool gotone;
163     wordlist *wl;
164     wordlist *tmodnames = modnames;
165     wordlist *tsubmod = submod;
166     struct subs *ts = subs;
167 
168     /* Save all the old stuff... */
169     modnames = NULL;
170     subs = NULL;
171     submod = NULL;
172 
173     /* Extract all the .subckts */
174     for (last = deck, lc = NULL; last; ) {
175         if (prefix(sbend, last->li_line)) {
176             fprintf(cp_err, "Error: misplaced %s card: %s\n", sbend,
177                 last->li_line);
178             deck = NULL;
179             goto cleanup;
180         }
181         else if (prefix(start, last->li_line)) {
182             if (last->li_next == NULL) {
183                 fprintf(cp_err, "Error: no %s card.\n", sbend);
184                 deck = NULL;
185                 goto cleanup;
186             }
187             gotone = false;
188             lcc = NULL;
189             for (nest = 0, c = last->li_next; c; lcc = c,c = c->li_next) {
190                 if (prefix(sbend, c->li_line)) {
191                     if (!nest)
192                         break;
193                     else {
194                         nest--;
195                         continue;
196                     }
197                 }
198                 else if (prefix(start, c->li_line))
199                     nest++;
200             }
201             if (!c) {
202                 fprintf(cp_err, "Error: no %s card.\n", sbend);
203                 deck = NULL;
204                 goto cleanup;
205             }
206             if (lcc)
207                 lcc->li_next = NULL;
208             else
209                 last->li_next = NULL;
210 
211             sss = alloc(struct subs);
212             if (lc)
213                 lc->li_next = c->li_next;
214             else
215                 deck = c->li_next;
216 
217             sss->su_def = last->li_next;
218             s = last->li_line;
219             advtok(&s);
220             sss->su_name = gettok(&s);
221             sss->su_args = copy(s);
222             for (sss->su_numargs = 0, i = 0; s[i]; ) {
223                 while (isspace(s[i]))
224                     i++;
225                 if (s[i]) {
226                     sss->su_numargs++;
227                     while (s[i] && !isspace(s[i]))
228                         i++;
229                 }
230             }
231             sss->su_next = subs;
232             subs = sss;
233 
234             last->li_next = NULL;
235             inp_deckfree(last);
236 
237             last = c->li_next;
238 
239             c->li_next = NULL;
240             inp_deckfree(c);
241         }
242         else {
243             lc = last;
244             last = last->li_next;
245         }
246     }
247 
248     /* Expand sub-subcircuits. */
249     for (sss = subs; sss; sss = sss->su_next)
250         if (sss->su_def) {
251             if (!(sss->su_def = doit(sss->su_def))) {
252                 deck = NULL;
253                 goto cleanup;
254             }
255         }
256 
257     /* Get all the model names so we can deal with BJT's. */
258     for (c = deck; c; c = c->li_next)
259         if (prefix(model, c->li_line)) {
260             s = c->li_line;
261             advtok(&s);
262             wl = alloc(struct wordlist);
263             wl->wl_next = modnames;
264             if (modnames)
265                 modnames->wl_prev = wl;
266             modnames = wl;
267             wl->wl_word = gettok(&s);
268         }
269 
270     /* Now do the replacements. */
271     do {
272         gotone = false;
273         for (c = deck, lc = NULL; c; ) {
274             if (ciprefix(invoke, c->li_line)) {
275                 char *tmpscname;
276                 gotone = true;
277                 t = s = copy(c->li_line);
278                 tmpscname = scname = gettok(&s);
279                 scname += strlen(invoke);
280                 while ((*scname == ' ') || (*scname == '\t') ||
281                         (*scname == ':'))
282                     scname++;
283                 while(*s)
284                     s++;
285                 s--;
286                 while ((*s == ' ') || (*s == '\t'))
287                     *s-- = '\0';
288                 while ((*s != ' ') && (*s != '\t'))
289                     s--;
290                 s++;
291                 for (sss = subs; sss; sss = sss->su_next)
292                     if (eq(sss->su_name, s))
293                         break;
294 
295                 /* Don't complain -- this might be an
296                  * instance of a subckt that is defined above.
297                  */
298                 if (!sss) {
299                     lc = c;
300                     c = c->li_next;
301                     txfree(tmpscname);
302                     txfree(t);
303                     continue;
304                 }
305 
306                 if (!sss->su_def) {
307                     /* null subccircuit, just clip out
308                      * invocation and ignore
309                      */
310                     if (lc)
311                         lc->li_next = c->li_next;
312                     else
313                         deck = c->li_next;
314 
315                     c->li_next = NULL;
316                     inp_deckfree(c);
317 
318                     if (lc)
319                         c = lc->li_next;
320                     else
321                         c = deck;
322 
323                     txfree(tmpscname);
324                     txfree(t);
325                     fprintf(cp_err,
326                         "Warning: empty subcircuit %s referenced, ignored\n",
327                         sss->su_name);
328                     continue;
329                 }
330 
331                 /* Now we have to replace this card with the
332                  * macro definition.
333                  */
334                 subname = copy(sss->su_name);
335 
336                 lcc = inp_deckcopy(sss->su_def);
337 
338                 /* Change the names of the models... */
339                 if (modtranslate(lcc, scname))
340                     devmodtranslate(lcc, scname);
341 
342                 s = sss->su_args;
343                 tt = t;
344                 advtok(&t); /* Throw out the name. */
345 
346                 if (translate(lcc, s, t, scname, subname, c->li_line)) {
347                     txfree(tmpscname);
348                     tfree(subname);
349                     txfree(tt);
350                     deck = NULL;
351                     goto cleanup;
352                 }
353 
354                 txfree(tmpscname);
355                 tfree(subname);
356                 txfree(tt);
357 
358                 /* Now splice the decks together. */
359                 if (lc)
360                     lc->li_next = lcc;
361                 else
362                     deck = lcc;
363                 while (lcc->li_next != NULL)
364                     lcc = lcc->li_next;
365                 lcc->li_next = c->li_next;
366 
367                 c->li_next = NULL;
368                 inp_deckfree(c);
369                 c = lcc->li_next;
370                 lc = lcc;
371             }
372             else {
373                 lc = c;
374                 c = c->li_next;
375             }
376         }
377     } while (numpasses-- && gotone);
378 
379     if (!numpasses) {
380         fprintf(cp_err, "Error: infinite subckt recursion\n");
381         deck = NULL;
382     }
383 
384 cleanup:
385     wl_free(modnames);
386     wl_free(submod);
387     for (sss = subs; sss; sss = subs) {
388         subs = sss->su_next;
389         txfree(sss->su_name);
390         txfree(sss->su_args);
391         inp_deckfree(sss->su_def);
392         txfree((char*)sss);
393     }
394     subs = ts;
395     modnames = tmodnames;
396     submod = tsubmod;
397     return (deck);
398 }
399 
400 
401 static bool
translate(deck,formal,actual,scname,subname,instr)402 translate(deck, formal, actual, scname, subname, instr)
403 
404 /* Translate all of the device names and node names in the deck. They are
405  * pre-pended with subname:, unless they are in the formal list, in which
406  * case they are replaced with the corresponding entry in the actual list.
407  * The one special case is node 0 -- this is always ground and we don't
408  * touch it.
409  */
410 struct line *deck;
411 char *formal, *actual, *scname, *subname, *instr;
412 {
413     struct line *c;
414     char *buffer, *name, *s, *t, ch;
415     int nnodes, ndevs, i;
416     char *tmpname;
417     bool retval = false;
418 
419     if (settrans(formal, actual, subname, instr)) {
420         retval = true;
421         goto cleanup;
422     }
423 
424     for (c = deck; c; c = c->li_next) {
425         /* Rename the device. */
426         ch = *c->li_line;
427         if (ch == '.' || ch == '*' || ch == '\0')
428             /* Nothing any good here. */
429             continue;
430 
431         s = c->li_line;
432         tmpname = name = gettok(&s);
433         if (!name)
434             continue;
435         if (!*name) {
436             txfree(name);
437             continue;
438         }
439         ch = *name;
440         buffer = tmalloc(1000);
441         name++;
442         if (*name == ':')
443             name++;
444         if (*name)
445             (void) sprintf(buffer, "%c:%s:%s ", ch, scname, name);
446         else
447             (void) sprintf(buffer, "%c:%s ", ch, scname);
448         txfree(tmpname);
449 
450         numrefs(c->li_line,&nnodes,&ndevs);
451         while (nnodes-- > 0) {
452             name = gettok(&s);
453             if (name == NULL) {
454                fprintf(cp_err, "Warning: too few nodes: %s\n", c->li_line);
455                 retval = true;
456                 txfree(name);
457                 goto cleanup;
458             }
459             t = gettrans(name);
460             if (t)
461                 (void) sprintf(buffer + strlen(buffer), "%s ", t);
462             else
463                 (void) sprintf(buffer + strlen(buffer),
464                         "%s:%s ", scname, name);
465             txfree(name);
466         }
467         while (ndevs-- > 0) {
468             tmpname = name = gettok(&s);
469             if (name == NULL) {
470                 fprintf(cp_err, "Warning: too few devs: %s\n", c->li_line);
471                 retval = true;
472                 goto cleanup;
473             }
474             ch = *name;
475             name++;
476             if (*name == ':')
477                 name++;
478             if (*name)
479                 (void) sprintf(buffer + strlen(buffer),
480                         "%c:%s:%s ", ch, scname, name);
481             else
482                 (void) sprintf(buffer + strlen(buffer),
483                         "%c:%s ", ch, scname);
484             txfree(tmpname);
485         }
486         /* Now scan through the line for v(something) and
487          * i(something)...
488          */
489         finishLine(buffer + strlen(buffer), s, scname);
490         txfree(c->li_line);
491         c->li_line = copy(buffer);
492         txfree(buffer);
493     }
494 cleanup:
495     for (i = 0; i < 512; i++) {
496         if (table[i].t_old == NULL) {
497             tfree(table[i].t_new);
498             break;
499         }
500         tfree(table[i].t_old);
501         tfree(table[i].t_new);
502     }
503     return retval;
504 }
505 
506 
507 static void
finishLine(dst,src,scname)508 finishLine(dst, src, scname)
509 
510 char *dst;
511 char *src;
512 char *scname;
513 {
514     char buf[4 * BSIZE_SP], which;
515     char *s;
516     int i;
517     int lastwasalpha;
518 
519     lastwasalpha = 0;
520     while (*src) {
521         /* Find the next instance of "<non-alpha>[vi]<opt spaces>(" in
522          * this string.
523          */
524         if (((*src != 'v') && (*src != 'V') &&
525             (*src != 'i') && (*src != 'I')) ||
526             lastwasalpha) {
527             lastwasalpha = isalpha(*src);
528             *dst++ = *src++;
529             continue;
530         }
531         for (s = src + 1; *s && isspace(*s); s++)
532             ;
533         if (!*s || (*s != '(')) {
534             lastwasalpha = isalpha(*src);
535             *dst++ = *src++;
536             continue;
537         }
538         lastwasalpha = 0;
539         which = *dst++ = *src;
540         src = s;
541         *dst++ = *src++;
542         while (isspace(*src))
543             src++;
544         for (i = 0;
545             *src && !isspace(*src) && *src != ',' && (*src != ')'); i++) {
546             buf[i] = *src++;
547         }
548         buf[i] = '\0';
549 
550         if ((which == 'v') || (which == 'V'))
551             s = gettrans(buf);
552         else
553             s = NULL;
554 
555         if (s) {
556             while (*s)
557                 *dst++ = *s++;
558         }
559         else {
560             /*
561              * i(vname) -> i(v:subckt:name)
562              * i(v:other:name) -> i(v:subckt:other:name)
563              */
564             if (buf[0] == 'v' || buf[0] == 'V') {
565                 *dst++ = buf[0];
566                 *dst++ = ':';
567                 i = 1;
568             }
569             else {
570                 i = 0;
571             }
572             for (s = scname; *s; )
573                 *dst++ = *s++;
574             *dst++ = ':';
575             for (s = buf + i; *s; )
576                 *dst++ = *s++;
577         }
578 
579         /* translate the reference node, as in the "2" in "v(4,2)" */
580 
581         if ((which == 'v') || (which == 'V')) {
582             while (*src && (isspace(*src) || *src == ',')) {
583                 src++;
584             }
585             if (*src && *src != ')') {
586                 for (i = 0; *src && !isspace(*src) && (*src != ')'); i++)
587                     buf[i] = *src++;
588                 buf[i] = '\0';
589                 s = gettrans(buf);
590                 *dst++ = ',';
591                 if (s) {
592                     while (*s)
593                     *dst++ = *s++;
594                 }
595                 else {
596                     for (s = scname; *s; )
597                         *dst++ = *s++;
598                     *dst++ = ':';
599                     for (s = buf; *s; )
600                         *dst++ = *s++;
601                 }
602             }
603         }
604     }
605     return;
606 }
607 
608 
609 static bool
settrans(formal,actual,subname,instr)610 settrans(formal, actual, subname, instr)
611 
612 char *formal, *actual, *subname, *instr;
613 {
614     int i;
615 
616     for (i = 0; ; i++) {
617         table[i].t_old = gettok(&formal);
618         table[i].t_new = gettok(&actual);
619 
620         if (table[i].t_new == NULL) {
621             fprintf(cp_err, "Error: too many params: %s\n", instr);
622             return (true);        /* Too few formal / too many actual */
623         }
624         if (table[i].t_old == NULL) {
625             if (eq(table[i].t_new, subname))
626                 break;
627             else {
628                 fprintf(cp_err, "Error: too few params: %s\n", instr);
629                 return (true);    /* Too few actual / too many formal */
630             }
631         }
632     }
633     if (aredups()) {
634         fprintf(cp_err,
635             "Error: duplicate nodes given in subckt %s\n",subname);
636         return (true);
637     }
638     return (false);
639 }
640 
641 
642 static bool
aredups()643 aredups()
644 
645 {
646     int i, j;
647     char *s;
648 
649     for (i = 0; table[i].t_old; i++) {
650         s = table[i].t_old;
651         for (j = i+1; table[j].t_old; j++) {
652             if (eq(s,table[j].t_old))
653                 return (true);
654         }
655     }
656     return (false);
657 }
658 
659 
660 static char *
gettrans(name)661 gettrans(name)
662 
663 char *name;
664 {
665     int i;
666 
667     if (eq(name, "0"))
668         return (name);
669     for (i = 0; table[i].t_old; i++)
670         if (eq(table[i].t_old, name))
671             return (table[i].t_new);
672     return (NULL);
673 }
674 
675 
676 static void
numrefs(name,terms,devs)677 numrefs(name,terms,devs)
678 
679 char *name;
680 int *terms, *devs;
681 {
682     char c;
683     struct subs *sss;
684     char *s, *t, buf[4 * BSIZE_SP];
685     wordlist *wl;
686     int n, i, soft;
687 
688     while (*name && isspace(*name))
689     name++;
690 
691     c = (isupper(*name) ? tolower(*name) : *name);
692 
693     (void) strncpy(buf, name, sizeof(buf));
694     s = buf;
695     if (c == 'x') {     /* Handle this ourselves. */
696         while(*s)
697             s++;
698         s--;
699         while ((*s == ' ') || (*s == '\t'))
700             *s-- = '\0';
701         while ((*s != ' ') && (*s != '\t'))
702             s--;
703         s++;
704         for (sss = subs; sss; sss = sss->su_next)
705             if (eq(sss->su_name, s))
706                 break;
707         if (!sss) {
708             *terms = 0;
709             *devs = 0;
710             fprintf(cp_err, "Error: no such subcircuit: %s\n", s);
711             return;
712         }
713         *terms = sss->su_numargs;
714         *devs = 0;
715         return;
716     }
717 
718     (void)INPnumRefs(c,&n,devs,&soft,NULL);
719     if (isalpha(c) && n == 0 && *devs == 0)
720         fprintf(cp_err, "Warning: unknown device key: %c\n", c);
721 
722     if (!soft) {
723         *terms = n;
724         return;
725     }
726     for (s = buf, i = 0; *s && (i < n); i++)
727         advtok(&s);
728     if (i == n-1) {
729         *terms = n-1;
730         return;
731     }
732     else if (i < n) {
733         fprintf(cp_err, "Error: too few nodes for %s\n", name);
734         *terms = 0;
735         return;
736     }
737     /* Now, is this a model? */
738     t = gettok(&s);
739     for (wl = modnames; wl; wl = wl->wl_next) {
740         if (eq(t, wl->wl_word)) {
741             txfree(t);
742             *terms = n-1;
743             return;
744         }
745     }
746     txfree(t);
747     *terms = n;
748     return;
749 }
750 
751 
752 static bool
modtranslate(deck,subname)753 modtranslate(deck, subname)
754 
755 struct line *deck;
756 char *subname;
757 {
758     struct line *c;
759     char *buffer, *name, *t;
760     wordlist *wl, *wlsub;
761     bool gotone;
762 
763     gotone = false;
764     for (c = deck; c; c = c->li_next) {
765         if (prefix(model, c->li_line)) {
766             gotone = true;
767             t = c->li_line;
768             name = gettok(&t);
769             buffer = tmalloc(strlen(name) + strlen(t) +
770                     strlen(subname) + 4);
771             (void) sprintf(buffer, "%s ",name);
772             txfree(name);
773             name = gettok(&t);
774             wlsub = alloc(struct wordlist);
775             wlsub->wl_next = submod;
776             if (submod)
777                 submod->wl_prev = wlsub;
778             submod = wlsub;
779             wlsub->wl_word = name;
780             (void) sprintf(buffer + strlen(buffer), "%s:%s ",
781                     subname, name);
782             (void) strcat(buffer, t);
783             txfree(c->li_line);
784             c->li_line = buffer;
785             t = c->li_line;
786             advtok(&t);
787             wl = alloc(struct wordlist);
788             wl->wl_next = modnames;
789             if (modnames)
790                 modnames->wl_prev = wl;
791             modnames = wl;
792             wl->wl_word = gettok(&t);
793         }
794     }
795     return (gotone);
796 }
797 
798 
799 static void
devmodtranslate(deck,subname)800 devmodtranslate(deck, subname)
801 
802 struct line *deck;
803 char *subname;
804 {
805     struct line *s;
806     char *buffer, *name, *t, c;
807     int terms, devs, soft, hasmod;
808 
809     for (s = deck; s; s = s->li_next) {
810         t = s->li_line;
811         while (*t && isspace(*t))
812             t++;
813         c = isupper(*t) ? tolower(*t) : *t;
814 
815         INPnumRefs(c,&terms,&devs,&soft,&hasmod);
816         if (!hasmod)
817             continue;
818 
819         buffer = tmalloc(strlen(t) + strlen(subname) + 4);
820 
821         terms += devs + 1;
822         if (soft) terms--;
823         *buffer = '\0';
824         while (terms--)
825             copytok1(buffer + strlen(buffer),&t);
826 
827         looksubmod(&t,buffer,subname);
828         if (soft)
829             looksubmod(&t,buffer,subname);
830         (void) strcat(buffer, t);
831         txfree(s->li_line);
832         s->li_line = buffer;
833     }
834 }
835 
836 
837 static void
looksubmod(t,buffer,subname)838 looksubmod(t,buffer,subname)
839 
840 char **t, *buffer, *subname;
841 {
842     wordlist *wlsub;
843     char *name;
844     bool found = false;;
845 
846     if (**t == '\0') return;
847     name = gettok(t);
848     /* Now, is this a subcircuit model? */
849     for (wlsub = submod; wlsub; wlsub = wlsub->wl_next)
850         if (eq(name, wlsub->wl_word)) {
851             (void) sprintf(buffer + strlen(buffer), "%s:%s ", subname, name);
852             found = true;
853             break;
854         }
855     if (!found)
856         (void) sprintf(buffer + strlen(buffer), "%s ", name);
857     txfree(name);
858 }
859 
860 
861 
862 
863 
864 
865 
866