ncurses 5.9 - patch 20150404
[ncurses.git] / ncurses / tinfo / captoinfo.c
1 /****************************************************************************
2  * Copyright (c) 1998-2011,2012 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey                        1996-on                 *
33  ****************************************************************************/
34
35 /*
36  *      captoinfo.c --- conversion between termcap and terminfo formats
37  *
38  *      The captoinfo() code was swiped from Ross Ridge's mytinfo package,
39  *      adapted to fit ncurses by Eric S. Raymond <esr@snark.thyrsus.com>.
40  *
41  *      There is just one entry point:
42  *
43  *      char *_nc_captoinfo(n, s, parameterized)
44  *
45  *      Convert value s for termcap string capability named n into terminfo
46  *      format.
47  *
48  *      This code recognizes all the standard 4.4BSD %-escapes:
49  *
50  *      %%       output `%'
51  *      %d       output value as in printf %d
52  *      %2       output value as in printf %2d
53  *      %3       output value as in printf %3d
54  *      %.       output value as in printf %c
55  *      %+x      add x to value, then do %.
56  *      %>xy     if value > x then add y, no output
57  *      %r       reverse order of two parameters, no output
58  *      %i       increment by one, no output
59  *      %n       exclusive-or all parameters with 0140 (Datamedia 2500)
60  *      %B       BCD (16*(value/10)) + (value%10), no output
61  *      %D       Reverse coding (value - 2*(value%16)), no output (Delta Data).
62  *
63  *      Also, %02 and %03 are accepted as synonyms for %2 and %3.
64  *
65  *      Besides all the standard termcap escapes, this translator understands
66  *      the following extended escapes:
67  *
68  *      used by GNU Emacs termcap libraries
69  *              %a[+*-/=][cp]x  GNU arithmetic.
70  *              %m              xor the first two parameters by 0177
71  *              %b              backup to previous parameter
72  *              %f              skip this parameter
73  *
74  *      used by the University of Waterloo (MFCF) termcap libraries
75  *              %-x      subtract parameter FROM char x and output it as a char
76  *              %ax      add the character x to parameter
77  *
78  *      If #define WATERLOO is on, also enable these translations:
79  *
80  *              %sx      subtract parameter FROM the character x
81  *
82  *      By default, this Waterloo translations are not compiled in, because
83  *      the Waterloo %s conflicts with the way terminfo uses %s in strings for
84  *      function programming.
85  *
86  *      Note the two definitions of %a: the GNU definition is translated if the
87  *      characters after the 'a' are valid for it, otherwise the UW definition
88  *      is translated.
89  */
90
91 #include <curses.priv.h>
92
93 #include <ctype.h>
94 #include <tic.h>
95
96 MODULE_ID("$Id: captoinfo.c,v 1.77 2012/12/30 00:50:40 tom Exp $")
97
98 #define MAX_PUSHED      16      /* max # args we can push onto the stack */
99
100 static int stack[MAX_PUSHED];   /* the stack */
101 static int stackptr;            /* the next empty place on the stack */
102 static int onstack;             /* the top of stack */
103 static int seenm;               /* seen a %m */
104 static int seenn;               /* seen a %n */
105 static int seenr;               /* seen a %r */
106 static int param;               /* current parameter */
107 static char *dp;                /* pointer to end of the converted string */
108
109 static char *my_string;
110 static size_t my_length;
111
112 static char *
113 init_string(void)
114 /* initialize 'my_string', 'my_length' */
115 {
116     if (my_string == 0)
117         TYPE_MALLOC(char, my_length = 256, my_string);
118
119     *my_string = '\0';
120     return my_string;
121 }
122
123 static char *
124 save_string(char *d, const char *const s)
125 {
126     size_t have = (size_t) (d - my_string);
127     size_t need = have + strlen(s) + 2;
128     if (need > my_length) {
129         my_string = (char *) _nc_doalloc(my_string, my_length = (need + need));
130         if (my_string == 0)
131             _nc_err_abort(MSG_NO_MEMORY);
132         d = my_string + have;
133     }
134     _nc_STRCPY(d, s, my_length - have);
135     return d + strlen(d);
136 }
137
138 static NCURSES_INLINE char *
139 save_char(char *s, int c)
140 {
141     static char temp[2];
142     temp[0] = (char) c;
143     return save_string(s, temp);
144 }
145
146 static void
147 push(void)
148 /* push onstack on to the stack */
149 {
150     if (stackptr >= MAX_PUSHED)
151         _nc_warning("string too complex to convert");
152     else
153         stack[stackptr++] = onstack;
154 }
155
156 static void
157 pop(void)
158 /* pop the top of the stack into onstack */
159 {
160     if (stackptr == 0) {
161         if (onstack == 0)
162             _nc_warning("I'm confused");
163         else
164             onstack = 0;
165     } else
166         onstack = stack[--stackptr];
167     param++;
168 }
169
170 static int
171 cvtchar(register const char *sp)
172 /* convert a character to a terminfo push */
173 {
174     unsigned char c = 0;
175     int len;
176
177     switch (*sp) {
178     case '\\':
179         switch (*++sp) {
180         case '\'':
181         case '$':
182         case '\\':
183         case '%':
184             c = (unsigned char) (*sp);
185             len = 2;
186             break;
187         case '\0':
188             c = '\\';
189             len = 1;
190             break;
191         case '0':
192         case '1':
193         case '2':
194         case '3':
195             len = 1;
196             while (isdigit(UChar(*sp))) {
197                 c = (unsigned char) (8 * c + (*sp++ - '0'));
198                 len++;
199             }
200             break;
201         default:
202             c = (unsigned char) (*sp);
203             len = 2;
204             break;
205         }
206         break;
207     case '^':
208         c = (unsigned char) (*++sp & 0x1f);
209         len = 2;
210         break;
211     default:
212         c = (unsigned char) (*sp);
213         len = 1;
214     }
215     if (isgraph(c) && c != ',' && c != '\'' && c != '\\' && c != ':') {
216         dp = save_string(dp, "%\'");
217         dp = save_char(dp, c);
218         dp = save_char(dp, '\'');
219     } else {
220         dp = save_string(dp, "%{");
221         if (c > 99)
222             dp = save_char(dp, c / 100 + '0');
223         if (c > 9)
224             dp = save_char(dp, ((int) (c / 10)) % 10 + '0');
225         dp = save_char(dp, c % 10 + '0');
226         dp = save_char(dp, '}');
227     }
228     return len;
229 }
230
231 static void
232 getparm(int parm, int n)
233 /* push n copies of param on the terminfo stack if not already there */
234 {
235     if (seenr) {
236         if (parm == 1)
237             parm = 2;
238         else if (parm == 2)
239             parm = 1;
240     }
241
242     while (n--) {
243         dp = save_string(dp, "%p");
244         dp = save_char(dp, '0' + parm);
245     }
246
247     if (onstack == parm) {
248         if (n > 1) {
249             _nc_warning("string may not be optimal");
250             dp = save_string(dp, "%Pa");
251             while (n--) {
252                 dp = save_string(dp, "%ga");
253             }
254         }
255         return;
256     }
257     if (onstack != 0)
258         push();
259
260     onstack = parm;
261
262     if (seenn && parm < 3) {
263         dp = save_string(dp, "%{96}%^");
264     }
265
266     if (seenm && parm < 3) {
267         dp = save_string(dp, "%{127}%^");
268     }
269 }
270
271 /*
272  * Convert a termcap string to terminfo format.
273  * 'cap' is the relevant terminfo capability index.
274  * 's' is the string value of the capability.
275  * 'parameterized' tells what type of translations to do:
276  *      % translations if 1
277  *      pad translations if >=0
278  */
279 NCURSES_EXPORT(char *)
280 _nc_captoinfo(const char *cap, const char *s, int const parameterized)
281 {
282     const char *capstart;
283
284     stackptr = 0;
285     onstack = 0;
286     seenm = 0;
287     seenn = 0;
288     seenr = 0;
289     param = 1;
290
291     dp = init_string();
292
293     /* skip the initial padding (if we haven't been told not to) */
294     capstart = 0;
295     if (s == 0)
296         s = "";
297     if (parameterized >= 0 && isdigit(UChar(*s)))
298         for (capstart = s;; s++)
299             if (!(isdigit(UChar(*s)) || *s == '*' || *s == '.'))
300                 break;
301
302     while (*s != '\0') {
303         switch (*s) {
304         case '%':
305             s++;
306             if (parameterized < 1) {
307                 dp = save_char(dp, '%');
308                 break;
309             }
310             switch (*s++) {
311             case '%':
312                 dp = save_char(dp, '%');
313                 break;
314             case 'r':
315                 if (seenr++ == 1) {
316                     _nc_warning("saw %%r twice in %s", cap);
317                 }
318                 break;
319             case 'm':
320                 if (seenm++ == 1) {
321                     _nc_warning("saw %%m twice in %s", cap);
322                 }
323                 break;
324             case 'n':
325                 if (seenn++ == 1) {
326                     _nc_warning("saw %%n twice in %s", cap);
327                 }
328                 break;
329             case 'i':
330                 dp = save_string(dp, "%i");
331                 break;
332             case '6':
333             case 'B':
334                 getparm(param, 1);
335                 dp = save_string(dp, "%{10}%/%{16}%*");
336                 getparm(param, 1);
337                 dp = save_string(dp, "%{10}%m%+");
338                 break;
339             case '8':
340             case 'D':
341                 getparm(param, 2);
342                 dp = save_string(dp, "%{2}%*%-");
343                 break;
344             case '>':
345                 getparm(param, 2);
346                 /* %?%{x}%>%t%{y}%+%; */
347                 dp = save_string(dp, "%?");
348                 s += cvtchar(s);
349                 dp = save_string(dp, "%>%t");
350                 s += cvtchar(s);
351                 dp = save_string(dp, "%+%;");
352                 break;
353             case 'a':
354                 if ((*s == '=' || *s == '+' || *s == '-'
355                      || *s == '*' || *s == '/')
356                     && (s[1] == 'p' || s[1] == 'c')
357                     && s[2] != '\0') {
358                     int l;
359                     l = 2;
360                     if (*s != '=')
361                         getparm(param, 1);
362                     if (s[1] == 'p') {
363                         getparm(param + s[2] - '@', 1);
364                         if (param != onstack) {
365                             pop();
366                             param--;
367                         }
368                         l++;
369                     } else
370                         l += cvtchar(s + 2);
371                     switch (*s) {
372                     case '+':
373                         dp = save_string(dp, "%+");
374                         break;
375                     case '-':
376                         dp = save_string(dp, "%-");
377                         break;
378                     case '*':
379                         dp = save_string(dp, "%*");
380                         break;
381                     case '/':
382                         dp = save_string(dp, "%/");
383                         break;
384                     case '=':
385                         if (seenr) {
386                             if (param == 1)
387                                 onstack = 2;
388                             else if (param == 2)
389                                 onstack = 1;
390                             else
391                                 onstack = param;
392                         } else
393                             onstack = param;
394                         break;
395                     }
396                     s += l;
397                     break;
398                 }
399                 getparm(param, 1);
400                 s += cvtchar(s);
401                 dp = save_string(dp, "%+");
402                 break;
403             case '+':
404                 getparm(param, 1);
405                 s += cvtchar(s);
406                 dp = save_string(dp, "%+%c");
407                 pop();
408                 break;
409             case 's':
410 #ifdef WATERLOO
411                 s += cvtchar(s);
412                 getparm(param, 1);
413                 dp = save_string(dp, "%-");
414 #else
415                 getparm(param, 1);
416                 dp = save_string(dp, "%s");
417                 pop();
418 #endif /* WATERLOO */
419                 break;
420             case '-':
421                 s += cvtchar(s);
422                 getparm(param, 1);
423                 dp = save_string(dp, "%-%c");
424                 pop();
425                 break;
426             case '.':
427                 getparm(param, 1);
428                 dp = save_string(dp, "%c");
429                 pop();
430                 break;
431             case '0':           /* not clear any of the historical termcaps did this */
432                 if (*s == '3')
433                     goto see03;
434                 else if (*s != '2')
435                     goto invalid;
436                 /* FALLTHRU */
437             case '2':
438                 getparm(param, 1);
439                 dp = save_string(dp, "%2d");
440                 pop();
441                 break;
442             case '3':
443               see03:
444                 getparm(param, 1);
445                 dp = save_string(dp, "%3d");
446                 pop();
447                 break;
448             case 'd':
449                 getparm(param, 1);
450                 dp = save_string(dp, "%d");
451                 pop();
452                 break;
453             case 'f':
454                 param++;
455                 break;
456             case 'b':
457                 param--;
458                 break;
459             case '\\':
460                 dp = save_string(dp, "%\\");
461                 break;
462             default:
463               invalid:
464                 dp = save_char(dp, '%');
465                 s--;
466                 _nc_warning("unknown %% code %s (%#x) in %s",
467                             unctrl((chtype) *s), UChar(*s), cap);
468                 break;
469             }
470             break;
471         default:
472             dp = save_char(dp, *s++);
473             break;
474         }
475     }
476
477     /*
478      * Now, if we stripped off some leading padding, add it at the end
479      * of the string as mandatory padding.
480      */
481     if (capstart) {
482         dp = save_string(dp, "$<");
483         for (s = capstart;; s++)
484             if (isdigit(UChar(*s)) || *s == '*' || *s == '.')
485                 dp = save_char(dp, *s);
486             else
487                 break;
488         dp = save_string(dp, "/>");
489     }
490
491     (void) save_char(dp, '\0');
492     return (my_string);
493 }
494
495 /*
496  * Check for an expression that corresponds to "%B" (BCD):
497  *      (parameter / 10) * 16 + (parameter % 10)
498  */
499 static int
500 bcd_expression(const char *str)
501 {
502     /* leave this non-const for HPUX */
503     static char fmt[] = "%%p%c%%{10}%%/%%{16}%%*%%p%c%%{10}%%m%%+";
504     int len = 0;
505     char ch1, ch2;
506
507     if (sscanf(str, fmt, &ch1, &ch2) == 2
508         && isdigit(UChar(ch1))
509         && isdigit(UChar(ch2))
510         && (ch1 == ch2)) {
511         len = 28;
512 #ifndef NDEBUG
513         {
514             char buffer[80];
515             int tst;
516             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) fmt, ch1, ch2);
517             tst = strlen(buffer) - 1;
518             assert(len == tst);
519         }
520 #endif
521     }
522     return len;
523 }
524
525 static char *
526 save_tc_char(char *bufptr, int c1)
527 {
528     char temp[80];
529
530     if (is7bits(c1) && isprint(c1)) {
531         if (c1 == ':' || c1 == '\\')
532             bufptr = save_char(bufptr, '\\');
533         bufptr = save_char(bufptr, c1);
534     } else {
535         if (c1 == (c1 & 0x1f)) {        /* iscntrl() returns T on 255 */
536             _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
537                         "%.20s", unctrl((chtype) c1));
538         } else {
539             _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
540                         "\\%03o", c1);
541         }
542         bufptr = save_string(bufptr, temp);
543     }
544     return bufptr;
545 }
546
547 static char *
548 save_tc_inequality(char *bufptr, int c1, int c2)
549 {
550     bufptr = save_string(bufptr, "%>");
551     bufptr = save_tc_char(bufptr, c1);
552     bufptr = save_tc_char(bufptr, c2);
553     return bufptr;
554 }
555
556 /*
557  * Here are the capabilities infotocap assumes it can translate to:
558  *
559  *     %%       output `%'
560  *     %d       output value as in printf %d
561  *     %2       output value as in printf %2d
562  *     %3       output value as in printf %3d
563  *     %.       output value as in printf %c
564  *     %+c      add character c to value, then do %.
565  *     %>xy     if value > x then add y, no output
566  *     %r       reverse order of two parameters, no output
567  *     %i       increment by one, no output
568  *     %n       exclusive-or all parameters with 0140 (Datamedia 2500)
569  *     %B       BCD (16*(value/10)) + (value%10), no output
570  *     %D       Reverse coding (value - 2*(value%16)), no output (Delta Data).
571  *     %m       exclusive-or all parameters with 0177 (not in 4.4BSD)
572  */
573
574 /*
575  * Convert a terminfo string to termcap format.  Parameters are as in
576  * _nc_captoinfo().
577  */
578 NCURSES_EXPORT(char *)
579 _nc_infotocap(const char *cap GCC_UNUSED, const char *str, int const parameterized)
580 {
581     int seenone = 0, seentwo = 0, saw_m = 0, saw_n = 0;
582     const char *padding;
583     const char *trimmed = 0;
584     int in0, in1, in2;
585     char ch1 = 0, ch2 = 0;
586     char *bufptr = init_string();
587     char octal[4];
588     int len;
589     bool syntax_error = FALSE;
590
591     /* we may have to move some trailing mandatory padding up front */
592     padding = str + strlen(str) - 1;
593     if (padding > str && *padding == '>') {
594         if (*--padding == '/')
595             --padding;
596         while (isdigit(UChar(*padding)) || *padding == '.' || *padding == '*')
597             padding--;
598         if (padding > str && *padding == '<' && *--padding == '$')
599             trimmed = padding;
600         padding += 2;
601
602         while (isdigit(UChar(*padding)) || *padding == '.' || *padding == '*')
603             bufptr = save_char(bufptr, *padding++);
604     }
605
606     for (; *str && ((trimmed == 0) || (str < trimmed)); str++) {
607         int c1, c2;
608         char *cp = 0;
609
610         if (str[0] == '^') {
611             if (str[1] == '\0' || (str + 1) == trimmed) {
612                 bufptr = save_string(bufptr, "\\136");
613                 ++str;
614             } else {
615                 bufptr = save_char(bufptr, *str++);
616                 bufptr = save_char(bufptr, *str);
617             }
618         } else if (str[0] == '\\') {
619             if (str[1] == '\0' || (str + 1) == trimmed) {
620                 bufptr = save_string(bufptr, "\\134");
621                 ++str;
622             } else if (str[1] == '^') {
623                 bufptr = save_string(bufptr, "\\136");
624                 ++str;
625             } else if (str[1] == ',') {
626                 bufptr = save_char(bufptr, *++str);
627             } else {
628                 int xx1, xx2;
629
630                 bufptr = save_char(bufptr, *str++);
631                 xx1 = *str;
632                 if (_nc_strict_bsd) {
633                     if (isdigit(UChar(xx1))) {
634                         int pad = 0;
635
636                         if (!isdigit(UChar(str[1])))
637                             pad = 2;
638                         else if (str[1] && !isdigit(UChar(str[2])))
639                             pad = 1;
640
641                         /*
642                          * Test for "\0", "\00" or "\000" and transform those
643                          * into "\200".
644                          */
645                         if (xx1 == '0'
646                             && ((pad == 2) || (str[1] == '0'))
647                             && ((pad >= 1) || (str[2] == '0'))) {
648                             xx2 = '2';
649                         } else {
650                             xx2 = '0';
651                             pad = 0;    /* FIXME - optionally pad to 3 digits */
652                         }
653                         while (pad-- > 0) {
654                             bufptr = save_char(bufptr, xx2);
655                             xx2 = '0';
656                         }
657                     } else if (strchr("E\\nrtbf", xx1) == 0) {
658                         switch (xx1) {
659                         case 'e':
660                             xx1 = 'E';
661                             break;
662                         case 'l':
663                             xx1 = 'n';
664                             break;
665                         case 's':
666                             bufptr = save_char(bufptr, '0');
667                             bufptr = save_char(bufptr, '4');
668                             xx1 = '0';
669                             break;
670                         case ':':
671                             /*
672                              * Note: termcap documentation claims that ":"
673                              * must be escaped as "\072", however the
674                              * documentation is incorrect - read the code.
675                              * The replacement does not work reliably,
676                              * so the advice is not helpful.
677                              */
678                             bufptr = save_char(bufptr, '0');
679                             bufptr = save_char(bufptr, '7');
680                             xx1 = '2';
681                             break;
682                         default:
683                             /* should not happen, but handle this anyway */
684                             _nc_SPRINTF(octal, _nc_SLIMIT(sizeof(octal))
685                                         "%03o", UChar(xx1));
686                             bufptr = save_char(bufptr, octal[0]);
687                             bufptr = save_char(bufptr, octal[1]);
688                             xx1 = octal[2];
689                             break;
690                         }
691                     }
692                 }
693                 bufptr = save_char(bufptr, xx1);
694             }
695         } else if (str[0] == '$' && str[1] == '<') {    /* discard padding */
696             str += 2;
697             while (isdigit(UChar(*str))
698                    || *str == '.'
699                    || *str == '*'
700                    || *str == '/'
701                    || *str == '>')
702                 str++;
703             --str;
704         } else if (sscanf(str,
705                           "[%%?%%p1%%{8}%%<%%t%d%%p1%%d%%e%%p1%%{16}%%<%%t%d%%p1%%{8}%%-%%d%%e%d;5;%%p1%%d%%;m",
706                           &in0, &in1, &in2) == 3
707                    && ((in0 == 4 && in1 == 10 && in2 == 48)
708                        || (in0 == 3 && in1 == 9 && in2 == 38))) {
709             /* dumb-down an optimized case from xterm-256color for termcap */
710             if ((str = strstr(str, ";m")) == 0)
711                 break;          /* cannot happen */
712             ++str;
713             if (in2 == 48) {
714                 bufptr = save_string(bufptr, "[48;5;%dm");
715             } else {
716                 bufptr = save_string(bufptr, "[38;5;%dm");
717             }
718         } else if (str[0] == '%' && str[1] == '%') {    /* escaped '%' */
719             bufptr = save_string(bufptr, "%%");
720             ++str;
721         } else if (*str != '%' || (parameterized < 1)) {
722             bufptr = save_char(bufptr, *str);
723         } else if (sscanf(str, "%%?%%{%d}%%>%%t%%{%d}%%+%%;", &c1, &c2) == 2) {
724             str = strchr(str, ';');
725             bufptr = save_tc_inequality(bufptr, c1, c2);
726         } else if (sscanf(str, "%%?%%{%d}%%>%%t%%'%c'%%+%%;", &c1, &ch2) == 2) {
727             str = strchr(str, ';');
728             bufptr = save_tc_inequality(bufptr, c1, ch2);
729         } else if (sscanf(str, "%%?%%'%c'%%>%%t%%{%d}%%+%%;", &ch1, &c2) == 2) {
730             str = strchr(str, ';');
731             bufptr = save_tc_inequality(bufptr, ch1, c2);
732         } else if (sscanf(str, "%%?%%'%c'%%>%%t%%'%c'%%+%%;", &ch1, &ch2) == 2) {
733             str = strchr(str, ';');
734             bufptr = save_tc_inequality(bufptr, ch1, ch2);
735         } else if ((len = bcd_expression(str)) != 0) {
736             str += len;
737             bufptr = save_string(bufptr, "%B");
738         } else if ((sscanf(str, "%%{%d}%%+%%c", &c1) == 1
739                     || sscanf(str, "%%'%c'%%+%%c", &ch1) == 1)
740                    && (cp = strchr(str, '+'))) {
741             str = cp + 2;
742             bufptr = save_string(bufptr, "%+");
743
744             if (ch1)
745                 c1 = ch1;
746             bufptr = save_tc_char(bufptr, c1);
747         }
748         /* FIXME: this "works" for 'delta' */
749         else if (strncmp(str, "%{2}%*%-", (size_t) 8) == 0) {
750             str += 7;
751             bufptr = save_string(bufptr, "%D");
752         } else if (strncmp(str, "%{96}%^", (size_t) 7) == 0) {
753             str += 6;
754             if (saw_m++ == 0) {
755                 bufptr = save_string(bufptr, "%n");
756             }
757         } else if (strncmp(str, "%{127}%^", (size_t) 8) == 0) {
758             str += 7;
759             if (saw_n++ == 0) {
760                 bufptr = save_string(bufptr, "%m");
761             }
762         } else {                /* cm-style format element */
763             str++;
764             switch (*str) {
765             case '%':
766                 bufptr = save_char(bufptr, '%');
767                 break;
768
769             case '0':
770             case '1':
771             case '2':
772             case '3':
773             case '4':
774             case '5':
775             case '6':
776             case '7':
777             case '8':
778             case '9':
779                 bufptr = save_char(bufptr, '%');
780                 ch1 = 0;
781                 ch2 = 0;
782                 while (isdigit(UChar(*str))) {
783                     ch2 = ch1;
784                     ch1 = *str++;
785                     if (_nc_strict_bsd) {
786                         if (ch1 > '3')
787                             return 0;
788                     } else {
789                         bufptr = save_char(bufptr, ch1);
790                     }
791                 }
792                 if (_nc_strict_bsd) {
793                     if (ch2 != 0 && ch2 != '0')
794                         return 0;
795                     if (ch1 < '2')
796                         ch1 = 'd';
797                     bufptr = save_char(bufptr, ch1);
798                 }
799                 if (strchr("doxX.", *str)) {
800                     if (*str != 'd')    /* termcap doesn't have octal, hex */
801                         return 0;
802                 }
803                 break;
804
805             case 'd':
806                 bufptr = save_string(bufptr, "%d");
807                 break;
808
809             case 'c':
810                 bufptr = save_string(bufptr, "%.");
811                 break;
812
813                 /*
814                  * %s isn't in termcap, but it's convenient to pass it through
815                  * so we can represent things like terminfo pfkey strings in
816                  * termcap notation.
817                  */
818             case 's':
819                 if (_nc_strict_bsd)
820                     return 0;
821                 bufptr = save_string(bufptr, "%s");
822                 break;
823
824             case 'p':
825                 str++;
826                 if (*str == '1')
827                     seenone = 1;
828                 else if (*str == '2') {
829                     if (!seenone && !seentwo) {
830                         bufptr = save_string(bufptr, "%r");
831                         seentwo++;
832                     }
833                 } else if (*str >= '3')
834                     return (0);
835                 break;
836
837             case 'i':
838                 bufptr = save_string(bufptr, "%i");
839                 break;
840
841             default:
842                 bufptr = save_char(bufptr, *str);
843                 syntax_error = TRUE;
844                 break;
845             }                   /* endswitch (*str) */
846         }                       /* endelse (*str == '%') */
847
848         /*
849          * 'str' always points to the end of what was scanned in this step,
850          * but that may not be the end of the string.
851          */
852         assert(str != 0);
853         if (str == 0 || *str == '\0')
854             break;
855
856     }                           /* endwhile (*str) */
857
858     return (syntax_error ? NULL : my_string);
859 }
860
861 #ifdef MAIN
862
863 int curr_line;
864
865 int
866 main(int argc, char *argv[])
867 {
868     int c, tc = FALSE;
869
870     while ((c = getopt(argc, argv, "c")) != EOF)
871         switch (c) {
872         case 'c':
873             tc = TRUE;
874             break;
875         }
876
877     curr_line = 0;
878     for (;;) {
879         char buf[BUFSIZ];
880
881         ++curr_line;
882         if (fgets(buf, sizeof(buf), stdin) == 0)
883             break;
884         buf[strlen(buf) - 1] = '\0';
885         _nc_set_source(buf);
886
887         if (tc) {
888             char *cp = _nc_infotocap("to termcap", buf, 1);
889
890             if (cp)
891                 (void) fputs(cp, stdout);
892         } else
893             (void) fputs(_nc_captoinfo("to terminfo", buf, 1), stdout);
894         (void) putchar('\n');
895     }
896     return (0);
897 }
898 #endif /* MAIN */
899
900 #if NO_LEAKS
901 NCURSES_EXPORT(void)
902 _nc_captoinfo_leaks(void)
903 {
904     if (my_string != 0) {
905         FreeAndNull(my_string);
906     }
907     my_length = 0;
908 }
909 #endif