xref: /illumos-gate/usr/src/lib/libcurses/screen/tparm.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2015 Gary Mills
24  * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
25  * All rights reserved.
26  */
27 
28 /*	Copyright (c) 1988 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 /* Copyright (c) 1979 Regents of the University of California */
32 
33 /*LINTLIBRARY*/
34 
35 #include	"curses_inc.h"
36 #include	"curshdr.h"
37 #include	"term.h"
38 #include	<string.h>
39 #include	<setjmp.h>
40 #include	<stdlib.h>
41 #include	<stdio.h>
42 
43 #ifndef	_CHCTRL
44 #define	_CHCTRL(c)	((c) & 037)
45 #endif	/* _CHCTRL */
46 
47 char	*_branchto(char *, char);
48 
49 /*
50  * Routine to perform parameter substitution.
51  * instring is a string containing printf type escapes.
52  * The whole thing uses a stack, much like an HP 35.
53  * The following escapes are defined for substituting row/column:
54  *
55  *	%[:[-+ #0]][0-9][.][0-9][dsoxX]
56  *		print pop() as in printf(3), as defined in the local
57  *		sprintf(3), except that a leading + or - must be preceded
58  *		with a colon (:) to distinguish from the plus/minus operators.
59  *
60  *	%c	print pop() like %c in printf(3)
61  *	%l	pop() a string address and push its length.
62  *	%P[a-z] set dynamic variable a-z
63  *	%g[a-z] get dynamic variable a-z
64  *	%P[A-Z] set static variable A-Z
65  *	%g[A-Z] get static variable A-Z
66  *
67  *	%p[1-0]	push ith parm
68  *	%'c'	char constant c
69  *	%{nn}	integer constant nn
70  *
71  *	%+ %- %* %/ %m		arithmetic (%m is mod): push(pop() op pop())
72  *	%& %| %^		bit operations:		push(pop() op pop())
73  *	%= %> %<		logical operations:	push(pop() op pop())
74  *	%A %O			logical AND, OR		push(pop() op pop())
75  *	%! %~			unary operations	push(op pop())
76  *	%%			output %
77  *	%? expr %t thenpart %e elsepart %;
78  *				if-then-else, %e elsepart is optional.
79  *				else-if's are possible ala Algol 68:
80  *				%? c1 %t %e c2 %t %e c3 %t %e c4 %t %e %;
81  *	% followed by anything else
82  *				is not defined, it may output the character,
83  *				and it may not. This is done so that further
84  *				enhancements to the format capabilities may
85  *				be made without worrying about being upwardly
86  *				compatible from buggy code.
87  *
88  * all other characters are ``self-inserting''.  %% gets % output.
89  *
90  * The stack structure used here is based on an idea by Joseph Yao.
91  */
92 
93 #define	MAX		10
94 #define	MEM_ALLOC_FAIL	1
95 #define	STACK_UNDERFLOW	2
96 
97 typedef struct {
98 	long	top;
99 	int	stacksize;
100 	long	*stack;
101 
102 }STACK;
103 
104 static jmp_buf env;
105 
106 static long
107 tops(STACK *st)
108 {
109 
110 	if (st->top < 0) {
111 		longjmp(env, STACK_UNDERFLOW);
112 	}
113 	return (st->stack[st->top]);
114 }
115 
116 static void
117 push(STACK *st, long i)
118 {
119 	if (st->top >= (st->stacksize - 1)) {
120 		st->stacksize += MAX;
121 		if ((st->stack = (void *)realloc(st->stack,
122 		    (st->stacksize * sizeof (long)))) == NULL) {
123 			longjmp(env, MEM_ALLOC_FAIL);
124 		}
125 	}
126 	st->stack[++st->top] = (i);
127 }
128 
129 static long
130 pop(STACK *st)
131 {
132 	if (st->top < 0) {
133 		longjmp(env, STACK_UNDERFLOW);
134 	}
135 	return (st->stack[st->top--]);
136 }
137 
138 /*
139  * The following routine was added to make lint shut up about converting from
140  * a long to a char *.  It is identical to the pop routine, except for the
141  * cast on the return statement.
142  */
143 static char *
144 pop_char_p(STACK *st)
145 {
146 	if (st->top < 0) {
147 		longjmp(env, STACK_UNDERFLOW);
148 	}
149 	return ((char *)(st->stack[st->top--]));
150 }
151 
152 static void
153 init_stack(STACK *st)
154 {
155 	st->top = -1;
156 	st->stacksize = MAX;
157 	if ((st->stack = (void *)malloc(MAX * sizeof (long))) == NULL) {
158 		longjmp(env, MEM_ALLOC_FAIL);
159 	}
160 }
161 
162 static void
163 free_stack(STACK *st)
164 {
165 	free(st->stack);
166 }
167 
168 
169 char *
170 tparm_p0(char *instring)
171 {
172 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
173 
174 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
175 	    p[7], p[8]));
176 }
177 
178 char *
179 tparm_p1(char *instring, long l1)
180 {
181 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
182 
183 	p[0] = l1;
184 
185 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
186 	    p[7], p[8]));
187 }
188 
189 char *
190 tparm_p2(char *instring, long l1, long l2)
191 {
192 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
193 
194 	p[0] = l1;
195 	p[1] = l2;
196 
197 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
198 	    p[7], p[8]));
199 }
200 
201 char *
202 tparm_p3(char *instring, long l1, long l2, long l3)
203 {
204 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
205 
206 	p[0] = l1;
207 	p[1] = l2;
208 	p[2] = l3;
209 
210 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
211 	    p[7], p[8]));
212 }
213 
214 char *
215 tparm_p4(char *instring, long l1, long l2, long l3, long l4)
216 {
217 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
218 
219 	p[0] = l1;
220 	p[1] = l2;
221 	p[2] = l3;
222 	p[3] = l4;
223 
224 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
225 	    p[7], p[8]));
226 }
227 
228 char *
229 tparm_p7(char *instring, long l1, long l2, long l3, long l4, long l5, long l6,
230     long l7)
231 {
232 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
233 
234 	p[0] = l1;
235 	p[1] = l2;
236 	p[2] = l3;
237 	p[3] = l4;
238 	p[4] = l5;
239 	p[5] = l6;
240 	p[6] = l7;
241 
242 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
243 	    p[7], p[8]));
244 }
245 
246 /* VARARGS */
247 char *
248 tparm(char *instring, long fp1, long fp2, long p3, long p4,
249     long p5, long p6, long p7, long p8, long p9)
250 {
251 	static	char	result[512];
252 	static	char	added[100];
253 	long		vars[26];
254 	STACK		stk;
255 	char		*cp = instring;
256 	char		*outp = result;
257 	char		c;
258 	long		op;
259 	long		op2;
260 	int		sign;
261 	volatile int	onrow = 0;
262 	volatile long	p1 = fp1, p2 = fp2; /* copy in case < 2 actual parms */
263 	char		*xp;
264 	char		formatbuffer[100];
265 	char		*format;
266 	int		looping;
267 	short		*regs = cur_term->_regs;
268 	int		val;
269 
270 
271 	if ((val = setjmp(env)) != 0) {
272 #ifdef DEBUG
273 		switch (val) {
274 			case MEM_ALLOC_FAIL:
275 				fprintf(outf, "TPARM: Memory allocation"
276 				    " failure.");
277 				break;
278 			case STACK_UNDERFLOW:
279 				fprintf(outf, "TPARM: Stack underflow.");
280 				break;
281 		}
282 #endif  /* DEBUG */
283 
284 		if (val == STACK_UNDERFLOW)
285 			free_stack(&stk);
286 		return (NULL);
287 	}
288 
289 	init_stack(&stk);
290 	push(&stk, 0);
291 
292 	if (instring == 0) {
293 #ifdef	DEBUG
294 		if (outf)
295 			fprintf(outf, "TPARM: null arg\n");
296 #endif	/* DEBUG */
297 		free_stack(&stk);
298 		return (NULL);
299 	}
300 
301 	added[0] = 0;
302 
303 	while ((c = *cp++) != 0) {
304 		if (c != '%') {
305 			*outp++ = c;
306 			continue;
307 		}
308 		op = tops(&stk);
309 		switch (c = *cp++) {
310 			/* PRINTING CASES */
311 			case ':':
312 			case ' ':
313 			case '#':
314 			case '0':
315 			case '1':
316 			case '2':
317 			case '3':
318 			case '4':
319 			case '5':
320 			case '6':
321 			case '7':
322 			case '8':
323 			case '9':
324 			case '.':
325 			case 'd':
326 			case 's':
327 			case 'o':
328 			case 'x':
329 			case 'X':
330 				format = formatbuffer;
331 				*format++ = '%';
332 
333 			/* leading ':' to allow +/- in format */
334 			if (c == ':')
335 				c = *cp++;
336 
337 			/* take care of flags, width and precision */
338 			looping = 1;
339 			while (c && looping)
340 				switch (c) {
341 					case '-':
342 					case '+':
343 					case ' ':
344 					case '#':
345 					case '0':
346 					case '1':
347 					case '2':
348 					case '3':
349 					case '4':
350 					case '5':
351 					case '6':
352 					case '7':
353 					case '8':
354 					case '9':
355 					case '.':
356 						*format++ = c;
357 						c = *cp++;
358 						break;
359 					default:
360 						looping = 0;
361 				}
362 
363 			/* add in the conversion type */
364 			switch (c) {
365 				case 'd':
366 				case 's':
367 				case 'o':
368 				case 'x':
369 				case 'X':
370 					*format++ = c;
371 					break;
372 				default:
373 #ifdef	DEBUG
374 				if (outf)
375 					fprintf(outf, "TPARM: invalid "
376 					    "conversion type\n");
377 #endif	/* DEBUG */
378 				free_stack(&stk);
379 				return (NULL);
380 			}
381 			*format = '\0';
382 
383 			/*
384 			 * Pass off the dirty work to sprintf.
385 			 * It's debatable whether we should just pull in
386 			 * the appropriate code here. I decided not to for
387 			 * now.
388 			 */
389 			if (c == 's')
390 				(void) sprintf(outp, formatbuffer, (char *)op);
391 			else
392 				(void) sprintf(outp, formatbuffer, op);
393 			/*
394 			 * Advance outp past what sprintf just did.
395 			 * sprintf returns an indication of its length on some
396 			 * systems, others the first char, and there's
397 			 * no easy way to tell which. The Sys V on
398 			 * BSD emulations are particularly confusing.
399 			 */
400 				while (*outp)
401 					outp++;
402 				(void) pop(&stk);
403 
404 				continue;
405 
406 			case 'c':
407 			/*
408 			 * This code is worth scratching your head at for a
409 			 * while.  The idea is that various weird things can
410 			 * happen to nulls, EOT's, tabs, and newlines by the
411 			 * tty driver, arpanet, and so on, so we don't send
412 			 * them if we can help it.  So we instead alter the
413 			 * place being addessed and then move the cursor
414 			 * locally using UP or RIGHT.
415 			 *
416 			 * This is a kludge, clearly.  It loses if the
417 			 * parameterized string isn't addressing the cursor
418 			 * (but hopefully that is all that %c terminals do
419 			 * with parms).  Also, since tab and newline happen
420 			 * to be next to each other in ASCII, if tab were
421 			 * included a loop would be needed.  Finally, note
422 			 * that lots of other processing is done here, so
423 			 * this hack won't always work (e.g. the Ann Arbor
424 			 * 4080, which uses %B and then %c.)
425 			 */
426 				switch (op) {
427 				/*
428 				 * Null.  Problem is that our
429 				 * output is, by convention, null terminated.
430 				 */
431 					case 0:
432 						op = 0200; /* Parity should */
433 							/* be ignored. */
434 						break;
435 				/*
436 				 * Control D.  Problem is that certain very
437 				 * ancient hardware hangs up on this, so the
438 				 * current(!) UNIX tty driver doesn't xmit
439 				 * control D's.
440 				 */
441 					case _CHCTRL('d'):
442 				/*
443 				 * Newline.  Problem is that UNIX will expand
444 				 * this to CRLF.
445 				 */
446 					case '\n':
447 						xp = (onrow ? cursor_down :
448 						    cursor_right);
449 					if (onrow && xp && op < lines-1 &&
450 					    cursor_up) {
451 						op += 2;
452 						xp = cursor_up;
453 					}
454 					if (xp && instring ==
455 					    cursor_address) {
456 						(void) strcat(added, xp);
457 						op--;
458 					}
459 					break;
460 				/*
461 				 * Tab used to be in this group too,
462 				 * because UNIX might expand it to blanks.
463 				 * We now require that this tab mode be turned
464 				 * off by any program using this routine,
465 				 * or using termcap in general, since some
466 				 * terminals use tab for other stuff, like
467 				 * nondestructive space.  (Filters like ul
468 				 * or vcrt will lose, since they can't stty.)
469 				 * Tab was taken out to get the Ann Arbor
470 				 * 4080 to work.
471 				 */
472 				}
473 
474 				/* LINTED */
475 				*outp++ = (char)op;
476 				(void) pop(&stk);
477 				break;
478 
479 			case 'l':
480 				xp = pop_char_p(&stk);
481 				push(&stk, strlen(xp));
482 				break;
483 
484 			case '%':
485 				*outp++ = c;
486 				break;
487 
488 			/*
489 			 * %i: shorthand for increment first two parms.
490 			 * Useful for terminals that start numbering from
491 			 * one instead of zero(like ANSI terminals).
492 			 */
493 			case 'i':
494 				p1++;
495 				p2++;
496 				break;
497 
498 			/* %pi: push the ith parameter */
499 			case 'p':
500 				switch (c = *cp++) {
501 					case '1':
502 						push(&stk, p1);
503 						break;
504 					case '2':
505 						push(&stk, p2);
506 						break;
507 					case '3':
508 						push(&stk, p3);
509 						break;
510 					case '4':
511 						push(&stk, p4);
512 						break;
513 					case '5':
514 						push(&stk, p5);
515 						break;
516 					case '6':
517 						push(&stk, p6);
518 						break;
519 					case '7':
520 						push(&stk, p7);
521 						break;
522 					case '8':
523 						push(&stk, p8);
524 						break;
525 					case '9':
526 						push(&stk, p9);
527 						break;
528 					default:
529 #ifdef	DEBUG
530 						if (outf)
531 							fprintf(outf, "TPARM:"
532 							    " bad parm"
533 							    " number\n");
534 #endif	/* DEBUG */
535 						free_stack(&stk);
536 						return (NULL);
537 				}
538 			onrow = (c == '1');
539 			break;
540 
541 			/* %Pi: pop from stack into variable i (a-z) */
542 			case 'P':
543 				if (*cp >= 'a' && *cp <= 'z') {
544 					vars[*cp++ - 'a'] = pop(&stk);
545 				} else {
546 					if (*cp >= 'A' && *cp <= 'Z') {
547 						regs[*cp++ - 'A'] =
548 						    /* LINTED */
549 						    (short)pop(&stk);
550 					}
551 #ifdef	DEBUG
552 					else if (outf) {
553 						fprintf(outf, "TPARM: bad"
554 						    " register name\n");
555 					}
556 #endif	/* DEBUG */
557 				}
558 				break;
559 
560 			/* %gi: push variable i (a-z) */
561 			case 'g':
562 				if (*cp >= 'a' && *cp <= 'z') {
563 					push(&stk, vars[*cp++ - 'a']);
564 				} else {
565 					if (*cp >= 'A' && *cp <= 'Z') {
566 						push(&stk, regs[*cp++ - 'A']);
567 					}
568 #ifdef	DEBUG
569 					else if (outf) {
570 						fprintf(outf, "TPARM: bad"
571 						    " register name\n");
572 
573 					}
574 #endif	/* DEBUG */
575 				}
576 				break;
577 
578 			/* %'c' : character constant */
579 			case '\'':
580 				push(&stk, *cp++);
581 				if (*cp++ != '\'') {
582 #ifdef	DEBUG
583 					if (outf)
584 						fprintf(outf, "TPARM: missing"
585 						    " closing quote\n");
586 #endif	/* DEBUG */
587 					free_stack(&stk);
588 					return (NULL);
589 				}
590 				break;
591 
592 			/* %{nn} : integer constant.  */
593 			case '{':
594 				op = 0;
595 				sign = 1;
596 				if (*cp == '-') {
597 					sign = -1;
598 					cp++;
599 				} else
600 					if (*cp == '+')
601 						cp++;
602 				while ((c = *cp++) >= '0' && c <= '9') {
603 					op = 10 * op + c - '0';
604 				}
605 				if (c != '}') {
606 #ifdef	DEBUG
607 					if (outf)
608 						fprintf(outf, "TPARM: missing "
609 						    "closing brace\n");
610 #endif	/* DEBUG */
611 					free_stack(&stk);
612 					return (NULL);
613 				}
614 				push(&stk, (sign * op));
615 				break;
616 
617 			/* binary operators */
618 			case '+':
619 				op2 = pop(&stk);
620 				op = pop(&stk);
621 				push(&stk, (op + op2));
622 				break;
623 			case '-':
624 				op2 = pop(&stk);
625 				op = pop(&stk);
626 				push(&stk, (op - op2));
627 				break;
628 			case '*':
629 				op2 = pop(&stk);
630 				op = pop(&stk);
631 				push(&stk, (op * op2));
632 				break;
633 			case '/':
634 				op2 = pop(&stk);
635 				op = pop(&stk);
636 				push(&stk, (op / op2));
637 				break;
638 			case 'm':
639 				op2 = pop(&stk);
640 				op = pop(&stk);
641 				push(&stk, (op % op2));
642 				break; /* %m: mod */
643 			case '&':
644 				op2 = pop(&stk);
645 				op = pop(&stk);
646 				push(&stk, (op & op2));
647 				break;
648 			case '|':
649 				op2 = pop(&stk);
650 				op = pop(&stk);
651 				push(&stk, (op | op2));
652 				break;
653 			case '^':
654 				op2 = pop(&stk);
655 				op = pop(&stk);
656 				push(&stk, (op ^ op2));
657 				break;
658 			case '=':
659 				op2 = pop(&stk);
660 				op = pop(&stk);
661 				push(&stk, (op == op2));
662 				break;
663 			case '>':
664 				op2 = pop(&stk);
665 				op = pop(&stk);
666 				push(&stk, (op > op2));
667 				break;
668 			case '<':
669 				op2 = pop(&stk);
670 				op = pop(&stk);
671 				push(&stk, (op < op2));
672 				break;
673 			case 'A':
674 				op2 = pop(&stk);
675 				op = pop(&stk);
676 				push(&stk, (op && op2));
677 				break; /* AND */
678 			case 'O':
679 				op2 = pop(&stk);
680 				op = pop(&stk);
681 				push(&stk, (op || op2));
682 				break; /* OR */
683 
684 			/* Unary operators. */
685 			case '!':
686 				push(&stk, !pop(&stk));
687 				break;
688 			case '~':
689 				push(&stk, ~pop(&stk));
690 				break;
691 
692 			/* Sorry, no unary minus, because minus is binary. */
693 
694 			/*
695 			 * If-then-else.  Implemented by a low level hack of
696 			 * skipping forward until the match is found, counting
697 			 * nested if-then-elses.
698 			 */
699 			case '?':	/* IF - just a marker */
700 				break;
701 
702 			case 't':	/* THEN - branch if false */
703 				if (!pop(&stk))
704 					cp = _branchto(cp, 'e');
705 				break;
706 
707 			case 'e':	/* ELSE - branch to ENDIF */
708 				cp = _branchto(cp, ';');
709 				break;
710 
711 			case ';':	/* ENDIF - just a marker */
712 				break;
713 
714 			default:
715 #ifdef	DEBUG
716 				if (outf)
717 					fprintf(outf, "TPARM: bad % "
718 					    "sequence\n");
719 #endif	/* DEBUG */
720 				free_stack(&stk);
721 				return (NULL);
722 		}
723 	}
724 	(void) strcpy(outp, added);
725 	free_stack(&stk);
726 	return (result);
727 }
728 
729 char	*
730 _branchto(register char *cp, char to)
731 {
732 	register	int	level = 0;
733 	register	char	c;
734 
735 	while (c = *cp++) {
736 		if (c == '%') {
737 			if ((c = *cp++) == to || c == ';') {
738 				if (level == 0) {
739 					return (cp);
740 				}
741 			}
742 			if (c == '?')
743 				level++;
744 			if (c == ';')
745 				level--;
746 		}
747 	}
748 #ifdef	DEBUG
749 	if (outf)
750 		fprintf(outf, "TPARM: no matching ENDIF");
751 #endif	/* DEBUG */
752 	return (NULL);
753 }
754