/*
 * fhist - file history and comparison tools
 * Copyright (C) 1998, 2000, 2002, 2008, 2010, 2012 Peter Miller
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <common/ac/errno.h>
#include <common/ac/libintl.h>
#include <common/ac/limits.h>
#include <common/ac/string.h>
#include <common/ac/wctype.h>
#include <libexplain/malloc.h>
#include <libexplain/output.h>
#include <libexplain/realloc.h>

#include <common/arglex.h>
#include <common/error.h>
#include <common/error_intl.h>
#include <common/language.h>
#include <common/str.h>
#include <common/sub.h>
#include <common/sub/basename.h>
#include <common/sub/date.h>
#include <common/sub/dirname.h>
#include <common/sub/downcase.h>
#include <common/sub/errno.h>
#include <common/sub/expr.h>
#include <common/sub/ident.h>
#include <common/sub/length.h>
#include <common/sub/left.h>
#include <common/sub/plural.h>
#include <common/sub/private.h>
#include <common/sub/progname.h>
#include <common/sub/right.h>
#include <common/sub/upcase.h>
#include <common/sub/zero_pad.h>
#include <common/trace.h>
#include <common/wstr_list.h>


typedef wstring_ty *(*fp) (sub_context_ty *, wstring_list_ty *);

typedef struct table_ty table_ty;
struct table_ty
{
    const char      *name;
    fp              func;
    int             resubstitute;
    wstring_ty      *value;
    int             must_be_used     :1;
    int             append_if_unused :1;
    int             override         :1;
};

typedef struct diversion_ty diversion_ty;
struct diversion_ty
{
    size_t          pos;
    wstring_ty      *text;
    diversion_ty    *prev;
    int             resubstitute;
};


typedef struct collect_ty collect_ty;
struct collect_ty
{
    size_t          pos;
    size_t          size;
    wchar_t         *buf;
};

/* typedef struct sub_context_ty sub_context_ty; */
struct sub_context_ty
{
    diversion_ty    *diversion;
    table_ty        *sub_var_list;
    size_t          sub_var_size;
    size_t          sub_var_pos;
    const char      *suberr;
    int             errno_sequester;
};


static void
collect_constructor(collect_ty * cp)
{
    cp->buf = 0;
    cp->size = 0;
    cp->pos = 0;
}


static void
collect_destructor(collect_ty * cp)
{
    if (cp->buf)
        free(cp->buf);
    cp->buf = 0;
    cp->size = 0;
    cp->pos = 0;
}


/*
 * NAME
 *      collect
 *
 * SYNOPSIS
 *      void collect(collect_ty *, int c);
 *
 * DESCRIPTION
 *      The collect function is used to accumulate a string
 *      one character at a time.  No size limit.
 *
 * ARGUMENTS
 *      c - the character being collected
 */

static void
collect(collect_ty *cp, wchar_t c)
{
    if (cp->pos >= cp->size)
    {
        size_t nbytes;

        cp->size += (1L << 10);
        nbytes = cp->size * sizeof(wchar_t);
        cp->buf = explain_realloc_or_die(cp->buf, nbytes);
    }
    cp->buf[cp->pos++] = c;
}


static void
collect_n(collect_ty *cp, wchar_t *s, size_t n)
{
    while (n > 0)
    {
        collect(cp, *s++);
        --n;
    }
}


/*
 * NAME
 *      collect_end
 *
 * SYNOPSIS
 *      wstring_ty *collect_end(collect_ty *);
 *
 * DESCRIPTION
 *      The collect_end function is used to fetch the string
 *      accumulated with the collect function.
 *      The bufferer for the collect function is cleared.
 *
 * RETURNS
 *      wstring_ty *; pointer to the string in dynamic memory.
 */

static wstring_ty *
collect_end(collect_ty *cp)
{
    wstring_ty      *result;

    result = wstr_n_from_wc(cp->buf, cp->pos);
    cp->pos = 0;
    return result;
}


static void
sub_context_constructor(sub_context_ty *scp)
{
    trace(("sub_context_constructor()\n{\n"));
    scp->diversion = 0;
    scp->sub_var_list = 0;
    scp->sub_var_size = 0;
    scp->sub_var_pos = 0;
    scp->suberr = 0;
    scp->errno_sequester = 0;
    trace(("}\n"));
}


sub_context_ty *
sub_context_new(void)
{
    sub_context_ty  *scp;
    int             hang_on_a_second;

    hang_on_a_second = errno;
    scp = explain_malloc_or_die(sizeof(sub_context_ty));
    errno = hang_on_a_second;
    sub_context_constructor(scp);
    return scp;
}


static void
sub_context_destructor(sub_context_ty *scp)
{
    size_t          j;

    for (j = 0; j < scp->sub_var_pos; ++j)
        wstr_free(scp->sub_var_list[j].value);
    if (scp->sub_var_list)
        free(scp->sub_var_list);

    scp->diversion = 0;
    scp->sub_var_list = 0;
    scp->sub_var_size = 0;
    scp->sub_var_pos = 0;
    scp->suberr = 0;
    scp->errno_sequester = 0;
}


void
sub_context_delete(sub_context_ty * scp)
{
    sub_context_destructor(scp);
    free(scp);
}


void
sub_context_error_set(sub_context_ty * scp, const char *s)
{
    scp->suberr = s;
}



static table_ty table[] =
{
    { "Basename",   sub_basename,   0, 0, 0, 0, 0 },
    { "DAte",       sub_date,       0, 0, 0, 0, 0 },
    { "Dirname",    sub_dirname,    0, 0, 0, 0, 0 },
    { "DownCase",   sub_downcase,   0, 0, 0, 0, 0 },
    { "ERrno",      sub_errno,      0, 0, 0, 0, 0 },
    { "EXpression", sub_expression, 0, 0, 0, 0, 0 },
    /* File_Name                   */
    /* Guess                       */
    { "IDentifier", sub_identifier, 0, 0, 0, 0, 0 },
    { "LEFt",       sub_left,       0, 0, 0, 0, 0 },
    { "LENgth",     sub_length,     0, 0, 0, 0, 0 },
    /* Name                        */
    /* Number                      */
    { "PLural",     sub_plural,     0, 0, 0, 0, 0 },
    { "PROGname",   sub_progname,   0, 0, 0, 0, 0 },
    { "RIght",      sub_right,      0, 0, 0, 0, 0 },
    { "UpCase",     sub_upcase,     0, 0, 0, 0, 0 },
    /* Value                       */
    { "Zero_Pad",   sub_zero_pad,   0, 0, 0, 0, 0 },
};


/*
 * NAME
 *      sub_diversion
 *
 * SYNOPSIS
 *      void sub_diversion(wstring_ty *s, int resub);
 *
 * DESCRIPTION
 *      The sub_diversion function is used to divert input
 *      to a string, until that string is exhausted.
 *      When the string is exhausted, input will resume
 *      from the previous string.
 *
 * ARGUMENTS
 *      s - string to take as input
 */

static void
sub_diversion(sub_context_ty *scp, wstring_ty *s, int resubstitute)
{
    diversion_ty    *dp;

    trace(("sub_diversion(s = %8.8lX, resub = %d)\n{\n",
            (long)s, resubstitute));
    dp = (diversion_ty *)explain_malloc_or_die(sizeof(diversion_ty));
    dp->text = wstr_copy(s);
    dp->pos = 0;
    dp->resubstitute = resubstitute;
    dp->prev = scp->diversion;
    scp->diversion = dp;
    trace(("}\n"));
}


/*
 * NAME
 *      sub_diversion_close
 *
 * SYNOPSIS
 *      void sub_diversion_close(void);
 *
 * DESCRIPTION
 *      The sub_diversion_close function is used to
 *      release a diversion when it has been exhausted.
 */

static void
sub_diversion_close(sub_context_ty *scp)
{
    diversion_ty    *dp;

    trace(("sub_diversion_close()\n{\n"));
    assert(scp->diversion);
    dp = scp->diversion;
    scp->diversion = dp->prev;
    wstr_free(dp->text);
    free((char *)dp);
    trace(("}\n"));
}


/*
 * NAME
 *      execute
 *
 * SYNOPSIS
 *      void execute(wstring_list_ty *args);
 *
 * DESCRIPTION
 *      The execute function is used to perform the substitution
 *      described by the argument list.
 *
 * ARGUMENTS
 *      args - the name and arguments of the substitution
 */

static void
execute(sub_context_ty *scp, wstring_list_ty *arg)
{
    string_ty       *cmd;
    wstring_ty      *s;
    table_ty        *hit[20];
    size_t          nhits;
    table_ty        *tp;
    size_t          j;

    trace(("execute(scp = %08lX, arg = %08lX)\n{\n", (long)scp, (long)arg));
    if (arg->nitems == 0)
    {
        sub_context_ty  *scp2;

        scp2 = sub_context_new();
        fatal_intl(scp2, i18n("empty $${} substitution"));
        /* NOTREACHED */
        sub_context_delete(scp2);
    }

    /*
     * scan the variables
     */
    nhits = 0;
    cmd = wstr_to_str(arg->item[0]);
    trace_string(cmd->str_text);
    for (j = 0; j < scp->sub_var_pos; ++j)
    {
        tp = &scp->sub_var_list[j];
        trace(("tp = %08lX\n", (long)tp));
        trace_string(tp->name);
        if (arglex_compare(tp->name, cmd->str_text))
        {
            if (nhits < SIZEOF(hit))
                hit[nhits++] = tp;
        }
    }

    /*
     * scan the functions
     */
    for (tp = table; tp < ENDOF(table); ++tp)
    {
        if (arglex_compare(tp->name, cmd->str_text))
        {
            if (tp->override)
                goto override;
            if (nhits < SIZEOF(hit))
                hit[nhits++] = tp;
        }
    }
    str_free(cmd);

    /*
     * figure what to do
     */
    switch (nhits)
    {
    case 0:
        scp->suberr = i18n("unknown substitution name");
        s = 0;
        break;

    case 1:
        tp = hit[0];
      override:
        if (tp->value)
        {
            /*
             * flag that the variable has been used
             */
            tp->must_be_used = 0;
            s = wstr_copy(tp->value);
        }
        else
        {
            wstr_free(arg->item[0]);
            arg->item[0] = wstr_from_c(tp->name);
            s = tp->func(scp, arg);
        }
        break;

    default:
        scp->suberr = i18n("ambiguous substitution name");
        s = 0;
        break;
    }

    /*
     * deal with the result
     */
    if (s)
    {
        sub_diversion(scp, s, tp->resubstitute);
        wstr_free(s);
    }
    else
    {
        wstring_ty *s2;
        string_ty *s3;
        sub_context_ty *scp2;
        const char *the_error;

        assert(scp->suberr);
        s2 = wstring_list_to_wstring(arg, 0, arg->nitems, (char *)0);
        s3 = wstr_to_str(s2);
        wstr_free(s2);
        the_error = scp->suberr ? scp->suberr : "this is a bug";
        scp2 = sub_context_new();
        sub_var_set_string(scp2, "Name", s3);
        sub_var_set_charstar(scp2, "Message", gettext(the_error));
        fatal_intl(scp2, i18n("substitution $${$name} failed: $message"));
        /* NOTREACHED */
        sub_context_delete(scp2);
        str_free(s3);
    }
    trace(("}\n"));
}


/*
 * NAME
 *      sub_getc_meta
 *
 * SYNOPSIS
 *      void sub_getc_meta(void);
 *
 * DESCRIPTION
 *      The sub_getc_meta function is used to get a character from
 *      the current input string.  When the current string is exhaused,
 *      the previous string is resumed.
 *
 * RETURNS
 *      int - the chacater, or NUL to indicate end of input
 */

static wchar_t
sub_getc_meta(sub_context_ty *scp)
{
    wchar_t         result;
    diversion_ty    *dp;

    trace(("sub_getc_meta()\n{\n"));
    dp = scp->diversion;
    if (!dp)
        result = 0;
    else if (dp->pos >= dp->text->wstr_length)
        result = 0;
    else
        result = dp->text->wstr_text[dp->pos++];
#ifdef DEBUG
    if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
        trace(("return '%c';\n", (char)result));
    else
        trace(("return %4.4lX;\n", (long)result));
#endif
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *      sub_getc_meta_undo
 *
 * SYNOPSIS
 *      void sub_getc_meta_undo(int c);
 *
 * DESCRIPTION
 *      The sub_getc_meta_undo function is used to give back
 *      a character output by sub_getc_meta.
 *
 * ARGUMENTS
 *      c - character being given back
 *
 * CAVEAT
 *      Only push back what was read.
 */

static void
sub_getc_meta_undo(sub_context_ty *scp, wchar_t c)
{
    diversion_ty    *dp;

    trace(("sub_getc_meta_undo(%ld)\n{\n", (long)c));
#ifdef DEBUG
    if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
        trace(("c = '%c'\n", (char)c));
#endif
    dp = scp->diversion;
    assert(dp);
    if (!c)
    {
        assert(dp->pos == dp->text->wstr_length);
    }
    else
    {
        assert(dp->pos >= 1);
        dp->pos--;
        assert(c == dp->text->wstr_text[dp->pos]);
    }
    trace(("}\n"));
}


/*
 * NAME
 *      dollar
 *
 * SYNOPSIS
 *      wchar_t dollar(void);
 *
 * DESCRIPTION
 *      The dollar function is used to perform dollar ($) substitutions.
 *      On entry, the $ is expected to have been consumed.
 *
 *      The substitution is usually achieved as a side-effect,
 *      by using the sub_diversion function.
 *
 * RETURNS
 *      wchar_t a character to deliver as output,
 *              or NUL if none.
 */

static wchar_t sub_getc(sub_context_ty *); /* forward */

static wchar_t
dollar(sub_context_ty *scp)
{
    wstring_list_ty arg;
    int             result;
    wchar_t         c;
    wstring_ty      *s;
    wchar_t         quoted;
    collect_ty      tmp;

    trace(("dollar()\n{\n"));
    collect_constructor(&tmp);
    wstring_list_zero(&arg);
    result = 0;
    c = sub_getc_meta(scp);
    switch (c)
    {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        for (;;)
        {
            collect(&tmp, c);
            c = sub_getc_meta(scp);
            switch (c)
            {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                continue;

            default:
                sub_getc_meta_undo(scp, c);
                break;
            }
            break;
        }
        s = collect_end(&tmp);
        trace(("push arg\n"));
        wstring_list_append(&arg, s);
        wstr_free(s);
        execute(scp, &arg);
        wstring_list_free(&arg);
        break;

    case 'a':
    case 'b':
    case 'c':
    case 'd':
    case 'e':
    case 'f':
    case 'g':
    case 'h':
    case 'i':
    case 'j':
    case 'k':
    case 'l':
    case 'm':
    case 'n':
    case 'o':
    case 'p':
    case 'q':
    case 'r':
    case 's':
    case 't':
    case 'u':
    case 'v':
    case 'w':
    case 'x':
    case 'y':
    case 'z':
    case 'A':
    case 'B':
    case 'C':
    case 'D':
    case 'E':
    case 'F':
    case 'G':
    case 'H':
    case 'I':
    case 'J':
    case 'K':
    case 'L':
    case 'M':
    case 'N':
    case 'O':
    case 'P':
    case 'Q':
    case 'R':
    case 'S':
    case 'T':
    case 'U':
    case 'V':
    case 'W':
    case 'X':
    case 'Y':
    case 'Z':
        for (;;)
        {
            collect(&tmp, c);
            c = sub_getc_meta(scp);
            switch (c)
            {
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
            case 'g':
            case 'h':
            case 'i':
            case 'j':
            case 'k':
            case 'l':
            case 'm':
            case 'n':
            case 'o':
            case 'p':
            case 'q':
            case 'r':
            case 's':
            case 't':
            case 'u':
            case 'v':
            case 'w':
            case 'x':
            case 'y':
            case 'z':
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'H':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '_':
            case '-':
                continue;

            default:
                sub_getc_meta_undo(scp, c);
                break;
            }
            break;
        }
        s = collect_end(&tmp);
        trace(("push arg\n"));
        wstring_list_append(&arg, s);
        wstr_free(s);
        execute(scp, &arg);
        wstring_list_free(&arg);
        break;

    case '{':
        c = sub_getc(scp);
        for (;;)
        {
            /*
             * look for terminator
             */
            if (c == '}')
                break;

            /*
             * watch out for unterminated substitutions
             */
            if (!c)
            {
                sub_context_ty *scp2;

                scp2 = sub_context_new();
                fatal_intl(scp2, i18n("unterminated $${} substitution"));
                /* NOTREACHED */
                sub_context_delete(scp2);
                break;
            }

            /*
             * skip white space separating the arguments
             */
            if (iswspace(c))
            {
                c = sub_getc(scp);
                continue;
            }

            /*
             * collect the argument
             *  any run of non-white-space characters
             */
            quoted = 0;
            for (;;)
            {
                if (!c)
                {
                    if (quoted)
                    {
                        sub_context_ty *scp2;

                        scp2 = sub_context_new();
                        fatal_intl(scp2, i18n("unterminated $${} quotes"));
                        /* NOTREACHED */
                        sub_context_delete(scp2);
                    }
                    break;
                }
                if (!quoted && (iswspace(c) || c == '}'))
                    break;
                if (c == quoted)
                {
                    assert(quoted);
                    quoted = 0;
                }
                else if (c == '\'')
                {
                    assert(!quoted);
                    quoted = c;
                }
                else if (c == '\\')
                {
                    c = sub_getc(scp);
                    if (!c)
                    {
                        sub_context_ty *scp2;

                        scp2 = sub_context_new();
                        fatal_intl(scp2, i18n("unterminated $${} \\ sequence"));
                        /* NOTREACHED */
                        sub_context_delete(scp2);
                    }
                    collect(&tmp, c);
                }
                else
                    collect(&tmp, c);
                c = sub_getc(scp);
            }
            s = collect_end(&tmp);
            trace(("push arg\n"));
            wstring_list_append(&arg, s);
            wstr_free(s);
        }
        execute(scp, &arg);
        wstring_list_free(&arg);
        break;

    case '$':
        result = '$';
        break;

    default:
        sub_getc_meta_undo(scp, c);
        result = '$';
        break;
    }
    collect_destructor(&tmp);
#ifdef DEBUG
    if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
        trace(("return '%c';\n", (char)result));
    else
        trace(("return %4.4lX;\n", (long)result));
#endif
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *      sub_getc
 *
 * SYNOPSIS
 *      wchar_t sub_getc(void);
 *
 * DESCRIPTION
 *      The sub_getc function is used to get a character from
 *      the substitution stream.  This is used both for the final output
 *      and for fetching arguments to dollar ($) substitutions.
 *
 * RETURNS
 *      wchar_t a character from the stream,
 *              or NUL to indicate end of input.
 */

static wchar_t
sub_getc(sub_context_ty *scp)
{
    wchar_t         c;

    trace(("sub_getc()\n{\n"));
    for (;;)
    {
        c = sub_getc_meta(scp);
        switch (c)
        {
        default:
            break;

        case 0:
            if (!scp->diversion)
                break;
            sub_diversion_close(scp);
            continue;

        case '$':
            if (!scp->diversion->resubstitute)
                break;
            c = dollar(scp);
            if (!c)
                continue;
            break;
        }
        break;
    }
#ifdef DEBUG
    if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
        trace(("return '%c';\n", (char)c));
    else
        trace(("return %4.4lX;\n", (long)c));
#endif
    trace(("}\n"));
    return c;
}


static wstring_ty *
subst(sub_context_ty *scp, wstring_ty *s)
{
    collect_ty      buf;
    wchar_t         c;
    wstring_ty      *result;
    table_ty        *tp;
    table_ty        *the_end;
    int             error_count;

    trace(("subst(scp = %08lX, s = %8.8lX)\n{\n", (long)scp, (long)s));
    collect_constructor(&buf);
    sub_diversion(scp, s, 1);
    for (;;)
    {
        /*
         * get the next character
         */
        c = sub_getc(scp);
        if (!c)
            break;

        /*
         * save the character
         */
        collect(&buf, c);
    }

    /*
     * find any unused variables marked "append if unused"
     */
    the_end = scp->sub_var_list + scp->sub_var_pos;
    for (tp = scp->sub_var_list; tp < the_end; ++tp)
    {
        if (!tp->append_if_unused)
            continue;
        if (!tp->must_be_used)
            continue;
        assert(tp->value);

        /*
         * flag that the variable has been used
         */
        tp->must_be_used = 0;
        if (!tp->value->wstr_length)
            continue;

        /*
         * append to the buffer, separated by a space
         */
        collect(&buf, (wchar_t) ' ');
        collect_n(&buf, tp->value->wstr_text, tp->value->wstr_length);
    }

    /*
     * find any unused variables
     * and complain about them
     */
    error_count = 0;
    for (tp = scp->sub_var_list; tp < the_end; ++tp)
    {
        sub_context_ty *scp2;
        string_ty *tmp;

        if (!tp->must_be_used)
            continue;

        /*
         * Make sure the variables of this message are optional,
         * to avoid infinite loops if there is a mistake in the
         * translation string.
         */
        scp2 = sub_context_new();
        tmp = wstr_to_str(s);
        sub_var_set_string(scp2, "Message", tmp);
        sub_var_optional(scp2, "Message");
        sub_var_set_format(scp2, "Name", "$%s", tp->name);
        sub_var_optional(scp2, "Name");
        error_intl
            (scp2,
            i18n("in substitution \"$message\" variable \"$name\" unused"));
        sub_context_delete(scp2);
        str_free(tmp);
        ++error_count;
    }
    if (error_count > 0)
    {
        string_ty *tmp;
        sub_context_ty *scp2;

        /*
         * Make sure the variables of this message are optional,
         * to avoid infinite loops if there is a mistake in the
         * translation string.
         */
        scp2 = sub_context_new();
        tmp = wstr_to_str(s);
        sub_var_set_string(scp2, "Message", tmp);
        sub_var_optional(scp2, "Message");
        sub_var_set_format(scp2, "Number", "%d", error_count);
        sub_var_optional(scp2, "Number");
        fatal_intl
            (scp2, i18n("in substitution \"$message\" found unused variables"));
        /* NOTREACHED */
        str_free(tmp);
        sub_context_delete(scp2);
    }

    /*
     * clear the slate, ready for the next run
     */
    sub_var_clear(scp);
    result = collect_end(&buf);
    collect_destructor(&buf);
    trace(("return %8.8lX;\n", (long)result));
    trace(("}\n"));
    return result;
}


wstring_ty *
subst_intl_wide(sub_context_ty *scp, const char *msg)
{
    char            *tmp;
    wstring_ty      *s;
    wstring_ty      *result;

    trace(("subst_intl_wide(scp = %08lX, msg = \"%s\")\n{\n", (long)scp, msg));
    language_human();
    tmp = gettext(msg);
    language_C();
#ifdef DEBUG
#ifdef HAVE_GETTEXT
    if (tmp == msg)
        explain_output_error("warning: message \"%s\" has no translation", msg);
#endif /* HAVE_GETTEXT */
#endif /* DEBUG */
    s = wstr_from_c(tmp);
    result = subst(scp, s);
    wstr_free(s);
    trace(("return %8.8lX;\n", (long)result));
    trace(("}\n"));
    return result;
}


string_ty *
subst_intl(sub_context_ty *scp, const char *s)
{
    wstring_ty      *result_wide;
    string_ty       *result;

    trace(("subst_intl(scp = %08lX, s = \"%s\")\n{\n", (long)scp, s));
    result_wide = subst_intl_wide(scp, s);
    result = wstr_to_str(result_wide);
    wstr_free(result_wide);
    trace(("return \"%s\";\n", result->str_text));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *      substitute
 *
 * SYNOPSIS
 *      string_ty *substitute(string_ty *s);
 *
 * DESCRIPTION
 *      The substitute function is used to perform substitutions on
 *      strings.  Usually command strings, but not always.
 *
 *      The format of substitutions, and the commonly available
 *      substitutions, are described in aesub(5).
 *
 * ARGUMENTS
 *      cp      - the aegis change involved with the command
 *                This may never be NULL.
 *      s       - the string to be substituted.
 *
 * RETURNS
 *      string_ty *; pointer to string in dynamic memory
 */

string_ty *
substitute(sub_context_ty *scp, string_ty *s)
{
    wstring_ty      *ws;
    wstring_ty      *result_wide;
    string_ty       *result;

    assert(s);
    trace(("substitute(scp = %08lX, s = \"%s\")\n{\n", (long)scp, s->str_text));
    ws = str_to_wstr(s);
    result_wide = subst(scp, ws);
    wstr_free(ws);
    result = wstr_to_str(result_wide);
    wstr_free(result_wide);
    trace(("return \"%s\";\n", result->str_text));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *      sub_var_clear
 *
 * SYNOPSIS
 *      void sub_var_clear(void);
 *
 * DESCRIPTION
 *      The sub_var_clear function is used to clear all of
 *      the substitution variables.  Not usually needed manually,
 *      as this is done automatically at the end of every substitute().
 */

void
sub_var_clear(sub_context_ty *scp)
{
    size_t          j;

    trace(("sub_var_clear()\n{\n"));
    for (j = 0; j < scp->sub_var_pos; ++j)
        wstr_free(scp->sub_var_list[j].value);
    scp->sub_var_pos = 0;
    scp->errno_sequester = 0;
    trace(("}\n"));
}


/*
 * NAME
 *      sub_var_set
 *
 * SYNOPSIS
 *      void sub_var_set(char *name, char *fmt, ...);
 *
 * DESCRIPTION
 *      The sub_var_set function is used to set the value of a
 *      substitution variable.  These variables are command specific,
 *      as opposed to the functions which are always present.
 *      The user documentation does NOT make this distinction by
 *      using the names "variable" and "function", they are always referred
 *      to as "substitutions".
 *
 * ARGUMENTS
 *      name    - the name of the variable
 *      fmt,... - a format string and arguments to construct the value.
 *                Handed to str_vformat to make a (string_ty *) out of it.
 *
 * CAVEAT
 *      remains in scope until the next invokation of sub_var_clear,
 *      or until the end of the next invokation of substitute.
 */

void
sub_var_set_string(sub_context_ty *scp, const char *name, string_ty *value)
{
    table_ty        *svp;

    trace(("sub_var_set_string(scp = %08lX, name = \"%s\", "
            "value = \"%s\")\n{\n", (long)scp, name, value->str_text));

    if (scp->sub_var_pos >= scp->sub_var_size)
    {
        size_t          nbytes;

        scp->sub_var_size += 10;
        nbytes = scp->sub_var_size * sizeof(scp->sub_var_list[0]);
        scp->sub_var_list = explain_realloc_or_die(scp->sub_var_list, nbytes);
    }
    svp = &scp->sub_var_list[scp->sub_var_pos++];
    svp->name = name;
    svp->func = 0;
    svp->value = str_to_wstr(value);
    svp->must_be_used = 1;
    svp->append_if_unused = 0;
    svp->override = 0;
    svp->resubstitute = !svp->must_be_used;
    trace(("}\n"));
}


void
sub_var_set_format(sub_context_ty *scp, const char *name, const char *fmt, ...)
{
    va_list         ap;
    string_ty       *s;

    trace(("sub_var_set_format(scp = %08lX, name = \"%s\")\n{\n",
            (long)scp, name));
    va_start(ap, fmt);
    s = str_vformat(fmt, ap);
    va_end(ap);
    sub_var_set_string(scp, name, s);
    str_free(s);
    trace(("}\n"));
}


void
sub_var_set_long(sub_context_ty *scp, const char *name, long value)
{
    sub_var_set_format(scp, name, "%ld", value);
}


void
sub_var_set_charstar(sub_context_ty *scp, const char *name, const char *value)
{
    string_ty       *s;

    trace(("sub_var_set_charstar(scp = %08lX, name = \"%s\")\n{\n",
            (long)scp, name));
    s = str_from_c(value);
    sub_var_set_string(scp, name, s);
    str_free(s);
    trace(("}\n"));
}


void
sub_var_resubstitute(sub_context_ty *scp, const char *name)
{
    table_ty        *the_end;
    table_ty        *svp;

    the_end = scp->sub_var_list + scp->sub_var_pos;
    for (svp = scp->sub_var_list; svp < the_end; ++svp)
        if (!strcmp(svp->name, name))
            break;
    assert(svp < the_end);
    if (svp < the_end)
        svp->resubstitute = 1;
}


void
sub_var_override(sub_context_ty *scp, const char *name)
{
    table_ty        *the_end;
    table_ty        *svp;

    the_end = scp->sub_var_list + scp->sub_var_pos;
    for (svp = scp->sub_var_list; svp < the_end; ++svp)
        if (!strcmp(svp->name, name))
            break;
    assert(svp < the_end);
    if (svp < the_end)
        svp->override = 1;
}


void
sub_var_optional(sub_context_ty *scp, const char *name)
{
    table_ty        *the_end;
    table_ty        *svp;

    the_end = scp->sub_var_list + scp->sub_var_pos;
    for (svp = scp->sub_var_list; svp < the_end; ++svp)
        if (!strcmp(svp->name, name))
            break;
    assert(svp < the_end);
    if (svp < the_end)
        svp->must_be_used = 0;
}


void
sub_var_append_if_unused(sub_context_ty *scp, const char *name)
{
    table_ty        *the_end;
    table_ty        *svp;

    the_end = scp->sub_var_list + scp->sub_var_pos;
    for (svp = scp->sub_var_list; svp < the_end; ++svp)
        if (!strcmp(svp->name, name))
            break;
    assert(svp < the_end);
    if (svp < the_end)
    {
        svp->must_be_used = 1;
        svp->append_if_unused = 1;
    }
}


void
sub_errno_setx(sub_context_ty *scp, int x)
{
    scp->errno_sequester = x;
}


void
sub_errno_set(sub_context_ty *scp)
{
    sub_errno_setx(scp, errno);
}


int
sub_context_errno_get(sub_context_ty *scp)
{
    if (scp->errno_sequester == 0)
        scp->errno_sequester = errno;
    return scp->errno_sequester;
}


/* vim: set ts=8 sw=4 et : */
