/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2015 Gary Mills
* Copyright (c) 1996-1997 by Sun Microsystems, Inc.
* All rights reserved.
*/
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
/* Copyright (c) 1979 Regents of the University of California */
/*LINTLIBRARY*/
#include "curses_inc.h"
#include "curshdr.h"
#include "term.h"
#include <string.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#ifndef _CHCTRL
#define _CHCTRL(c) ((c) & 037)
#endif /* _CHCTRL */
char *_branchto(char *, char);
/*
* Routine to perform parameter substitution.
* instring is a string containing printf type escapes.
* The whole thing uses a stack, much like an HP 35.
* The following escapes are defined for substituting row/column:
*
* %[:[-+ #0]][0-9][.][0-9][dsoxX]
* print pop() as in printf(3), as defined in the local
* sprintf(3), except that a leading + or - must be preceded
* with a colon (:) to distinguish from the plus/minus operators.
*
* %c print pop() like %c in printf(3)
* %l pop() a string address and push its length.
* %P[a-z] set dynamic variable a-z
* %g[a-z] get dynamic variable a-z
* %P[A-Z] set static variable A-Z
* %g[A-Z] get static variable A-Z
*
* %p[1-0] push ith parm
* %'c' char constant c
* %{nn} integer constant nn
*
* %+ %- %* %/ %m arithmetic (%m is mod): push(pop() op pop())
* %& %| %^ bit operations: push(pop() op pop())
* %= %> %< logical operations: push(pop() op pop())
* %A %O logical AND, OR push(pop() op pop())
* %! %~ unary operations push(op pop())
* %% output %
* %? expr %t thenpart %e elsepart %;
* if-then-else, %e elsepart is optional.
* else-if's are possible ala Algol 68:
* %? c1 %t %e c2 %t %e c3 %t %e c4 %t %e %;
* % followed by anything else
* is not defined, it may output the character,
* and it may not. This is done so that further
* enhancements to the format capabilities may
* be made without worrying about being upwardly
* compatible from buggy code.
*
* all other characters are ``self-inserting''. %% gets % output.
*
* The stack structure used here is based on an idea by Joseph Yao.
*/
#define MAX 10
#define MEM_ALLOC_FAIL 1
#define STACK_UNDERFLOW 2
typedef struct {
long top;
int stacksize;
long *stack;
}STACK;
static jmp_buf env;
static long
tops(STACK *st)
{
if (st->top < 0) {
longjmp(env, STACK_UNDERFLOW);
}
return (st->stack[st->top]);
}
static void
push(STACK *st, long i)
{
if (st->top >= (st->stacksize - 1)) {
st->stacksize += MAX;
if ((st->stack = (void *)realloc(st->stack,
(st->stacksize * sizeof (long)))) == NULL) {
longjmp(env, MEM_ALLOC_FAIL);
}
}
st->stack[++st->top] = (i);
}
static long
pop(STACK *st)
{
if (st->top < 0) {
longjmp(env, STACK_UNDERFLOW);
}
return (st->stack[st->top--]);
}
/* The following routine was added to make lint shut up about converting from
* a long to a char *. It is identical to the pop routine, except for the
* cast on the return statement.
*/
static char *
pop_char_p(STACK *st)
{
if (st->top < 0) {
longjmp(env, STACK_UNDERFLOW);
}
return ((char *)(st->stack[st->top--]));
}
static void
init_stack(STACK *st)
{
st->top = -1;
st->stacksize = MAX;
if ((st->stack = (void *)malloc(MAX * sizeof (long))) == NULL) {
longjmp(env, MEM_ALLOC_FAIL);
}
}
static void
free_stack(STACK *st)
{
free(st->stack);
}
char *
tparm_p0(char *instring)
{
long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[8]));
}
char *
tparm_p1(char *instring, long l1)
{
long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
p[0] = l1;
return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[8]));
}
char *
tparm_p2(char *instring, long l1, long l2)
{
long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
p[0] = l1;
p[1] = l2;
return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[8]));
}
char *
tparm_p3(char *instring, long l1, long l2, long l3)
{
long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
p[0] = l1;
p[1] = l2;
p[2] = l3;
return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[8]));
}
char *
tparm_p4(char *instring, long l1, long l2, long l3, long l4)
{
long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
p[0] = l1;
p[1] = l2;
p[2] = l3;
p[3] = l4;
return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[8]));
}
char *
tparm_p7(char *instring, long l1, long l2, long l3, long l4, long l5, long l6,
long l7)
{
long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
p[0] = l1;
p[1] = l2;
p[2] = l3;
p[3] = l4;
p[4] = l5;
p[5] = l6;
p[6] = l7;
return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[8]));
}
/* VARARGS */
char *
tparm(char *instring, long fp1, long fp2, long p3, long p4,
long p5, long p6, long p7, long p8, long p9)
{
static char result[512];
static char added[100];
long vars[26];
STACK stk;
char *cp = instring;
char *outp = result;
char c;
long op;
long op2;
int sign;
int onrow = 0;
volatile long p1 = fp1, p2 = fp2; /* copy in case < 2 actual parms */
char *xp;
char formatbuffer[100];
char *format;
int looping;
short *regs = cur_term->_regs;
int val;
if ((val = setjmp(env)) != 0) {
#ifdef DEBUG
switch (val) {
case MEM_ALLOC_FAIL:
fprintf(outf, "TPARM: Memory allocation"
" failure.");
break;
case STACK_UNDERFLOW:
fprintf(outf, "TPARM: Stack underflow.");
break;
}
#endif /* DEBUG */
if (val == STACK_UNDERFLOW)
free_stack(&stk);
return (NULL);
}
init_stack(&stk);
push(&stk, 0);
if (instring == 0) {
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM: null arg\n");
#endif /* DEBUG */
free_stack(&stk);
return (NULL);
}
added[0] = 0;
while ((c = *cp++) != 0) {
if (c != '%') {
*outp++ = c;
continue;
}
op = tops(&stk);
switch (c = *cp++) {
/* PRINTING CASES */
case ':':
case ' ':
case '#':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
case 'd':
case 's':
case 'o':
case 'x':
case 'X':
format = formatbuffer;
*format++ = '%';
/* leading ':' to allow +/- in format */
if (c == ':')
c = *cp++;
/* take care of flags, width and precision */
looping = 1;
while (c && looping)
switch (c) {
case '-':
case '+':
case ' ':
case '#':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
*format++ = c;
c = *cp++;
break;
default:
looping = 0;
}
/* add in the conversion type */
switch (c) {
case 'd':
case 's':
case 'o':
case 'x':
case 'X':
*format++ = c;
break;
default:
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM: invalid "
"conversion type\n");
#endif /* DEBUG */
free_stack(&stk);
return (NULL);
}
*format = '\0';
/*
* Pass off the dirty work to sprintf.
* It's debatable whether we should just pull in
* the appropriate code here. I decided not to for
* now.
*/
if (c == 's')
(void) sprintf(outp, formatbuffer,
(char *) op);
else
(void) sprintf(outp, formatbuffer, op);
/*
* Advance outp past what sprintf just did.
* sprintf returns an indication of its length on some
* systems, others the first char, and there's
* no easy way to tell which. The Sys V on
* BSD emulations are particularly confusing.
*/
while (*outp)
outp++;
(void) pop(&stk);
continue;
case 'c':
/*
* This code is worth scratching your head at for a
* while. The idea is that various weird things can
* happen to nulls, EOT's, tabs, and newlines by the
* tty driver, arpanet, and so on, so we don't send
* them if we can help it. So we instead alter the
* place being addessed and then move the cursor
* locally using UP or RIGHT.
*
* This is a kludge, clearly. It loses if the
* parameterized string isn't addressing the cursor
* (but hopefully that is all that %c terminals do
* with parms). Also, since tab and newline happen
* to be next to each other in ASCII, if tab were
* included a loop would be needed. Finally, note
* that lots of other processing is done here, so
* this hack won't always work (e.g. the Ann Arbor
* 4080, which uses %B and then %c.)
*/
switch (op) {
/*
* Null. Problem is that our
* output is, by convention, null terminated.
*/
case 0:
op = 0200; /* Parity should */
/* be ignored. */
break;
/*
* Control D. Problem is that certain very
* ancient hardware hangs up on this, so the
* current(!) UNIX tty driver doesn't xmit
* control D's.
*/
case _CHCTRL('d'):
/*
* Newline. Problem is that UNIX will expand
* this to CRLF.
*/
case '\n':
xp = (onrow ? cursor_down :
cursor_right);
if (onrow && xp && op < lines-1 &&
cursor_up) {
op += 2;
xp = cursor_up;
}
if (xp && instring ==
cursor_address) {
(void) strcat(added, xp);
op--;
}
break;
/*
* Tab used to be in this group too,
* because UNIX might expand it to blanks.
* We now require that this tab mode be turned
* off by any program using this routine,
* or using termcap in general, since some
* terminals use tab for other stuff, like
* nondestructive space. (Filters like ul
* or vcrt will lose, since they can't stty.)
* Tab was taken out to get the Ann Arbor
* 4080 to work.
*/
}
/* LINTED */
*outp++ = (char)op;
(void) pop(&stk);
break;
case 'l':
xp = pop_char_p(&stk);
push(&stk, strlen(xp));
break;
case '%':
*outp++ = c;
break;
/*
* %i: shorthand for increment first two parms.
* Useful for terminals that start numbering from
* one instead of zero(like ANSI terminals).
*/
case 'i':
p1++;
p2++;
break;
/* %pi: push the ith parameter */
case 'p':
switch (c = *cp++) {
case '1':
push(&stk, p1);
break;
case '2':
push(&stk, p2);
break;
case '3':
push(&stk, p3);
break;
case '4':
push(&stk, p4);
break;
case '5':
push(&stk, p5);
break;
case '6':
push(&stk, p6);
break;
case '7':
push(&stk, p7);
break;
case '8':
push(&stk, p8);
break;
case '9':
push(&stk, p9);
break;
default:
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM:"
" bad parm"
" number\n");
#endif /* DEBUG */
free_stack(&stk);
return (NULL);
}
onrow = (c == '1');
break;
/* %Pi: pop from stack into variable i (a-z) */
case 'P':
if (*cp >= 'a' && *cp <= 'z') {
vars[*cp++ - 'a'] = pop(&stk);
} else {
if (*cp >= 'A' && *cp <= 'Z') {
regs[*cp++ - 'A'] =
/* LINTED */
(short) pop(&stk);
}
#ifdef DEBUG
else if (outf) {
fprintf(outf, "TPARM: bad"
" register name\n");
}
#endif /* DEBUG */
}
break;
/* %gi: push variable i (a-z) */
case 'g':
if (*cp >= 'a' && *cp <= 'z') {
push(&stk, vars[*cp++ - 'a']);
} else {
if (*cp >= 'A' && *cp <= 'Z') {
push(&stk, regs[*cp++ - 'A']);
}
#ifdef DEBUG
else if (outf) {
fprintf(outf, "TPARM: bad"
" register name\n");
}
#endif /* DEBUG */
}
break;
/* %'c' : character constant */
case '\'':
push(&stk, *cp++);
if (*cp++ != '\'') {
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM: missing"
" closing quote\n");
#endif /* DEBUG */
free_stack(&stk);
return (NULL);
}
break;
/* %{nn} : integer constant. */
case '{':
op = 0;
sign = 1;
if (*cp == '-') {
sign = -1;
cp++;
} else
if (*cp == '+')
cp++;
while ((c = *cp++) >= '0' && c <= '9') {
op = 10 * op + c - '0';
}
if (c != '}') {
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM: missing "
"closing brace\n");
#endif /* DEBUG */
free_stack(&stk);
return (NULL);
}
push(&stk, (sign * op));
break;
/* binary operators */
case '+':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op + op2));
break;
case '-':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op - op2));
break;
case '*':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op * op2));
break;
case '/':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op / op2));
break;
case 'm':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op % op2));
break; /* %m: mod */
case '&':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op & op2));
break;
case '|':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op | op2));
break;
case '^':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op ^ op2));
break;
case '=':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op == op2));
break;
case '>':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op > op2));
break;
case '<':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op < op2));
break;
case 'A':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op && op2));
break; /* AND */
case 'O':
op2 = pop(&stk);
op = pop(&stk);
push(&stk, (op || op2));
break; /* OR */
/* Unary operators. */
case '!':
push(&stk, !pop(&stk));
break;
case '~':
push(&stk, ~pop(&stk));
break;
/* Sorry, no unary minus, because minus is binary. */
/*
* If-then-else. Implemented by a low level hack of
* skipping forward until the match is found, counting
* nested if-then-elses.
*/
case '?': /* IF - just a marker */
break;
case 't': /* THEN - branch if false */
if (!pop(&stk))
cp = _branchto(cp, 'e');
break;
case 'e': /* ELSE - branch to ENDIF */
cp = _branchto(cp, ';');
break;
case ';': /* ENDIF - just a marker */
break;
default:
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM: bad % "
"sequence\n");
#endif /* DEBUG */
free_stack(&stk);
return (NULL);
}
}
(void) strcpy(outp, added);
free_stack(&stk);
return (result);
}
char *
_branchto(register char *cp, char to)
{
register int level = 0;
register char c;
while (c = *cp++) {
if (c == '%') {
if ((c = *cp++) == to || c == ';') {
if (level == 0) {
return (cp);
}
}
if (c == '?')
level++;
if (c == ';')
level--;
}
}
#ifdef DEBUG
if (outf)
fprintf(outf, "TPARM: no matching ENDIF");
#endif /* DEBUG */
return (NULL);
}