xref: /netbsd-src/lib/libterminfo/tparm.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /* $NetBSD: tparm.c,v 1.1 2010/02/03 15:16:32 roy Exp $ */
2 
3 /*
4  * Copyright (c) 2009 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.1 2010/02/03 15:16:32 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 tpstack {
46 	long nums[20];
47 	char *strings[20];
48 	size_t offset;
49 } TPSTACK;
50 
51 typedef struct tpvar {
52 	long num;
53 	char *string;
54 } TPVAR;
55 
56 static int
57 push(long 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(long *num, char **string, TPSTACK *stack)
71 {
72 	if (stack->offset == 0) {
73 		errno = E2BIG;
74 		return -1;
75 	}
76 	stack->offset--;
77 	if (num)
78 		*num = stack->nums[stack->offset];
79 	if (string)
80 		*string = stack->strings[stack->offset];
81 	return 0;
82 }
83 
84 static char *
85 checkbuf(TERMINAL *term, size_t len)
86 {
87 	char *buf;
88 
89 	if (term->_bufpos + len >= term->_buflen) {
90 		len = term->_buflen + BUFSIZ;
91 		buf = realloc(term->_buf, len);
92 		if (buf == NULL)
93 			return 0;
94 		term->_buf = buf;
95 		term->_buflen = len;
96 	}
97 	return term->_buf;
98 }
99 
100 static size_t
101 ochar(TERMINAL *term, int c)
102 {
103 	if (c == 0)
104 		c = 0200;
105 	/* Check we have space and a terminator */
106 	if (checkbuf(term, 2) == NULL)
107 		return 0;
108 	term->_buf[term->_bufpos++] = (char)c;
109 	return 1;
110 }
111 
112 static size_t
113 onum(TERMINAL *term, const char *fmt, long num, int len)
114 {
115 	size_t l;
116 
117 	/* Assume we never have natural number longer than 64 chars */
118 	if (len < 64)
119 		len = 64;
120 	if (checkbuf(term, (size_t)len + 1) == NULL)
121 		return 0;
122 	l = sprintf(term->_buf + term->_bufpos, fmt, num);
123 	term->_bufpos += l;
124 	return l;
125 }
126 
127 static char *
128 _ti_vtparm(TERMINAL *term, const char *str, va_list parms)
129 {
130 	const char *sp;
131 	char c, fmt[64], *fp, *ostr;
132 	long val, val2;
133 	long dnums[26]; /* dynamic variables a-z, not preserved */
134 	size_t l, max;
135 	TPSTACK stack;
136 	TPVAR params[9];
137 	int done, dot, minus, width, precision, olen;
138 	int piss[9]; /* Parameter IS String - piss ;) */
139 
140 	if (str == NULL)
141 		return NULL;
142 
143 	/*
144 	  If not passed a terminal, malloc a dummy one.
145 	  This means we can preserve buffers and variables per terminal and
146 	  still work with non thread safe functions (which sadly are still the
147 	  norm and standard).
148 	*/
149 	if (term == NULL) {
150 		if (dumbterm == NULL) {
151 			dumbterm = malloc(sizeof(*dumbterm));
152 			if (dumbterm == NULL)
153 				return NULL;
154 			dumbterm->_buflen = 0;
155 		}
156 		term = dumbterm;
157 	}
158 
159 	term->_bufpos = 0;
160 	/* Ensure we have an initial buffer */
161 	if (term->_buflen == 0) {
162 		term->_buf = malloc(BUFSIZ);
163 		if (term->_buf == NULL)
164 			return NULL;
165 		term->_buflen = BUFSIZ;
166 	}
167 
168 	/*
169 	  Make a first pass through the string so we can work out
170 	  which parameters are longs and which are char *.
171 	  Basically we only use char * if %p[1-9] is followed by %l or %s.
172 	*/
173 	memset(&piss, 0, sizeof(piss));
174 	max = 0;
175 	sp = str;
176 	while ((c = *sp++) != '\0') {
177 		if (c != '%')
178 			continue;
179 		c = *sp++;
180 		if (c == '\0')
181 			break;
182 		if (c != 'p')
183 			continue;
184 		c = *sp++;
185 		if (c < '1' || c > '9') {
186 			errno = EINVAL;
187 			return NULL;
188 		}
189 		l = c - '0';
190 		if (l > max)
191 			max = l;
192 		if (*sp != '%')
193 			continue;
194 		/* Skip formatting */
195 		sp++;
196 		while (*sp == '.' || *sp == '#' || *sp == ' ' || *sp == ':' ||
197 		    *sp == '-' || isdigit((unsigned char)*sp))
198 			sp++;
199 		if (*sp == 'l' || *sp == 's')
200 			piss[l - 1] = 1;
201 	}
202 
203 	/* Put our parameters into variables */
204 	memset(&params, 0, sizeof(params));
205 	for (l = 0; l < max; l++) {
206 		if (piss[l] == 0)
207 			params[l].num = va_arg(parms, long);
208 		else
209 			params[l].string = va_arg(parms, char *);
210 	}
211 
212 	term->_bufpos = 0;
213 	memset(&stack, 0, sizeof(stack));
214 	while ((c = *str++) != '\0') {
215 		if (c != '%' || (c = *str++) == '%') {
216 			if (c == '\0')
217 				break;
218 			if (ochar(term, c) == 0)
219 				return NULL;
220 			continue;
221 		}
222 
223 		/* Handle formatting. */
224 		fp = fmt;
225 		*fp++ = '%';
226 		done = dot = minus = width = precision = 0;
227 		val = 0;
228 		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
229 			switch (c) {
230 			case 'c': /* FALLTHROUGH */
231 			case 'd': /* FALLTHROUGH */
232 			case 'o': /* FALLTHROUGH */
233 			case 'x': /* FALLTHROUGH */
234 			case 'X': /* FALLTHROUGH */
235 			case 's':
236 				*fp++ = c;
237 				done = 1;
238 				break;
239 			case '#': /* FALLTHROUGH */
240 			case ' ':
241 				*fp++ = c;
242 				break;
243 			case '.':
244 				*fp++ = c;
245 				if (dot == 0) {
246 					dot = 1;
247 					width = val;
248 				} else
249 					done = 2;
250 				val = 0;
251 				break;
252 			case ':':
253 				minus = 1;
254 				break;
255 			case '-':
256 				if (minus)
257 					*fp++ = c;
258 				else
259 					done = 1;
260 				break;
261 			default:
262 				if (isdigit((unsigned char)c)) {
263 					val = (val * 10) + (c - '0');
264 					if (val > 10000)
265 						done = 2;
266 					else
267 						*fp++ = c;
268 				} else
269 					done = 1;
270 			}
271 			if (done == 0)
272 				c = *str++;
273 		}
274 		if (done == 2) {
275 			/* Found an error in the format */
276 			fp = fmt + 1;
277 			*fp = *str;
278 			olen = 0;
279 		} else {
280 			if (dot == 0)
281 				width = val;
282 			else
283 				precision = val;
284 			olen = (width > precision) ? width : precision;
285 		}
286 		*fp++ = '\0';
287 
288 		/* Handle commands */
289 		switch (c) {
290 		case 'c':
291 			if (pop(&val, NULL, &stack))
292 			    return NULL;
293 			if (ochar(term, (unsigned char)val) == 0)
294 				return NULL;
295 			break;
296 		case 's':
297 			if (pop(NULL, &ostr, &stack))
298 				return NULL;
299 			if (ostr != NULL) {
300 				l = strlen(ostr);
301 				if (l < (size_t)olen)
302 					l = olen;
303 				if (checkbuf(term, (size_t)(l + 1)) == NULL)
304 					return NULL;
305 				l = sprintf(term->_buf + term->_bufpos,
306 				    fmt, ostr);
307 				term->_bufpos += l;
308 			}
309 			break;
310 		case 'l':
311 			if (pop(NULL, &ostr, &stack))
312 				return NULL;
313 			if (ostr == NULL)
314 				l = 0;
315 			else
316 				l = strlen(ostr);
317 			if (onum(term, "%d", (long)l, 0) == 0)
318 				return NULL;
319 			break;
320 		case 'd': /* FALLTHROUGH */
321 		case 'o': /* FALLTHROUGH */
322 		case 'x': /* FALLTHROUGH */
323 		case 'X':
324 			if (pop(&val, NULL, &stack))
325 				return NULL;
326 			if (onum(term, fmt, val, olen) == 0)
327 				return NULL;
328 			break;
329 		case 'p':
330 			if (*str < '1' || *str > '9') {
331 				errno = EINVAL;
332 				return NULL;
333 			}
334 			l = *str++ - '1';
335 			if (push(params[l].num, params[l].string, &stack))
336 				return NULL;
337 			break;
338 		case 'P':
339 			if (pop(&val, NULL, &stack))
340 				return NULL;
341 			str++;
342 			if (*str >= 'a' && *str <= 'z')
343 				dnums[*str - 'a'] = val;
344 			else if (*str >= 'A' && *str <= 'Z')
345 				term->_snums[*str - 'A'] = val;
346 			break;
347 		case 'g':
348 			str++;
349 			if (*str >= 'a' && *str <= 'z') {
350 				if (push(dnums[*str - 'a'], NULL, &stack))
351 					return NULL;
352 			} else if (*str >= 'A' && *str <= 'Z') {
353 				if (push(term->_snums[*str - 'A'],
354 					NULL, &stack))
355 					return NULL;
356 			}
357 			break;
358 		case 'i':
359 			if (piss[0] == 0)
360 				params[0].num++;
361 			if (piss[1] == 0)
362 				params[1].num++;
363 			break;
364 		case '\'':
365 			if (push((long)(unsigned char)*str++, NULL, &stack))
366 				return NULL;
367 			while (*str != '\0' && *str != '\'')
368 				str++;
369 			if (*str == '\'')
370 				str++;
371 			break;
372 		case '{':
373 			val = 0;
374 			for (str++; isdigit((unsigned char)*str);  str++)
375 				val = (val * 10) + (*str - '0');
376 			if (push(val, NULL, &stack))
377 				return NULL;
378 			while (*str != '\0' && *str != '}')
379 				str++;
380 			if (*str == '}')
381 				str++;
382 			break;
383 		case '+': /* FALLTHROUGH */
384 		case '-': /* FALLTHROUGH */
385 		case '*': /* FALLTHROUGH */
386 		case '/': /* FALLTHROUGH */
387 		case 'm': /* FALLTHROUGH */
388 		case 'A': /* FALLTHROUGH */
389 		case 'O': /* FALLTHROUGH */
390 		case '&': /* FALLTHROUGH */
391 		case '|': /* FALLTHROUGH */
392 		case '^': /* FALLTHROUGH */
393 		case '=': /* FALLTHROUGH */
394 		case '<': /* FALLTHROUGH */
395 		case '>':
396 			if (pop(&val, NULL, &stack) ||
397 			    pop(&val2, NULL, &stack))
398 				return NULL;
399 			switch (c) {
400 			case '+':
401 				val = val + val2;
402 				break;
403 			case '-':
404 				val = val2 - val;
405 				break;
406 			case '*':
407 				val = val * val2;
408 				break;
409 			case '/':
410 				val = val ? val2 / val : 0;
411 				break;
412 			case 'm':
413 				val = val ? val2 % val : 0;
414 				break;
415 			case 'A':
416 				val = val && val2;
417 				break;
418 			case 'O':
419 				val = val || val2;
420 				break;
421 			case '&':
422 				val = val & val2;
423 				break;
424 			case '|':
425 				val = val | val2;
426 				break;
427 			case '^':
428 				val = val ^ val2;
429 				break;
430 			case '=':
431 				val = val == val2;
432 				break;
433 			case '<':
434 				val = val2 < val;
435 				break;
436 			case '>':
437 				val = val2 > val;
438 				break;
439 			}
440 			if (push(val, NULL, &stack))
441 				return NULL;
442 			break;
443 		case '!':
444 		case '~':
445 			if (pop(&val, NULL, &stack))
446 				return NULL;
447 			switch (*str) {
448 			case '!':
449 				val = !val;
450 				break;
451 			case '~':
452 				val = ~val;
453 				break;
454 			}
455 			if (push(val, NULL, &stack))
456 				return NULL;
457 			break;
458 		case '?': /* if */
459 			break;
460 		case 't': /* then */
461 			if (pop(&val, NULL, &stack))
462 				return NULL;
463 			if (val != 0) {
464 				l = 0;
465 				for (; *str != '\0'; str++) {
466 					if (*str != '%')
467 						continue;
468 					str++;
469 					if (*str == '?')
470 						l++;
471 					else if (*str == ';') {
472 						if (l > 0)
473 							l--;
474 						else
475 							break;
476 					} else if (*str == 'e' && l == 0)
477 						break;
478 				}
479 			}
480 			break;
481 		case 'e': /* else */
482 			l = 0;
483 			for (; *str != '\0'; str++) {
484 				if (*str != '%')
485 					continue;
486 				str++;
487 				if (*str == '?')
488 					l++;
489 				else if (*str == ';') {
490 					if (l > 0)
491 						l--;
492 					else
493 						break;
494 				}
495 			}
496 			break;
497 		case ';': /* fi */
498 			break;
499 		}
500 	}
501 	term->_buf[term->_bufpos] = '\0';
502 	return term->_buf;
503 }
504 
505 char *
506 t_vparm(TERMINAL *term, const char *str, ...)
507 {
508 	va_list va;
509 	char *ret;
510 
511 	_DIAGASSERT(term != NULL);
512 	_DIAGASSERT(str != NULL);
513 
514 	va_start(va, str);
515 	ret = _ti_vtparm(term, str, va);
516 	va_end(va);
517 	return ret;
518 }
519 
520 char *
521 vtparm(const char *str, ...)
522 {
523 	va_list va;
524 	char *ret;
525 
526 	_DIAGASSERT(str != NULL);
527 
528 	va_start(va, str);
529         ret = _ti_vtparm(NULL, str, va);
530 	va_end(va);
531 	return ret;
532 }
533 
534 char *
535 t_parm(TERMINAL *term, const char *str,
536     long p1, long p2, long p3, long p4, long p5,
537     long p6, long p7, long p8, long p9)
538 {
539 
540 	_DIAGASSERT(term != NULL);
541 	_DIAGASSERT(str != NULL);
542 	return t_vparm(term, str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
543 }
544 
545 char *
546 tparm(const char *str,
547     long p1, long p2, long p3, long p4, long p5,
548     long p6, long p7, long p8, long p9)
549 {
550 
551 	_DIAGASSERT(str != NULL);
552 	return t_vparm(NULL, str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
553 }
554