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