root/lib/libterminfo/tparm.c
/* $NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $ */

/*
 * Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Roy Marples.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $");
#include <sys/param.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term_private.h>
#include <term.h>

#define LONG_STR_MAX ((CHAR_BIT * sizeof(long)) / 3)
#define BUFINC 128      /* Size to increment the terminal buffer by */

#define VA_LONG_LONG    1
#define VA_CHAR_INT     2
//#define VA_CHAR_LONG  3       /* No need for this yet */

static TERMINAL *dumbterm; /* For non thread safe functions */

typedef struct {
        long nums[20];
        char *strings[20];
        size_t offset;
} TPSTACK;

typedef struct {
        long num;
        char *string;
} TPVAR;

static int
push(long num, char *string, TPSTACK *stack)
{
        if (stack->offset >= sizeof(stack->nums)) {
                errno = E2BIG;
                return -1;
        }
        stack->nums[stack->offset] = num;
        stack->strings[stack->offset] = string;
        stack->offset++;
        return 0;
}

static int
pop(long *num, char **string, TPSTACK *stack)
{
        if (stack->offset == 0) {
                if (num)
                        *num = 0;
                if (string)
                        *string = NULL;
                errno = E2BIG;
                return -1;
        }
        stack->offset--;
        if (num)
                *num = stack->nums[stack->offset];
        if (string)
                *string = stack->strings[stack->offset];
        return 0;
}

static char *
checkbuf(TERMINAL *term, size_t len)
{
        char *buf;

        if (term->_bufpos + len >= term->_buflen) {
                len = term->_buflen + MAX(len, BUFINC);
                buf = realloc(term->_buf, len);
                if (buf == NULL)
                        return NULL;
                term->_buf = buf;
                term->_buflen = len;
        }
        return term->_buf;
}

static size_t
ochar(TERMINAL *term, int c)
{
        if (c == 0)
                c = 0200;
        /* Check we have space and a terminator */
        if (checkbuf(term, 2) == NULL)
                return 0;
        term->_buf[term->_bufpos++] = (char)c;
        return 1;
}

static size_t
onum(TERMINAL *term, const char *fmt, int num, size_t len)
{
        int l;
        size_t r;

        if (len < LONG_STR_MAX)
                len = LONG_STR_MAX;
        if (checkbuf(term, len + 2) == NULL)
                return 0;
        l = snprintf(term->_buf + term->_bufpos, len + 2, fmt, num);
        if (l == -1)
                return 0;
        r = (size_t)l;
        term->_bufpos += r;
        return r;
}

/*
  Make a pass through the string so we can work out
  which parameters are ints and which are char *.
  Basically we only use char * if %p[1-9] is followed by %l or %s.
*/
int
_ti_parm_analyse(const char *str, int *piss, int piss_len)
{
        int nparm, lpop;
        char c;

        nparm = 0;
        lpop = -1;
        while ((c = *str++) != '\0') {
                if (c != '%')
                        continue;
                c = *str++;
                switch (c) {
                        case 'l':
                        case 's':
                                if (lpop > 0) {
                                        if (lpop <= piss_len)
                                                piss[lpop - 1] = 1;
                                        else if (piss)
                                                errno = E2BIG;
                                }
                                break;
                        case 'p':
                                c = *str++;
                                if (c < '1' || c > '9') {
                                        errno = EINVAL;
                                        continue;
                                } else {
                                        lpop = c - '0';
                                        if (lpop > nparm)
                                                nparm = lpop;
                                }
                                break;
                        default:
                                lpop = -1;
                }
        }

        return nparm;
}

static char *
_ti_tiparm(TERMINAL *term, const char *str, int va_type, va_list parms)
{
        char c, fmt[64], *fp, *ostr;
        long val, val2;
        long dnums[26]; /* dynamic variables a-z, not preserved */
        size_t l, max, width, precision, olen;
        TPSTACK stack;
        TPVAR params[TPARM_MAX];
        unsigned int done, dot, minus;
        int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */

        if (str == NULL)
                return NULL;

        /*
          If not passed a terminal, malloc a dummy one.
          This means we can preserve buffers and variables per terminal and
          still work with non thread safe functions (which sadly are still the
          norm and standard).
        */
        if (term == NULL) {
                if (dumbterm == NULL) {
                        dumbterm = malloc(sizeof(*dumbterm));
                        if (dumbterm == NULL)
                                return NULL;
                        dumbterm->_buflen = 0;
                }
                term = dumbterm;
        }

        term->_bufpos = 0;
        /* Ensure we have an initial buffer */
        if (term->_buflen == 0) {
                term->_buf = malloc(BUFINC);
                if (term->_buf == NULL)
                        return NULL;
                term->_buflen = BUFINC;
        }

        memset(&piss, 0, sizeof(piss));
        max = (size_t)_ti_parm_analyse(str, piss, TPARM_MAX);

        /* Put our parameters into variables */
        memset(&params, 0, sizeof(params));
        for (l = 0; l < max; l++) {
                if (piss[l]) {
                        if (va_type == VA_LONG_LONG) {
                                /* This only works if char * fits into a long
                                 * on this platform. */
                                if (sizeof(char *) <= sizeof(long))
                                        params[l].string =
                                            (char *)va_arg(parms, long);
                                else {
                                        errno = ENOTSUP;
                                        return NULL;
                                }
                        } else
                                params[l].string = va_arg(parms, char *);
                } else {
                        if (va_type == VA_CHAR_INT)
                                params[l].num = (long)va_arg(parms, int);
                        else
                                params[l].num = va_arg(parms, long);
                }
        }

        memset(&stack, 0, sizeof(stack));
        while ((c = *str++) != '\0') {
                if (c != '%' || (c = *str++) == '%') {
                        if (c == '\0')
                                break;
                        if (ochar(term, c) == 0)
                                return NULL;
                        continue;
                }

                /* Handle formatting. */
                fp = fmt;
                *fp++ = '%';
                done = dot = minus = 0;
                width = precision = 0;
                val = 0;
                while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
                        switch (c) {
                        case 'c':
                        case 's':
                                *fp++ = c;
                                done = 1;
                                break;
                        case 'd':
                        case 'o':
                        case 'x':
                        case 'X':
                                *fp++ = 'l';
                                *fp++ = c;
                                done = 1;
                                break;
                        case '#':
                        case ' ':
                                *fp++ = c;
                                break;
                        case '.':
                                *fp++ = c;
                                if (dot == 0) {
                                        dot = 1;
                                        width = (size_t)val;
                                } else
                                        done = 2;
                                val = 0;
                                break;
                        case ':':
                                minus = 1;
                                break;
                        case '-':
                                if (minus)
                                        *fp++ = c;
                                else
                                        done = 1;
                                break;
                        default:
                                if (isdigit((unsigned char)c)) {
                                        val = (val * 10) + (c - '0');
                                        if (val > 10000)
                                                done = 2;
                                        else
                                                *fp++ = c;
                                } else
                                        done = 1;
                        }
                        if (done == 0)
                                c = *str++;
                }
                if (done == 2) {
                        /* Found an error in the format */
                        fp = fmt + 1;
                        *fp = *str;
                        olen = 0;
                } else {
                        if (dot == 0)
                                width = (size_t)val;
                        else
                                precision = (size_t)val;
                        olen = MAX(width, precision);
                }
                *fp++ = '\0';

                /* Handle commands */
                switch (c) {
                case 'c':
                        pop(&val, NULL, &stack);
                        if (ochar(term, (unsigned char)val) == 0)
                                return NULL;
                        break;
                case 's':
                        pop(NULL, &ostr, &stack);
                        if (ostr != NULL) {
                                int r;

                                l = strlen(ostr);
                                if (l < (size_t)olen)
                                        l = olen;
                                if (checkbuf(term, (size_t)(l + 1)) == NULL)
                                        return NULL;
                                r = snprintf(term->_buf + term->_bufpos, l + 1,
                                    fmt, ostr);
                                if (r != -1)
                                        term->_bufpos += (size_t)r;
                        }
                        break;
                case 'l':
                        pop(NULL, &ostr, &stack);
                        if (ostr == NULL)
                                l = 0;
                        else
                                l = strlen(ostr);
#ifdef NCURSES_COMPAT_57
                        if (onum(term, "%ld", (long)l, 0) == 0)
                                return NULL;
#else
                        push((long)l, NULL, &stack);
#endif
                        break;
                case 'd':
                case 'o':
                case 'x':
                case 'X':
                        pop(&val, NULL, &stack);
                        if (onum(term, fmt, (int)val, olen) == 0)
                                return NULL;
                        break;
                case 'p':
                        if (*str < '1' || *str > '9')
                                break;
                        l = (size_t)(*str++ - '1');
                        if (push(params[l].num, params[l].string, &stack))
                                return NULL;
                        break;
                case 'P':
                        pop(&val, NULL, &stack);
                        if (*str >= 'a' && *str <= 'z')
                                dnums[*str - 'a'] = val;
                        else if (*str >= 'A' && *str <= 'Z')
                                term->_snums[*str - 'A'] = val;
                        break;
                case 'g':
                        if (*str >= 'a' && *str <= 'z') {
                                if (push(dnums[*str - 'a'], NULL, &stack))
                                        return NULL;
                        } else if (*str >= 'A' && *str <= 'Z') {
                                if (push(term->_snums[*str - 'A'],
                                        NULL, &stack))
                                        return NULL;
                        }
                        break;
                case 'i':
                        if (piss[0] == 0)
                                params[0].num++;
                        if (piss[1] == 0)
                                params[1].num++;
                        break;
                case '\'':
                        if (push((long)(unsigned char)*str++, NULL, &stack))
                                return NULL;
                        while (*str != '\0' && *str != '\'')
                                str++;
                        if (*str == '\'')
                                str++;
                        break;
                case '{':
                        val = 0;
                        for (; isdigit((unsigned char)*str);  str++)
                                val = (val * 10) + (*str - '0');
                        if (push(val, NULL, &stack))
                                return NULL;
                        while (*str != '\0' && *str != '}')
                                str++;
                        if (*str == '}')
                                str++;
                        break;
                case '+':
                case '-':
                case '*':
                case '/':
                case 'm':
                case 'A':
                case 'O':
                case '&':
                case '|':
                case '^':
                case '=':
                case '<':
                case '>':
                        pop(&val, NULL, &stack);
                        pop(&val2, NULL, &stack);
                        switch (c) {
                        case '+':
                                val = val + val2;
                                break;
                        case '-':
                                val = val2 - val;
                                break;
                        case '*':
                                val = val * val2;
                                break;
                        case '/':
                                val = val ? val2 / val : 0;
                                break;
                        case 'm':
                                val = val ? val2 % val : 0;
                                break;
                        case 'A':
                                val = val && val2;
                                break;
                        case 'O':
                                val = val || val2;
                                break;
                        case '&':
                                val = val & val2;
                                break;
                        case '|':
                                val = val | val2;
                                break;
                        case '^':
                                val = val ^ val2;
                                break;
                        case '=':
                                val = val == val2;
                                break;
                        case '<':
                                val = val2 < val;
                                break;
                        case '>':
                                val = val2 > val;
                                break;
                        }
                        if (push(val, NULL, &stack))
                                return NULL;
                        break;
                case '!':
                case '~':
                        pop(&val, NULL, &stack);
                        switch (c) {
                        case '!':
                                val = !val;
                                break;
                        case '~':
                                val = ~val;
                                break;
                        }
                        if (push(val, NULL, &stack))
                                return NULL;
                        break;
                case '?': /* if */
                        break;
                case 't': /* then */
                        pop(&val, NULL, &stack);
                        if (val == 0) {
                                l = 0;
                                for (; *str != '\0'; str++) {
                                        if (*str != '%')
                                                continue;
                                        str++;
                                        if (*str == '?')
                                                l++;
                                        else if (*str == ';') {
                                                if (l > 0)
                                                        l--;
                                                else {
                                                        str++;
                                                        break;
                                                }
                                        } else if (*str == 'e' && l == 0) {
                                                str++;
                                                break;
                                        }
                                }
                        }
                        break;
                case 'e': /* else */
                        l = 0;
                        for (; *str != '\0'; str++) {
                                if (*str != '%')
                                        continue;
                                str++;
                                if (*str == '?')
                                        l++;
                                else if (*str == ';') {
                                        if (l > 0)
                                                l--;
                                        else {
                                                str++;
                                                break;
                                        }
                                }
                        }
                        break;
                case ';': /* fi */
                        break;
                }
        }
        term->_buf[term->_bufpos] = '\0';
        return term->_buf;
}

char *
ti_tiparm(TERMINAL *term, const char *str, ...)
{
        va_list va;
        char *ret;

        _DIAGASSERT(term != NULL);
        _DIAGASSERT(str != NULL);

        va_start(va, str);
        ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
        va_end(va);
        return ret;
}

char *
tiparm(const char *str, ...)
{
        va_list va;
        char *ret;

        _DIAGASSERT(str != NULL);

        va_start(va, str);
        ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
        va_end(va);
        return ret;
}

#ifdef VA_CHAR_LONG
char *
ti_tlparm(TERMINAL *term, const char *str, ...)
{
        va_list va;
        char *ret;

        _DIAGASSERT(term != NULL);
        _DIAGASSERT(str != NULL);

        va_start(va, str);
        ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
        va_end(va);
        return ret;
}

char *
tlparm(const char *str, ...)
{
        va_list va;
        char *ret;

        _DIAGASSERT(str != NULL);

        va_start(va, str);
        ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
        va_end(va);
        return ret;
}
#endif

static char *
_tparm(const char *str, ...)
{
        va_list va;
        char *ret;

        _DIAGASSERT(str != NULL);

        va_start(va, str);
        ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
        va_end(va);
        return ret;
}

char *
tparm(const char *str,
    long p1, long p2, long p3, long p4, long p5,
    long p6, long p7, long p8, long p9)
{

        return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}