xref: /openbsd/lib/libcurses/tinfo/lib_tparm.c (revision 92dd1ec0)
1 /*	$OpenBSD: lib_tparm.c,v 1.1 1999/01/18 19:10:20 millert Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998 Free Software Foundation, Inc.                        *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  ****************************************************************************/
35 
36 
37 /*
38  *	tparm.c
39  *
40  */
41 
42 #include <curses.priv.h>
43 
44 #include <ctype.h>
45 #include <term.h>
46 #include <tic.h>
47 
48 MODULE_ID("$From: lib_tparm.c,v 1.37 1999/01/02 22:38:25 tom Exp $")
49 
50 /*
51  *	char *
52  *	tparm(string, ...)
53  *
54  *	Substitute the given parameters into the given string by the following
55  *	rules (taken from terminfo(5)):
56  *
57  *	     Cursor addressing and other strings  requiring  parame-
58  *	ters in the terminal are described by a parameterized string
59  *	capability, with like escapes %x in  it.   For  example,  to
60  *	address  the  cursor, the cup capability is given, using two
61  *	parameters: the row and column to  address  to.   (Rows  and
62  *	columns  are  numbered  from  zero and refer to the physical
63  *	screen visible to the user, not to any  unseen  memory.)  If
64  *	the terminal has memory relative cursor addressing, that can
65  *	be indicated by
66  *
67  *	     The parameter mechanism uses  a  stack  and  special  %
68  *	codes  to manipulate it.  Typically a sequence will push one
69  *	of the parameters onto the stack and then print it  in  some
70  *	format.  Often more complex operations are necessary.
71  *
72  *	     The % encodings have the following meanings:
73  *
74  *	     %%        outputs `%'
75  *	     %c        print pop() like %c in printf()
76  *	     %s        print pop() like %s in printf()
77  *           %[[:]flags][width[.precision]][doxXs]
78  *                     as in printf, flags are [-+#] and space
79  *
80  *	     %p[1-9]   push ith parm
81  *	     %P[a-z]   set dynamic variable [a-z] to pop()
82  *	     %g[a-z]   get dynamic variable [a-z] and push it
83  *	     %P[A-Z]   set static variable [A-Z] to pop()
84  *	     %g[A-Z]   get static variable [A-Z] and push it
85  *	     %l        push strlen(pop)
86  *	     %'c'      push char constant c
87  *	     %{nn}     push integer constant nn
88  *
89  *	     %+ %- %* %/ %m
90  *	               arithmetic (%m is mod): push(pop() op pop())
91  *	     %& %| %^  bit operations: push(pop() op pop())
92  *	     %= %> %<  logical operations: push(pop() op pop())
93  *	     %A %O     logical and & or operations for conditionals
94  *	     %! %~     unary operations push(op pop())
95  *	     %i        add 1 to first two parms (for ANSI terminals)
96  *
97  *	     %? expr %t thenpart %e elsepart %;
98  *	               if-then-else, %e elsepart is optional.
99  *	               else-if's are possible ala Algol 68:
100  *	               %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
101  *
102  *	For those of the above operators which are binary and not commutative,
103  *	the stack works in the usual way, with
104  *			%gx %gy %m
105  *	resulting in x mod y, not the reverse.
106  */
107 
108 #define STACKSIZE	20
109 
110 typedef union {
111 	unsigned int	num;
112 	char	       *str;
113 } stack_frame;
114 
115 static  stack_frame	stack[STACKSIZE];
116 static	int	stack_ptr;
117 #ifdef TRACE
118 static const char *tname;
119 #endif /* TRACE */
120 
121 static char  *out_buff;
122 static size_t out_size;
123 static size_t out_used;
124 
125 #if NO_LEAKS
126 void _nc_free_tparm(void)
127 {
128 	if (out_buff != 0) {
129 		FreeAndNull(out_buff);
130 		out_size = 0;
131 		out_used = 0;
132 	}
133 }
134 #endif
135 
136 static void really_get_space(size_t need)
137 {
138 	out_size = need * 2;
139 	out_buff = (char *)_nc_doalloc(out_buff, out_size);
140 	if (out_buff == 0)
141 		_nc_err_abort("Out of memory");
142 }
143 
144 static inline void get_space(size_t need)
145 {
146 	need += out_used;
147 	if (need > out_size)
148 		really_get_space(need);
149 }
150 
151 static inline void save_text(const char *fmt, char *s, int len)
152 {
153 	size_t s_len = strlen(s);
154 	if (len > (int)s_len)
155 		s_len = len;
156 
157 	get_space(s_len + 1);
158 
159 	(void)sprintf(out_buff+out_used, fmt, s);
160 	out_used += strlen(out_buff+out_used);
161 }
162 
163 static inline void save_number(const char *fmt, int number, int len)
164 {
165 	if (len < 30)
166 		len = 30; /* actually log10(MAX_INT)+1 */
167 
168 	get_space(len + 1);
169 
170 	(void)sprintf(out_buff+out_used, fmt, number);
171 	out_used += strlen(out_buff+out_used);
172 }
173 
174 static inline void save_char(int c)
175 {
176 	if (c == 0)
177 		c = 0200;
178 	get_space(1);
179 	out_buff[out_used++] = c;
180 }
181 
182 static inline void npush(int x)
183 {
184 	if (stack_ptr < STACKSIZE) {
185 		stack[stack_ptr].num = x;
186 		stack_ptr++;
187 	}
188 }
189 
190 static inline int npop(void)
191 {
192 	return   (stack_ptr > 0  ?  stack[--stack_ptr].num  :  0);
193 }
194 
195 static inline char *spop(void)
196 {
197 	static char dummy[] = "";	/* avoid const-cast */
198 	return   (stack_ptr > 0  ?  stack[--stack_ptr].str  :  dummy);
199 }
200 
201 static inline const char *parse_format(const char *s, char *format, int *len)
202 {
203 	bool done = FALSE;
204 	bool allowminus = FALSE;
205 	bool dot = FALSE;
206 	int prec  = 0;
207 	int width = 0;
208 
209 	*len = 0;
210 	*format++ = '%';
211 	while (*s != '\0' && !done) {
212 		switch (*s) {
213 		case 'c':	/* FALLTHRU */
214 		case 'd':	/* FALLTHRU */
215 		case 'o':	/* FALLTHRU */
216 		case 'x':	/* FALLTHRU */
217 		case 'X':	/* FALLTHRU */
218 		case 's':
219 			*format++ = *s;
220 			done = TRUE;
221 			break;
222 		case '.':
223 			*format++ = *s++;
224 			dot = TRUE;
225 			break;
226 		case '#':
227 			*format++ = *s++;
228 			break;
229 		case ' ':
230 			*format++ = *s++;
231 			break;
232 		case ':':
233 			s++;
234 			allowminus = TRUE;
235 			break;
236 		case '-':
237 			if (allowminus) {
238 				*format++ = *s++;
239 			} else {
240 				done = TRUE;
241 			}
242 			break;
243 		default:
244 			if (isdigit(*s)) {
245 				if (dot)
246 					prec  = (prec * 10) + (*s - '0');
247 				else
248 					width = (width * 10) + (*s - '0');
249 				*format++ = *s++;
250 			} else {
251 				done = TRUE;
252 			}
253 		}
254 	}
255 	*format = '\0';
256 	/* return maximum string length in print */
257 	*len = (prec > width) ? prec : width;
258 	return s;
259 }
260 
261 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
262 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
263 
264 static inline char *tparam_internal(const char *string, va_list ap)
265 {
266 #define NUM_VARS 26
267 int	param[9];
268 int	popcount;
269 int	number;
270 int	len;
271 int	level;
272 int	x, y;
273 int	i;
274 register const char *cp;
275 static	size_t len_fmt;
276 static	char *format;
277 static	int dynamic_var[NUM_VARS];
278 static	int static_vars[NUM_VARS];
279 
280 	out_used = 0;
281 	if (string == NULL)
282 		return NULL;
283 
284 	/*
285 	 * Find the highest parameter-number referred to in the format string.
286 	 * Use this value to limit the number of arguments copied from the
287 	 * variable-length argument list.
288 	 */
289 	for (cp = string, popcount = number = 0; *cp != '\0'; cp++) {
290 		if (cp[0] == '%' && cp[1] != '\0') {
291 			switch (cp[1]) {
292 			case '%':
293 				cp++;
294 				break;
295 			case 'i':
296 				if (popcount < 2)
297 					popcount = 2;
298 				break;
299 			case 'p':
300 				cp++;
301 				if (cp[1] >= '1' && cp[1] <= '9') {
302 					int c = cp[1] - '0';
303 					if (c > popcount)
304 						popcount = c;
305 				}
306 				break;
307 			case '0': case '1': case '2': case '3': case '4':
308 			case '5': case '6': case '7': case '8': case '9':
309 			case 'd': case 'c': case 's':
310 				++number;
311 				break;
312 			}
313 		}
314 	}
315 	if ((size_t)(cp - string) > len_fmt) {
316 		len_fmt = (cp - string) + len_fmt + 2;
317 		if ((format = _nc_doalloc(format, len_fmt)) == 0)
318 			return 0;
319 	}
320 
321 	if (number > 9) number = 9;
322 	for (i = 0; i < max(popcount, number); i++) {
323 		/*
324 		 * FIXME: potential loss here if sizeof(int) != sizeof(char *).
325 		 * A few caps (such as plab_norm) have string-valued parms.
326 		 */
327 		param[i] = va_arg(ap, int);
328 	}
329 
330 	/*
331 	 * This is a termcap compatibility hack.  If there are no explicit pop
332 	 * operations in the string, load the stack in such a way that
333 	 * successive pops will grab successive parameters.  That will make
334 	 * the expansion of (for example) \E[%d;%dH work correctly in termcap
335 	 * style, which means tparam() will expand termcap strings OK.
336 	 */
337 	stack_ptr = 0;
338 	if (popcount == 0) {
339 		popcount = number;
340 		for (i = number - 1; i >= 0; i--)
341 			npush(param[i]);
342 	}
343 
344 #ifdef TRACE
345 	if (_nc_tracing & TRACE_CALLS) {
346 		for (i = 0; i < popcount; i++)
347 			save_number(", %d", param[i], 0);
348 		_tracef(T_CALLED("%s(%s%s)"), tname, _nc_visbuf(string), out_buff);
349 		out_used = 0;
350 	}
351 #endif /* TRACE */
352 
353 	while (*string) {
354 		if (*string != '%') {
355 			save_char(*string);
356 		} else {
357 			string++;
358 			string = parse_format(string, format, &len);
359 			switch (*string) {
360 			default:
361 				break;
362 			case '%':
363 				save_char('%');
364 				break;
365 
366 			case 'd':	/* FALLTHRU */
367 			case 'o':	/* FALLTHRU */
368 			case 'x':	/* FALLTHRU */
369 			case 'X':	/* FALLTHRU */
370 			case 'c':
371 				save_number(format, npop(), len);
372 				break;
373 
374 			case 'l':
375 				save_number("%d", strlen(spop()), 0);
376 				break;
377 
378 			case 's':
379 				save_text(format, spop(), len);
380 				break;
381 
382 			case 'p':
383 				string++;
384 				if (*string >= '1'  &&  *string <= '9')
385 					npush(param[*string - '1']);
386 				break;
387 
388 			case 'P':
389 				string++;
390 				if (isUPPER(*string)) {
391 					i = (*string - 'A');
392 					static_vars[i] = npop();
393 				} else if (isLOWER(*string)) {
394 					i = (*string - 'a');
395 					dynamic_var[i] = npop();
396 				}
397 				break;
398 
399 			case 'g':
400 				string++;
401 				if (isUPPER(*string)) {
402 					i = (*string - 'A');
403 					npush(static_vars[i]);
404 				} else if (isLOWER(*string)) {
405 					i = (*string - 'a');
406 					npush(dynamic_var[i]);
407 				}
408 				break;
409 
410 			case S_QUOTE:
411 				string++;
412 				npush(*string);
413 				string++;
414 				break;
415 
416 			case L_BRACE:
417 				number = 0;
418 				string++;
419 				while (*string >= '0'  &&  *string <= '9') {
420 					number = number * 10 + *string - '0';
421 					string++;
422 				}
423 				npush(number);
424 				break;
425 
426 			case '+':
427 				npush(npop() + npop());
428 				break;
429 
430 			case '-':
431 				y = npop();
432 				x = npop();
433 				npush(x - y);
434 				break;
435 
436 			case '*':
437 				npush(npop() * npop());
438 				break;
439 
440 			case '/':
441 				y = npop();
442 				x = npop();
443 				npush(x / y);
444 				break;
445 
446 			case 'm':
447 				y = npop();
448 				x = npop();
449 				npush(x % y);
450 				break;
451 
452 			case 'A':
453 				npush(npop() && npop());
454 				break;
455 
456 			case 'O':
457 				npush(npop() || npop());
458 				break;
459 
460 			case '&':
461 				npush(npop() & npop());
462 				break;
463 
464 			case '|':
465 				npush(npop() | npop());
466 				break;
467 
468 			case '^':
469 				npush(npop() ^ npop());
470 				break;
471 
472 			case '=':
473 				y = npop();
474 				x = npop();
475 				npush(x == y);
476 				break;
477 
478 			case '<':
479 				y = npop();
480 				x = npop();
481 				npush(x < y);
482 				break;
483 
484 			case '>':
485 				y = npop();
486 				x = npop();
487 				npush(x > y);
488 				break;
489 
490 			case '!':
491 				npush(! npop());
492 				break;
493 
494 			case '~':
495 				npush(~ npop());
496 				break;
497 
498 			case 'i':
499 				param[0]++;
500 				param[1]++;
501 				break;
502 
503 			case '?':
504 				break;
505 
506 			case 't':
507 				x = npop();
508 				if (!x) {
509 					/* scan forward for %e or %; at level zero */
510 					string++;
511 					level = 0;
512 					while (*string) {
513 						if (*string == '%') {
514 							string++;
515 							if (*string == '?')
516 								level++;
517 							else if (*string == ';') {
518 								if (level > 0)
519 									level--;
520 								else
521 									break;
522 							}
523 							else if (*string == 'e'  && level == 0)
524 								break;
525 						}
526 
527 						if (*string)
528 							string++;
529 					}
530 				}
531 				break;
532 
533 			case 'e':
534 				/* scan forward for a %; at level zero */
535 				string++;
536 				level = 0;
537 				while (*string) {
538 					if (*string == '%') {
539 						string++;
540 						if (*string == '?')
541 							level++;
542 						else if (*string == ';') {
543 							if (level > 0)
544 								level--;
545 							else
546 								break;
547 						}
548 					}
549 
550 					if (*string)
551 						string++;
552 				}
553 				break;
554 
555 			case ';':
556 				break;
557 
558 			} /* endswitch (*string) */
559 		} /* endelse (*string == '%') */
560 
561 		if (*string == '\0')
562 			break;
563 
564 		string++;
565 	} /* endwhile (*string) */
566 
567 	if (out_buff == 0 && (out_buff = calloc(1,1)) == NULL)
568 		return(NULL);
569 	out_buff[out_used] = '\0';
570 
571 	T((T_RETURN("%s"), _nc_visbuf(out_buff)));
572 	return(out_buff);
573 }
574 
575 char *tparm(NCURSES_CONST char *string, ...)
576 {
577 va_list	ap;
578 char *result;
579 
580 	va_start(ap, string);
581 #ifdef TRACE
582 	tname = "tparm";
583 #endif /* TRACE */
584 	result = tparam_internal(string, ap);
585 	va_end(ap);
586 	return result;
587 }
588