xref: /netbsd-src/lib/libterminfo/tparm.c (revision b757af438b42b93f8c6571f026d8b8ef3eaf5fc9)
1 /* $NetBSD: tparm.c,v 1.7 2011/10/03 20:13:48 roy Exp $ */
2 
3 /*
4  * Copyright (c) 2009, 2011 The NetBSD Foundation, Inc.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Roy Marples.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: tparm.c,v 1.7 2011/10/03 20:13:48 roy Exp $");
32 
33 #include <assert.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <term_private.h>
41 #include <term.h>
42 
43 static TERMINAL *dumbterm; /* For non thread safe functions */
44 
45 typedef struct {
46 	int nums[20];
47 	char *strings[20];
48 	size_t offset;
49 } TPSTACK;
50 
51 typedef struct {
52 	int num;
53 	char *string;
54 } TPVAR;
55 
56 static int
57 push(int num, char *string, TPSTACK *stack)
58 {
59 	if (stack->offset > sizeof(stack->nums)) {
60 		errno = E2BIG;
61 		return -1;
62 	}
63 	stack->nums[stack->offset] = num;
64 	stack->strings[stack->offset] = string;
65 	stack->offset++;
66 	return 0;
67 }
68 
69 static int
70 pop(int *num, char **string, TPSTACK *stack)
71 {
72 	if (stack->offset == 0) {
73 		if (num)
74 			*num = 0;
75 		if (string)
76 			*string = NULL;
77 		errno = E2BIG;
78 		return -1;
79 	}
80 	stack->offset--;
81 	if (num)
82 		*num = stack->nums[stack->offset];
83 	if (string)
84 		*string = stack->strings[stack->offset];
85 	return 0;
86 }
87 
88 static char *
89 checkbuf(TERMINAL *term, size_t len)
90 {
91 	char *buf;
92 
93 	if (term->_bufpos + len >= term->_buflen) {
94 		len = term->_buflen + BUFSIZ;
95 		buf = realloc(term->_buf, len);
96 		if (buf == NULL)
97 			return 0;
98 		term->_buf = buf;
99 		term->_buflen = len;
100 	}
101 	return term->_buf;
102 }
103 
104 static size_t
105 ochar(TERMINAL *term, int c)
106 {
107 	if (c == 0)
108 		c = 0200;
109 	/* Check we have space and a terminator */
110 	if (checkbuf(term, 2) == NULL)
111 		return 0;
112 	term->_buf[term->_bufpos++] = (char)c;
113 	return 1;
114 }
115 
116 static size_t
117 onum(TERMINAL *term, const char *fmt, int num, int len)
118 {
119 	size_t l;
120 
121 	/* Assume we never have natural number longer than 64 chars */
122 	if (len < 64)
123 		len = 64;
124 	if (checkbuf(term, (size_t)len + 1) == NULL)
125 		return 0;
126 	l = sprintf(term->_buf + term->_bufpos, fmt, num);
127 	term->_bufpos += l;
128 	return l;
129 }
130 
131 static char *
132 _ti_tiparm(TERMINAL *term, const char *str, va_list parms)
133 {
134 	const char *sp;
135 	char c, fmt[64], *fp, *ostr;
136 	int val, val2;
137 	int dnums[26]; /* dynamic variables a-z, not preserved */
138 	size_t l, max;
139 	TPSTACK stack;
140 	TPVAR params[9];
141 	int done, dot, minus, width, precision, olen;
142 	int piss[9]; /* Parameter IS String - piss ;) */
143 
144 	if (str == NULL)
145 		return NULL;
146 
147 	/*
148 	  If not passed a terminal, malloc a dummy one.
149 	  This means we can preserve buffers and variables per terminal and
150 	  still work with non thread safe functions (which sadly are still the
151 	  norm and standard).
152 	*/
153 
154 	if (term == NULL) {
155 		if (dumbterm == NULL) {
156 			dumbterm = malloc(sizeof(*dumbterm));
157 			if (dumbterm == NULL)
158 				return NULL;
159 			dumbterm->_buflen = 0;
160 		}
161 		term = dumbterm;
162 	}
163 
164 	term->_bufpos = 0;
165 	/* Ensure we have an initial buffer */
166 	if (term->_buflen == 0) {
167 		term->_buf = malloc(BUFSIZ);
168 		if (term->_buf == NULL)
169 			return NULL;
170 		term->_buflen = BUFSIZ;
171 	}
172 
173 	/*
174 	  Make a first pass through the string so we can work out
175 	  which parameters are ints and which are char *.
176 	  Basically we only use char * if %p[1-9] is followed by %l or %s.
177 	*/
178 	memset(&piss, 0, sizeof(piss));
179 	max = 0;
180 	sp = str;
181 	while ((c = *sp++) != '\0') {
182 		if (c != '%')
183 			continue;
184 		c = *sp++;
185 		if (c == '\0')
186 			break;
187 		if (c != 'p')
188 			continue;
189 		c = *sp++;
190 		if (c < '1' || c > '9') {
191 			errno = EINVAL;
192 			continue;
193 		}
194 		l = c - '0';
195 		if (l > max)
196 			max = l;
197 		if (*sp != '%')
198 			continue;
199 		/* Skip formatting */
200 		sp++;
201 		while (*sp == '.' || *sp == '#' || *sp == ' ' || *sp == ':' ||
202 		    *sp == '-' || isdigit((unsigned char)*sp))
203 			sp++;
204 		if (*sp == 'l' || *sp == 's')
205 			piss[l - 1] = 1;
206 	}
207 
208 	/* Put our parameters into variables */
209 	memset(&params, 0, sizeof(params));
210 	for (l = 0; l < max; l++) {
211 		if (piss[l] == 0)
212 			params[l].num = va_arg(parms, int);
213 		else
214 			params[l].string = va_arg(parms, char *);
215 	}
216 
217 	term->_bufpos = 0;
218 	memset(&stack, 0, sizeof(stack));
219 	while ((c = *str++) != '\0') {
220 		if (c != '%' || (c = *str++) == '%') {
221 			if (c == '\0')
222 				break;
223 			if (ochar(term, c) == 0)
224 				return NULL;
225 			continue;
226 		}
227 
228 		/* Handle formatting. */
229 		fp = fmt;
230 		*fp++ = '%';
231 		done = dot = minus = width = precision = 0;
232 		val = 0;
233 		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
234 			switch (c) {
235 			case 'c': /* FALLTHROUGH */
236 			case 'd': /* FALLTHROUGH */
237 			case 'o': /* FALLTHROUGH */
238 			case 'x': /* FALLTHROUGH */
239 			case 'X': /* FALLTHROUGH */
240 			case 's':
241 				*fp++ = c;
242 				done = 1;
243 				break;
244 			case '#': /* FALLTHROUGH */
245 			case ' ':
246 				*fp++ = c;
247 				break;
248 			case '.':
249 				*fp++ = c;
250 				if (dot == 0) {
251 					dot = 1;
252 					width = val;
253 				} else
254 					done = 2;
255 				val = 0;
256 				break;
257 			case ':':
258 				minus = 1;
259 				break;
260 			case '-':
261 				if (minus)
262 					*fp++ = c;
263 				else
264 					done = 1;
265 				break;
266 			default:
267 				if (isdigit((unsigned char)c)) {
268 					val = (val * 10) + (c - '0');
269 					if (val > 10000)
270 						done = 2;
271 					else
272 						*fp++ = c;
273 				} else
274 					done = 1;
275 			}
276 			if (done == 0)
277 				c = *str++;
278 		}
279 		if (done == 2) {
280 			/* Found an error in the format */
281 			fp = fmt + 1;
282 			*fp = *str;
283 			olen = 0;
284 		} else {
285 			if (dot == 0)
286 				width = val;
287 			else
288 				precision = val;
289 			olen = (width > precision) ? width : precision;
290 		}
291 		*fp++ = '\0';
292 
293 		/* Handle commands */
294 		switch (c) {
295 		case 'c':
296 			pop(&val, NULL, &stack);
297 			if (ochar(term, (unsigned char)val) == 0)
298 				return NULL;
299 			break;
300 		case 's':
301 			pop(NULL, &ostr, &stack);
302 			if (ostr != NULL) {
303 				l = strlen(ostr);
304 				if (l < (size_t)olen)
305 					l = olen;
306 				if (checkbuf(term, (size_t)(l + 1)) == NULL)
307 					return NULL;
308 				l = sprintf(term->_buf + term->_bufpos,
309 				    fmt, ostr);
310 				term->_bufpos += l;
311 			}
312 			break;
313 		case 'l':
314 			pop(NULL, &ostr, &stack);
315 			if (ostr == NULL)
316 				l = 0;
317 			else
318 				l = strlen(ostr);
319 			if (onum(term, "%d", (int)l, 0) == 0)
320 				return NULL;
321 			break;
322 		case 'd': /* FALLTHROUGH */
323 		case 'o': /* FALLTHROUGH */
324 		case 'x': /* FALLTHROUGH */
325 		case 'X':
326 			pop(&val, NULL, &stack);
327 			if (onum(term, fmt, val, olen) == 0)
328 				return NULL;
329 			break;
330 		case 'p':
331 			if (*str < '1' || *str > '9')
332 				break;
333 			l = *str++ - '1';
334 			if (push(params[l].num, params[l].string, &stack))
335 				return NULL;
336 			break;
337 		case 'P':
338 			pop(&val, NULL, &stack);
339 			if (*str >= 'a' && *str <= 'z')
340 				dnums[*str - 'a'] = val;
341 			else if (*str >= 'A' && *str <= 'Z')
342 				term->_snums[*str - 'A'] = val;
343 			break;
344 		case 'g':
345 			if (*str >= 'a' && *str <= 'z') {
346 				if (push(dnums[*str - 'a'], NULL, &stack))
347 					return NULL;
348 			} else if (*str >= 'A' && *str <= 'Z') {
349 				if (push(term->_snums[*str - 'A'],
350 					NULL, &stack))
351 					return NULL;
352 			}
353 			break;
354 		case 'i':
355 			if (piss[0] == 0)
356 				params[0].num++;
357 			if (piss[1] == 0)
358 				params[1].num++;
359 			break;
360 		case '\'':
361 			if (push((int)(unsigned char)*str++, NULL, &stack))
362 				return NULL;
363 			while (*str != '\0' && *str != '\'')
364 				str++;
365 			if (*str == '\'')
366 				str++;
367 			break;
368 		case '{':
369 			val = 0;
370 			for (; isdigit((unsigned char)*str);  str++)
371 				val = (val * 10) + (*str - '0');
372 			if (push(val, NULL, &stack))
373 				return NULL;
374 			while (*str != '\0' && *str != '}')
375 				str++;
376 			if (*str == '}')
377 				str++;
378 			break;
379 		case '+': /* FALLTHROUGH */
380 		case '-': /* FALLTHROUGH */
381 		case '*': /* FALLTHROUGH */
382 		case '/': /* FALLTHROUGH */
383 		case 'm': /* FALLTHROUGH */
384 		case 'A': /* FALLTHROUGH */
385 		case 'O': /* FALLTHROUGH */
386 		case '&': /* FALLTHROUGH */
387 		case '|': /* FALLTHROUGH */
388 		case '^': /* FALLTHROUGH */
389 		case '=': /* FALLTHROUGH */
390 		case '<': /* FALLTHROUGH */
391 		case '>':
392 			pop(&val, NULL, &stack);
393 			pop(&val2, NULL, &stack);
394 			switch (c) {
395 			case '+':
396 				val = val + val2;
397 				break;
398 			case '-':
399 				val = val2 - val;
400 				break;
401 			case '*':
402 				val = val * val2;
403 				break;
404 			case '/':
405 				val = val ? val2 / val : 0;
406 				break;
407 			case 'm':
408 				val = val ? val2 % val : 0;
409 				break;
410 			case 'A':
411 				val = val && val2;
412 				break;
413 			case 'O':
414 				val = val || val2;
415 				break;
416 			case '&':
417 				val = val & val2;
418 				break;
419 			case '|':
420 				val = val | val2;
421 				break;
422 			case '^':
423 				val = val ^ val2;
424 				break;
425 			case '=':
426 				val = val == val2;
427 				break;
428 			case '<':
429 				val = val2 < val;
430 				break;
431 			case '>':
432 				val = val2 > val;
433 				break;
434 			}
435 			if (push(val, NULL, &stack))
436 				return NULL;
437 			break;
438 		case '!':
439 		case '~':
440 			pop(&val, NULL, &stack);
441 			switch (*str) {
442 			case '!':
443 				val = !val;
444 				break;
445 			case '~':
446 				val = ~val;
447 				break;
448 			}
449 			if (push(val, NULL, &stack))
450 				return NULL;
451 			break;
452 		case '?': /* if */
453 			break;
454 		case 't': /* then */
455 			pop(&val, NULL, &stack);
456 			if (val != 0) {
457 				l = 0;
458 				for (; *str != '\0'; str++) {
459 					if (*str != '%')
460 						continue;
461 					str++;
462 					if (*str == '?')
463 						l++;
464 					else if (*str == ';') {
465 						if (l > 0)
466 							l--;
467 						else
468 							break;
469 					} else if (*str == 'e' && l == 0)
470 						break;
471 				}
472 			}
473 			break;
474 		case 'e': /* else */
475 			l = 0;
476 			for (; *str != '\0'; str++) {
477 				if (*str != '%')
478 					continue;
479 				str++;
480 				if (*str == '?')
481 					l++;
482 				else if (*str == ';') {
483 					if (l > 0)
484 						l--;
485 					else
486 						break;
487 				}
488 			}
489 			break;
490 		case ';': /* fi */
491 			break;
492 		}
493 	}
494 	term->_buf[term->_bufpos] = '\0';
495 	return term->_buf;
496 }
497 
498 char *
499 ti_tiparm(TERMINAL *term, const char *str, ...)
500 {
501 	va_list va;
502 	char *ret;
503 
504 	_DIAGASSERT(term != NULL);
505 	_DIAGASSERT(str != NULL);
506 
507 	va_start(va, str);
508 	ret = _ti_tiparm(term, str, va);
509 	va_end(va);
510 	return ret;
511 }
512 
513 char *
514 tiparm(const char *str, ...)
515 {
516 	va_list va;
517 	char *ret;
518 
519 	_DIAGASSERT(str != NULL);
520 
521 	va_start(va, str);
522         ret = _ti_tiparm(NULL, str, va);
523 	va_end(va);
524 	return ret;
525 }
526 
527 char *
528 tparm(const char *str,
529     long lp1, long lp2, long lp3, long lp4, long lp5,
530     long lp6, long lp7, long lp8, long lp9)
531 {
532 	int p1 = lp1, p2 = lp2, p3 = lp3, p4 = lp4, p5 = lp5;
533 	int p6 = lp6, p7 = lp7, p8 = lp8, p9 = lp9;
534 
535 	return tiparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
536 }
537