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