xref: /illumos-gate/usr/src/lib/libcurses/screen/tparm.c (revision 45818ee124adeaaf947698996b4f4c722afc6d1f)
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 /* The following routine was added to make lint shut up about converting from
139  * a long to a char *.  It is identical to the pop routine, except for the
140  * cast on the return statement.
141  */
142 static char *
143 pop_char_p(STACK *st)
144 {
145 	if (st->top < 0) {
146 		longjmp(env, STACK_UNDERFLOW);
147 	}
148 	return ((char *)(st->stack[st->top--]));
149 }
150 
151 static void
152 init_stack(STACK *st)
153 {
154 	st->top = -1;
155 	st->stacksize = MAX;
156 	if ((st->stack = (void *)malloc(MAX * sizeof (long))) == NULL) {
157 		longjmp(env, MEM_ALLOC_FAIL);
158 	}
159 }
160 
161 static void
162 free_stack(STACK *st)
163 {
164 	free(st->stack);
165 }
166 
167 
168 char	*
169 tparm_p0(char *instring)
170 {
171 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
172 
173 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
174 			p[7], p[8]));
175 }
176 
177 char	*
178 tparm_p1(char *instring, long l1)
179 {
180 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
181 
182 	p[0] = l1;
183 
184 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
185 			p[7], p[8]));
186 }
187 
188 char	*
189 tparm_p2(char *instring, long l1, long l2)
190 {
191 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
192 
193 	p[0] = l1;
194 	p[1] = l2;
195 
196 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
197 			p[7], p[8]));
198 }
199 
200 char	*
201 tparm_p3(char *instring, long l1, long l2, long l3)
202 {
203 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
204 
205 	p[0] = l1;
206 	p[1] = l2;
207 	p[2] = l3;
208 
209 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
210 			p[7], p[8]));
211 }
212 
213 char	*
214 tparm_p4(char *instring, long l1, long l2, long l3, long l4)
215 {
216 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
217 
218 	p[0] = l1;
219 	p[1] = l2;
220 	p[2] = l3;
221 	p[3] = l4;
222 
223 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
224 			p[7], p[8]));
225 }
226 
227 char	*
228 tparm_p7(char *instring, long l1, long l2, long l3, long l4, long l5, long l6,
229 	long l7)
230 {
231 	long	p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
232 
233 	p[0] = l1;
234 	p[1] = l2;
235 	p[2] = l3;
236 	p[3] = l4;
237 	p[4] = l5;
238 	p[5] = l6;
239 	p[6] = l7;
240 
241 	return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
242 			p[7], p[8]));
243 }
244 
245 /* VARARGS */
246 char	*
247 tparm(char *instring, long fp1, long fp2, long p3, long p4,
248 	long p5, long p6, long p7, long p8, long p9)
249 {
250 	static	char	result[512];
251 	static	char	added[100];
252 	long		vars[26];
253 	STACK		stk;
254 	char		*cp = instring;
255 	char		*outp = result;
256 	char		c;
257 	long		op;
258 	long		op2;
259 	int		sign;
260 	int		onrow = 0;
261 	volatile long	p1 = fp1, p2 = fp2; /* copy in case < 2 actual parms */
262 	char		*xp;
263 	char		formatbuffer[100];
264 	char		*format;
265 	int		looping;
266 	short		*regs = cur_term->_regs;
267 	int		val;
268 
269 
270 	if ((val = setjmp(env)) != 0) {
271 #ifdef DEBUG
272 		switch (val) {
273 			case MEM_ALLOC_FAIL:
274 				fprintf(outf, "TPARM: Memory allocation"
275 				    " failure.");
276 				break;
277 			case STACK_UNDERFLOW:
278 				fprintf(outf, "TPARM: Stack underflow.");
279 				break;
280 		}
281 #endif  /* DEBUG */
282 
283 		if (val == STACK_UNDERFLOW)
284 			free_stack(&stk);
285 		return (NULL);
286 	}
287 
288 	init_stack(&stk);
289 	push(&stk, 0);
290 
291 	if (instring == 0) {
292 #ifdef	DEBUG
293 		if (outf)
294 			fprintf(outf, "TPARM: null arg\n");
295 #endif	/* DEBUG */
296 		free_stack(&stk);
297 		return (NULL);
298 	}
299 
300 	added[0] = 0;
301 
302 	while ((c = *cp++) != 0) {
303 		if (c != '%') {
304 			*outp++ = c;
305 			continue;
306 		}
307 		op = tops(&stk);
308 		switch (c = *cp++) {
309 			/* PRINTING CASES */
310 			case ':':
311 			case ' ':
312 			case '#':
313 			case '0':
314 			case '1':
315 			case '2':
316 			case '3':
317 			case '4':
318 			case '5':
319 			case '6':
320 			case '7':
321 			case '8':
322 			case '9':
323 			case '.':
324 			case 'd':
325 			case 's':
326 			case 'o':
327 			case 'x':
328 			case 'X':
329 				format = formatbuffer;
330 				*format++ = '%';
331 
332 			/* leading ':' to allow +/- in format */
333 			if (c == ':')
334 				c = *cp++;
335 
336 			/* take care of flags, width and precision */
337 			looping = 1;
338 			while (c && looping)
339 				switch (c) {
340 					case '-':
341 					case '+':
342 					case ' ':
343 					case '#':
344 					case '0':
345 					case '1':
346 					case '2':
347 					case '3':
348 					case '4':
349 					case '5':
350 					case '6':
351 					case '7':
352 					case '8':
353 					case '9':
354 					case '.':
355 						*format++ = c;
356 						c = *cp++;
357 						break;
358 					default:
359 						looping = 0;
360 				}
361 
362 			/* add in the conversion type */
363 			switch (c) {
364 				case 'd':
365 				case 's':
366 				case 'o':
367 				case 'x':
368 				case 'X':
369 					*format++ = c;
370 					break;
371 				default:
372 #ifdef	DEBUG
373 				if (outf)
374 					fprintf(outf, "TPARM: invalid "
375 					    "conversion type\n");
376 #endif	/* DEBUG */
377 				free_stack(&stk);
378 				return (NULL);
379 			}
380 			*format = '\0';
381 
382 			/*
383 			 * Pass off the dirty work to sprintf.
384 			 * It's debatable whether we should just pull in
385 			 * the appropriate code here. I decided not to for
386 			 * now.
387 			 */
388 			if (c == 's')
389 				(void) sprintf(outp, formatbuffer,
390 				    (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