exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / tparm.c
blob4340df481551756f6525ef3645123884ef4fbe6a
1 /* Substitution of parameters in strings from terminal descriptions.
2 Copyright (C) 2006-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation, either version 3 of the
7 License, or (at your option) any later version.
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Originally by Ross Ridge, Public Domain, 92/02/01 07:30:36 */
19 #include <config.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <string.h>
25 #include "attribute.h"
26 #include "c-ctype.h"
28 #ifdef USE_SCCS_IDS
29 static const char SCCSid[] = "@(#) mytinfo tparm.c 3.2 92/02/01 public domain, By Ross Ridge";
30 #endif
32 #ifndef MAX_PUSHED
33 #define MAX_PUSHED 32
34 #endif
36 #define ARG 1
37 #define NUM 2
39 #define INTEGER 1
40 #define STRING 2
42 #define MAX_LINE 640
44 typedef struct stack_str
46 int type;
47 int argnum;
48 int value;
49 } stack;
51 static stack S[MAX_PUSHED];
52 static stack vars['z'-'a'+1];
53 static int pos = 0;
55 static
56 struct arg_str
58 int type;
59 int integer;
60 char *string;
61 } arg_list[10];
63 static int argcnt;
65 static va_list tparm_args;
67 static int
68 pusharg (int arg)
70 if (pos == MAX_PUSHED)
71 return 1;
72 S[pos].type = ARG;
73 S[pos++].argnum = arg;
74 return 0;
77 static int
78 pushnum (int num)
80 if (pos == MAX_PUSHED)
81 return 1;
82 S[pos].type = NUM;
83 S[pos++].value = num;
84 return 0;
87 static int
88 getarg (int argnum, int type, void *p)
90 while (argcnt < argnum)
92 arg_list[argcnt].type = INTEGER;
93 arg_list[argcnt++].integer = (int) va_arg (tparm_args, int);
95 if (argcnt > argnum)
97 if (arg_list[argnum].type != type)
98 return 1;
99 else if (type == STRING)
100 *(char **)p = arg_list[argnum].string;
101 else
102 *(int *)p = arg_list[argnum].integer;
104 else
106 arg_list[argcnt].type = type;
107 if (type == STRING)
108 *(char **)p = arg_list[argcnt++].string = (char *) va_arg (tparm_args, char *);
109 else
110 *(int *)p = arg_list[argcnt++].integer = (int) va_arg (tparm_args, int);
112 return 0;
115 static int
116 popstring (char **str)
118 if (pos-- == 0)
119 return 1;
120 if (S[pos].type != ARG)
121 return 1;
122 return getarg (S[pos].argnum, STRING, str);
125 static int
126 popnum (int *num)
128 if (pos-- == 0)
129 return 1;
130 switch (S[pos].type)
132 case ARG:
133 return getarg (S[pos].argnum, INTEGER, num);
134 case NUM:
135 *num = S[pos].value;
136 return 0;
138 return 1;
141 static int
142 cvtchar (const char *sp, char *c)
144 switch (*sp)
146 case '\\':
147 switch (*++sp)
149 case '\'':
150 case '$':
151 case '\\':
152 case '%':
153 *c = *sp;
154 return 2;
155 case '\0':
156 *c = '\\';
157 return 1;
158 case '0':
159 if (sp[1] == '0' && sp[2] == '0')
161 *c = '\0';
162 return 4;
164 *c = '\200'; /* '\0' ???? */
165 return 2;
166 default:
167 *c = *sp;
168 return 2;
170 default:
171 *c = *sp;
172 return 1;
176 /* sigh... this has got to be the ugliest code I've ever written.
177 Trying to handle everything has its cost, I guess.
179 It actually isn't too hard to figure out if a given % code is supposed
180 to be interpreted with its termcap or terminfo meaning since almost
181 all terminfo codes are invalid unless something has been pushed on
182 the stack and termcap strings will never push things on the stack
183 (%p isn't used by termcap). So where we have a choice we make the
184 decision by whether or not something has been pushed on the stack.
185 The static variable termcap keeps track of this; it starts out set
186 to 1 and is incremented for each argument processed for a termcap % code,
187 however if something is pushed on the stack it's set to 0 and the
188 rest of the % codes are interpreted as terminfo % codes. Another way
189 of putting it is that if termcap equals one we haven't decided either
190 way yet, if it equals zero we're looking for terminfo codes, and if
191 its greater than 1 we're looking for termcap codes.
193 Terminfo % codes:
195 %% output a '%'
196 %[[:][-+# ][width][.precision]][doxXs]
197 output pop according to the printf format
198 %c output pop as a char
199 %'c' push character constant c.
200 %{n} push decimal constant n.
201 %p[1-9] push parameter [1-9]
202 %g[a-z] push variable [a-z]
203 %P[a-z] put pop in variable [a-z]
204 %l push the length of pop (a string)
205 %+ add pop to pop and push the result
206 %- subtract pop from pop and push the result
207 %* multiply pop and pop and push the result
208 %& bitwise and pop and pop and push the result
209 %| bitwise or pop and pop and push the result
210 %^ bitwise xor pop and pop and push the result
211 %~ push the bitwise not of pop
212 %= compare if pop and pop are equal and push the result
213 %> compare if pop is less than pop and push the result
214 %< compare if pop is greater than pop and push the result
215 %A logical and pop and pop and push the result
216 %O logical or pop and pop and push the result
217 %! push the logical not of pop
218 %? condition %t if_true [%e if_false] %;
219 if condition evaluates as true then evaluate if_true,
220 else evaluate if_false. elseif's can be done:
221 %? cond %t true [%e cond2 %t true2] ... [%e condN %t trueN] [%e false] %;
222 %i add one to parameters 1 and 2. (ANSI)
224 Termcap Codes:
226 %% output a %
227 %. output parameter as a character
228 %d output parameter as a decimal number
229 %2 output parameter in printf format %02d
230 %3 output parameter in printf format %03d
231 %+x add the character x to parameter and output it as a character
232 (UW) %-x subtract parameter FROM the character x and output it as a char
233 (UW) %ax add the character x to parameter
234 (GNU) %a[+*-/=][cp]x
235 GNU arithmetic.
236 (UW) %sx subtract parameter FROM the character x
237 %>xy if parameter > character x then add character y to parameter
238 %B convert to BCD (parameter = (parameter/10)*16 + parameter%16)
239 %D Delta Data encode (parameter = parameter - 2*(parameter%16))
240 %i increment the first two parameters by one
241 %n xor the first two parameters by 0140
242 (GNU) %m xor the first two parameters by 0177
243 %r swap the first two parameters
244 (GNU) %b backup to previous parameter
245 (GNU) %f skip this parameter
247 Note the two definitions of %a, the GNU definition is used if the characters
248 after the 'a' are valid, otherwise the UW definition is used.
250 (GNU) used by GNU Emacs termcap libraries
251 (UW) used by the University of Waterloo (MFCF) termcap libraries
255 char *
256 tparm (const char *str, ...)
258 static int termcap;
259 static char OOPS[] = "OOPS";
260 static char buf[MAX_LINE];
261 const char *sp;
262 char *dp;
263 const char *fmt;
264 char scan_for;
265 int scan_depth;
266 int if_depth;
267 char fmt_buf[MAX_LINE];
268 char sbuf[MAX_LINE];
270 va_start (tparm_args, str);
272 sp = str;
273 dp = buf;
274 scan_for = 0;
275 scan_depth = 0;
276 if_depth = 0;
277 argcnt = 0;
278 pos = 0;
279 termcap = 1;
280 while (*sp != '\0')
282 switch (*sp)
284 case '\\':
285 if (scan_for)
287 if (*++sp != '\0')
288 sp++;
289 break;
291 *dp++ = *sp++;
292 if (*sp != '\0')
293 *dp++ = *sp++;
294 break;
295 case '%':
296 sp++;
297 if (scan_for)
299 if (*sp == scan_for && if_depth == scan_depth)
301 if (scan_for == ';')
302 if_depth--;
303 scan_for = 0;
305 else if (*sp == '?')
306 if_depth++;
307 else if (*sp == ';')
309 if (if_depth == 0)
310 return OOPS;
311 else
312 if_depth--;
314 sp++;
315 break;
317 fmt = NULL;
318 switch (*sp)
320 case '%':
321 *dp++ = *sp++;
322 break;
323 case '+':
324 if (!termcap)
326 int i, j;
327 if (popnum (&j) || popnum (&i))
328 return OOPS;
329 i += j;
330 if (pushnum (i))
331 return OOPS;
332 sp++;
333 break;
335 FALLTHROUGH;
336 case 'C':
337 if (*sp == 'C')
339 int i;
340 if (getarg (termcap - 1, INTEGER, &i))
341 return OOPS;
342 if (i >= 96)
344 i /= 96;
345 if (i == '$')
346 *dp++ = '\\';
347 *dp++ = i;
350 fmt = "%c";
351 FALLTHROUGH;
352 case 'a':
353 if (!termcap)
354 return OOPS;
356 int i;
357 if (getarg (termcap - 1, INTEGER, &i))
358 return OOPS;
359 if (*++sp == '\0')
360 return OOPS;
361 if ((sp[1] == 'p' || sp[1] == 'c')
362 && sp[2] != '\0' && fmt == NULL)
364 /* GNU arithmetic parameter, what they really need is
365 terminfo. */
366 int val;
367 int lc;
368 if (sp[1] == 'p'
369 && getarg (termcap - 1 + sp[2] - '@', INTEGER, &val))
370 return OOPS;
371 if (sp[1] == 'c')
373 char c;
374 lc = cvtchar (sp + 2, &c) + 2;
375 /* Mask out 8th bit so \200 can be used for \0 as per
376 GNU docs. */
377 val = c & 0177;
379 else
380 lc = 2;
381 switch (sp[0])
383 case '=':
384 break;
385 case '+':
386 val = i + val;
387 break;
388 case '-':
389 val = i - val;
390 break;
391 case '*':
392 val = i * val;
393 break;
394 case '/':
395 val = i / val;
396 break;
397 default:
398 /* Not really GNU's %a after all... */
400 char c;
401 lc = cvtchar (sp, &c);
402 val = c + i;
404 break;
406 arg_list[termcap - 1].integer = val;
407 sp += lc;
408 break;
411 char c;
412 sp += cvtchar (sp, &c);
413 arg_list[termcap - 1].integer = c + i;
416 if (fmt == NULL)
417 break;
418 sp--;
419 FALLTHROUGH;
420 case '-':
421 if (!termcap)
423 int i, j;
424 if (popnum (&j) || popnum (&i))
425 return OOPS;
426 i -= j;
427 if (pushnum (i))
428 return OOPS;
429 sp++;
430 break;
432 fmt = "%c";
433 FALLTHROUGH;
434 case 's':
435 if (termcap && (fmt == NULL || *sp == '-'))
437 int i;
438 if (getarg (termcap - 1, INTEGER, &i))
439 return OOPS;
440 if (*++sp == '\0')
441 return OOPS;
443 char c;
444 sp += cvtchar (sp, &c);
445 arg_list[termcap - 1].integer = c - i;
447 if (fmt == NULL)
448 break;
449 sp--;
451 if (!termcap)
452 return OOPS;
453 FALLTHROUGH;
454 case '.':
455 if (termcap && fmt == NULL)
456 fmt = "%c";
457 FALLTHROUGH;
458 case 'd':
459 if (termcap && fmt == NULL)
460 fmt = "%d";
461 FALLTHROUGH;
462 case '2':
463 if (termcap && fmt == NULL)
464 fmt = "%02d";
465 FALLTHROUGH;
466 case '3':
467 if (termcap && fmt == NULL)
468 fmt = "%03d";
469 FALLTHROUGH;
470 case ':': case ' ': case '#': case 'u':
471 case 'x': case 'X': case 'o': case 'c':
472 case '0': case '1': case '4': case '5':
473 case '6': case '7': case '8': case '9':
474 if (fmt == NULL)
476 char *fmtp;
477 if (termcap)
478 return OOPS;
479 if (*sp == ':')
480 sp++;
481 fmtp = fmt_buf;
482 *fmtp++ = '%';
483 while (*sp != 's' && *sp != 'x' && *sp != 'X' && *sp != 'd'
484 && *sp != 'o' && *sp != 'c' && *sp != 'u')
486 if (*sp == '\0')
487 return OOPS;
488 *fmtp++ = *sp++;
490 *fmtp++ = *sp;
491 *fmtp = '\0';
492 fmt = fmt_buf;
495 char conv_char = fmt[strlen (fmt) - 1];
496 if (conv_char == 's')
498 char *s;
499 if (popstring (&s))
500 return OOPS;
501 sprintf (sbuf, fmt, s);
503 else
505 int i;
506 if (termcap)
508 if (getarg (termcap++ - 1, INTEGER, &i))
509 return OOPS;
511 else
512 if (popnum (&i))
513 return OOPS;
514 if (i == 0 && conv_char == 'c')
515 strcpy (sbuf, "\000");
516 else
517 sprintf (sbuf, fmt, i);
520 sp++;
521 fmt = sbuf;
522 while (*fmt != '\0')
524 if (*fmt == '$')
525 *dp++ = '\\';
526 *dp++ = *fmt++;
528 break;
529 case 'r':
531 int i;
532 if (!termcap || getarg (1, INTEGER, &i))
533 return OOPS;
534 arg_list[1].integer = arg_list[0].integer;
535 arg_list[0].integer = i;
537 sp++;
538 break;
539 case 'i':
541 int i;
542 if (getarg (1, INTEGER, &i) || arg_list[0].type != INTEGER)
543 return OOPS;
545 arg_list[1].integer++;
546 arg_list[0].integer++;
547 sp++;
548 break;
549 case 'n':
551 int i;
552 if (!termcap || getarg (1, INTEGER, &i))
553 return OOPS;
555 arg_list[0].integer ^= 0140;
556 arg_list[1].integer ^= 0140;
557 sp++;
558 break;
559 case '>':
560 if (!termcap)
562 int i, j;
563 if (popnum (&j) || popnum (&i))
564 return OOPS;
565 i = (i > j);
566 if (pushnum (i))
567 return OOPS;
568 sp++;
569 break;
572 int i;
573 if (getarg (termcap-1, INTEGER, &i))
574 return OOPS;
576 char c;
577 sp += cvtchar (sp, &c);
578 if (i > c)
580 sp += cvtchar (sp, &c);
581 arg_list[termcap-1].integer += c;
583 else
584 sp += cvtchar (sp, &c);
587 sp++;
588 break;
589 case 'B':
591 int i;
592 if (!termcap || getarg (termcap-1, INTEGER, &i))
593 return OOPS;
594 arg_list[termcap-1].integer = 16 * (i / 10) + i % 10;
596 sp++;
597 break;
598 case 'D':
600 int i;
601 if (!termcap || getarg (termcap-1, INTEGER, &i))
602 return OOPS;
603 arg_list[termcap-1].integer = i - 2 * (i % 16);
605 sp++;
606 break;
607 case 'p':
608 if (termcap > 1)
609 return OOPS;
610 if (*++sp == '\0')
611 return OOPS;
613 int i = (*sp == '0' ? 9 : *sp - '1');
614 if (i < 0 || i > 9)
615 return OOPS;
616 if (pusharg (i))
617 return OOPS;
619 termcap = 0;
620 sp++;
621 break;
622 case 'P':
623 if (termcap || *++sp == '\0')
624 return OOPS;
626 int i = *sp++ - 'a';
627 if (i < 0 || i > 25)
628 return OOPS;
629 if (pos-- == 0)
630 return OOPS;
631 switch (vars[i].type = S[pos].type)
633 case ARG:
634 vars[i].argnum = S[pos].argnum;
635 break;
636 case NUM:
637 vars[i].value = S[pos].value;
638 break;
641 break;
642 case 'g':
643 if (termcap || *++sp == '\0')
644 return OOPS;
646 int i = *sp++ - 'a';
647 if (i < 0 || i > 25)
648 return OOPS;
649 switch (vars[i].type)
651 case ARG:
652 if (pusharg (vars[i].argnum))
653 return OOPS;
654 break;
655 case NUM:
656 if (pushnum (vars[i].value))
657 return OOPS;
658 break;
661 break;
662 case '\'':
663 if (termcap > 1)
664 return OOPS;
665 if (*++sp == '\0')
666 return OOPS;
668 char c;
669 sp += cvtchar (sp, &c);
670 if (pushnum (c) || *sp++ != '\'')
671 return OOPS;
673 termcap = 0;
674 break;
675 case '{':
676 if (termcap > 1)
677 return OOPS;
679 int i;
680 i = 0;
681 sp++;
682 while (c_isdigit (*sp))
683 i = 10 * i + *sp++ - '0';
684 if (*sp++ != '}' || pushnum (i))
685 return OOPS;
687 termcap = 0;
688 break;
689 case 'l':
691 int i;
692 char *s;
693 if (termcap || popstring (&s))
694 return OOPS;
695 i = strlen (s);
696 if (pushnum (i))
697 return OOPS;
699 sp++;
700 break;
701 case '*':
703 int i, j;
704 if (termcap || popnum (&j) || popnum (&i))
705 return OOPS;
706 i *= j;
707 if (pushnum (i))
708 return OOPS;
710 sp++;
711 break;
712 case '/':
714 int i, j;
715 if (termcap || popnum (&j) || popnum (&i))
716 return OOPS;
717 i /= j;
718 if (pushnum (i))
719 return OOPS;
721 sp++;
722 break;
723 case 'm':
724 if (termcap)
726 int i;
727 if (getarg (1, INTEGER, &i))
728 return OOPS;
729 arg_list[0].integer ^= 0177;
730 arg_list[1].integer ^= 0177;
731 sp++;
732 break;
735 int i, j;
736 if (popnum (&j) || popnum (&i))
737 return OOPS;
738 i %= j;
739 if (pushnum (i))
740 return OOPS;
742 sp++;
743 break;
744 case '&':
746 int i, j;
747 if (popnum (&j) || popnum (&i))
748 return OOPS;
749 i &= j;
750 if (pushnum (i))
751 return OOPS;
753 sp++;
754 break;
755 case '|':
757 int i, j;
758 if (popnum (&j) || popnum (&i))
759 return OOPS;
760 i |= j;
761 if (pushnum (i))
762 return OOPS;
764 sp++;
765 break;
766 case '^':
768 int i, j;
769 if (popnum (&j) || popnum (&i))
770 return OOPS;
771 i ^= j;
772 if (pushnum (i))
773 return OOPS;
775 sp++;
776 break;
777 case '=':
779 int i, j;
780 if (popnum (&j) || popnum (&i))
781 return OOPS;
782 i = (i == j);
783 if (pushnum (i))
784 return OOPS;
786 sp++;
787 break;
788 case '<':
790 int i, j;
791 if (popnum (&j) || popnum (&i))
792 return OOPS;
793 i = (i < j);
794 if (pushnum (i))
795 return OOPS;
797 sp++;
798 break;
799 case 'A':
801 int i, j;
802 if (popnum (&j) || popnum (&i))
803 return OOPS;
804 i = (i && j);
805 if (pushnum (i))
806 return OOPS;
808 sp++;
809 break;
810 case 'O':
812 int i, j;
813 if (popnum (&j) || popnum (&i))
814 return OOPS;
815 i = (i || j);
816 if (pushnum (i))
817 return OOPS;
819 sp++;
820 break;
821 case '!':
823 int i;
824 if (popnum (&i))
825 return OOPS;
826 i = !i;
827 if (pushnum (i))
828 return OOPS;
830 sp++;
831 break;
832 case '~':
834 int i;
835 if (popnum (&i))
836 return OOPS;
837 i = ~i;
838 if (pushnum (i))
839 return OOPS;
841 sp++;
842 break;
843 case '?':
844 if (termcap > 1)
845 return OOPS;
846 termcap = 0;
847 if_depth++;
848 sp++;
849 break;
850 case 't':
852 int i;
853 if (popnum (&i) || if_depth == 0)
854 return OOPS;
855 if (!i)
857 scan_for = 'e';
858 scan_depth = if_depth;
861 sp++;
862 break;
863 case 'e':
864 if (if_depth == 0)
865 return OOPS;
866 scan_for = ';';
867 scan_depth = if_depth;
868 sp++;
869 break;
870 case ';':
871 if (if_depth-- == 0)
872 return OOPS;
873 sp++;
874 break;
875 case 'b':
876 if (--termcap < 1)
877 return OOPS;
878 sp++;
879 break;
880 case 'f':
881 if (!termcap++)
882 return OOPS;
883 sp++;
884 break;
886 break;
887 default:
888 if (scan_for)
889 sp++;
890 else
891 *dp++ = *sp++;
892 break;
895 va_end (tparm_args);
896 *dp = '\0';
897 return buf;