/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/
#ifdef _MSC_VER
#   define _POSIX_
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __hpux
#   define _HPUX_SOURCE
#endif
#ifdef SCO
#   define _IBCS2
#endif
#include <limits.h>

#include "cpp.h"

#define NCONCAT 16384

/*
 * do a macro definition.  tp points to the name being defined in the line
 */
void
    dodefine(Tokenrow * trp)
{
    Token *tp;
    Nlist *np;
    Source *s;
    Tokenrow *def, *args;
#ifdef OS2
    static uchar location[(_MAX_PATH + 8) * NINC], *cp;
#else
    static uchar location[(PATH_MAX + 8) * NINC], *cp;
#endif
    tp = trp->tp + 1;
    if (tp >= trp->lp || tp->type != NAME)
    {
        error(ERROR, "#defined token is not a name");
        return;
    }
    np = lookup(tp, 1);
    if (np->flag & ISUNCHANGE)
    {
        error(ERROR, "#defined token %t can't be redefined", tp);
        return;
    }
    /* collect arguments */
    tp += 1;
    args = NULL;
    if (tp < trp->lp && tp->type == LP && tp->wslen == 0)
    {
        tp += 1;
        args = new(Tokenrow);
        maketokenrow(2, args);
        if (tp->type != RP)
        {
            /* macro with args */
            int narg = 0;

            int err = 0;

            for (;;)
            {
                Token *atp;

                if (tp->type != NAME)
                {
                    err++;
                    break;
                }
                if (narg >= args->max)
                    growtokenrow(args);
                for (atp = args->bp; atp < args->lp; atp++)
                    if (atp->len == tp->len
                        && strncmp((char *) atp->t, (char *) tp->t, tp->len) == 0)
                        error(ERROR, "Duplicate macro argument");
                *args->lp++ = *tp;
                narg++;
                tp += 1;
                if (tp->type == RP)
                    break;
                if (tp->type != COMMA)
                {
                    err++;
                    break;
                }
                tp += 1;
            }
            if (err)
            {
                error(ERROR, "Syntax error in macro parameters");
                return;
            }
        }
        tp += 1;
    }
    trp->tp = tp;
    if (((trp->lp) - 1)->type == NL)
        trp->lp -= 1;
    def = normtokenrow(trp);
    if (np->flag & ISDEFINED)
    {
        if (comparetokens(def, np->vp)
            || (np->ap == NULL) != (args == NULL)
            || (np->ap && comparetokens(args, np->ap)))
            error(ERROR, "Macro redefinition of %t (already defined at %s)", trp->bp + 2, np->loc);
    }
    if (args)
    {
        Tokenrow *tap;

        tap = normtokenrow(args);
        dofree(args->bp);
        args = tap;
    }
    np->ap = args;
    np->vp = def;
    np->flag |= ISDEFINED;

    /* build location string of macro definition */
    for (cp = location, s = cursource; s; s = s->next)
        if (*s->filename)
        {
            if (cp != location)
                *cp++ = ' ';
            sprintf((char *)cp, "%s:%d", s->filename, s->line);
            cp += strlen((char *)cp);
        }

    np->loc = newstring(location, strlen((char *)location), 0);

    if (Mflag)
    {
        if (np->ap)
            error(INFO, "Macro definition of %s(%r) [%r]", np->name, np->ap, np->vp);
        else
            error(INFO, "Macro definition of %s [%r]", np->name, np->vp);
    }
}

/*
 * Definition received via -D or -U
 */
void
    doadefine(Tokenrow * trp, int type)
{
    Nlist *np;
    static uchar onestr[2] = "1";
    static Token onetoken[1] = {{NUMBER, 0, 0, 1, onestr}};
    static Tokenrow onetr = {onetoken, onetoken, onetoken + 1, 1};

    trp->tp = trp->bp;
    if (type == 'U')
    {
        if (trp->lp - trp->tp != 2 || trp->tp->type != NAME)
            goto syntax;
        if ((np = lookup(trp->tp, 0)) == NULL)
            return;
        np->flag &= ~ISDEFINED;
        return;
    }

    if (type == 'A')
    {
        if (trp->tp >= trp->lp || trp->tp->type != NAME)
            goto syntax;
        trp->tp->type = ARCHITECTURE;
        np = lookup(trp->tp, 1);
        np->flag |= ISARCHITECTURE;
        trp->tp += 1;
        if (trp->tp >= trp->lp || trp->tp->type == END)
        {
            np->vp = &onetr;
            return;
        }
        else
            error(FATAL, "Illegal -A argument %r", trp);
    }

    if (trp->tp >= trp->lp || trp->tp->type != NAME)
        goto syntax;
    np = lookup(trp->tp, 1);
    np->flag |= ISDEFINED;
    trp->tp += 1;
    if (trp->tp >= trp->lp || trp->tp->type == END)
    {
        np->vp = &onetr;
        return;
    }
    if (trp->tp->type != ASGN)
        goto syntax;
    trp->tp += 1;
    if ((trp->lp - 1)->type == END)
        trp->lp -= 1;
    np->vp = normtokenrow(trp);
    return;
syntax:
    error(FATAL, "Illegal -D or -U argument %r", trp);
}

/*
 * Do macro expansion in a row of tokens.
 * Flag is NULL if more input can be gathered.
 */
void
    expandrow(Tokenrow * trp, char *flag)
{
    Token *tp;
    Nlist *np=NULL;

    if (flag)
        setsource(flag, -1, -1, "", 0);
    for (tp = trp->tp; tp < trp->lp;)
    {
        if (tp->type != NAME
            || quicklook(tp->t[0], tp->len > 1 ? tp->t[1] : 0) == 0
            || (np = lookup(tp, 0)) == NULL
            || (np->flag & (ISDEFINED | ISMAC)) == 0
            || (np->flag & ISACTIVE) != 0)
        {
            tp++;
            continue;
        }
        trp->tp = tp;
        if (np->val == KDEFINED)
        {
            tp->type = DEFINED;
            if ((tp + 1) < trp->lp && (tp + 1)->type == NAME)
                (tp + 1)->type = NAME1;
            else
                if ((tp + 3) < trp->lp && (tp + 1)->type == LP
                    && (tp + 2)->type == NAME && (tp + 3)->type == RP)
                    (tp + 2)->type = NAME1;
                else
                    error(ERROR, "Incorrect syntax for `defined'");
            tp++;
            continue;
        }
        else
            if (np->val == KMACHINE)
            {
                if (((tp - 1) >= trp->bp) && ((tp - 1)->type == SHARP))
                {
                    tp->type = ARCHITECTURE;
                    if ((tp + 1) < trp->lp && (tp + 1)->type == NAME)
                        (tp + 1)->type = NAME2;
                    else
                        if ((tp + 3) < trp->lp && (tp + 1)->type == LP
                            && (tp + 2)->type == NAME && (tp + 3)->type == RP)
                            (tp + 2)->type = NAME2;
                        else
                            error(ERROR, "Incorrect syntax for `#machine'");
                }
                tp++;
                continue;
            }

        if (np->flag & ISMAC)
            builtin(trp, np->val);
        else
            expand(trp, np);
        tp = trp->tp;
    }
    if (flag)
        unsetsource();
}

/*
 * Expand the macro whose name is np, at token trp->tp, in the tokenrow.
 * Return trp->tp at the first token next to be expanded
 * (ordinarily the beginning of the expansion)
 */
void
    expand(Tokenrow * trp, Nlist * np)
{
    Tokenrow ntr;
    int ntokc, narg;
    Tokenrow *atr[NARG + 1];

    if (Mflag == 2)
    {
        if (np->ap)
            error(INFO, "Macro expansion of %t with %s(%r)", trp->tp, np->name, np->ap);
        else
            error(INFO, "Macro expansion of %t with %s", trp->tp, np->name);
    }

    copytokenrow(&ntr, np->vp);         /* copy macro value */
    if (np->ap == NULL)                 /* parameterless */
        ntokc = 1;
    else
    {
        int i;

        ntokc = gatherargs(trp, atr, &narg);
        if (narg < 0)
        {                               /* not actually a call (no '(') */
            trp->tp++;
            return;
        }
        if (narg != rowlen(np->ap))
        {
            error(ERROR, "Disagreement in number of macro arguments");
            trp->tp += ntokc;
            return;
        }
        substargs(np, &ntr, atr);       /* put args into replacement */
        for (i = 0; i < narg; ++i)
        {
            dofree(atr[i]->bp);
            dofree(atr[i]);
        }
    }

    np->flag |= ISACTIVE;

    doconcat(&ntr);                     /* execute ## operators */
    ntr.tp = ntr.bp;
    makespace(&ntr, trp->tp);
    expandrow(&ntr, "<expand>");
    insertrow(trp, ntokc, &ntr);
    dofree(ntr.bp);

    np->flag &= ~ISACTIVE;

    return;
}

/*
 * Gather an arglist, starting in trp with tp pointing at the macro name.
 * Return total number of tokens passed, stash number of args found.
 * trp->tp is not changed relative to the tokenrow.
 */
int
    gatherargs(Tokenrow * trp, Tokenrow ** atr, int *narg)
{
    int parens = 1;
    int ntok = 0;
    Token *bp, *lp;
    Tokenrow ttr;
    int ntokp;
    int needspace;

    *narg = -1;                         /* means that there is no macro
                                         * call */
    /* look for the ( */
    for (;;)
    {
        trp->tp++;
        ntok++;
        if (trp->tp >= trp->lp)
        {
            gettokens(trp, 0);
            if ((trp->lp - 1)->type == END)
            {
                trp->lp -= 1;
                trp->tp -= ntok;
                return ntok;
            }
        }
        if (trp->tp->type == LP)
            break;
        if (trp->tp->type != NL)
            return ntok;
    }
    *narg = 0;
    ntok++;
    ntokp = ntok;
    trp->tp++;
    /* search for the terminating ), possibly extending the row */
    needspace = 0;
    while (parens > 0)
    {
        if (trp->tp >= trp->lp)
            gettokens(trp, 0);
        if (needspace)
        {
            needspace = 0;
            /* makespace(trp); [rh] */
        }
        if (trp->tp->type == END)
        {
            trp->lp -= 1;
            trp->tp -= ntok;
            error(ERROR, "EOF in macro arglist");
            return ntok;
        }
        if (trp->tp->type == NL)
        {
            trp->tp += 1;
            adjustrow(trp, -1);
            trp->tp -= 1;
            /* makespace(trp); [rh] */
            needspace = 1;
            continue;
        }
        if (trp->tp->type == LP)
            parens++;
        else
            if (trp->tp->type == RP)
                parens--;
        trp->tp++;
        ntok++;
    }
    trp->tp -= ntok;
    /* Now trp->tp won't move underneath us */
    lp = bp = trp->tp + ntokp;
    for (; parens >= 0; lp++)
    {
        if (lp->type == LP)
        {
            parens++;
            continue;
        }
        if (lp->type == RP)
            parens--;
        if (lp->type == DSHARP)
            lp->type = DSHARP1;         /* ## not special in arg */
        if ((lp->type == COMMA && parens == 0)
            || (parens < 0 && (lp - 1)->type != LP))
        {
            if (*narg >= NARG - 1)
                error(FATAL, "Sorry, too many macro arguments");
            ttr.bp = ttr.tp = bp;
            ttr.lp = lp;
            atr[(*narg)++] = normtokenrow(&ttr);
            bp = lp + 1;
        }
    }
    return ntok;
}

/*
 * substitute the argument list into the replacement string
 *  This would be simple except for ## and #
 */
void
    substargs(Nlist * np, Tokenrow * rtr, Tokenrow ** atr)
{
    Tokenrow tatr;
    Token *tp;
    int ntok, argno;

    for (rtr->tp = rtr->bp; rtr->tp < rtr->lp;)
    {
        if (rtr->tp->type == SHARP)
        {                               /* string operator */
            tp = rtr->tp;
            rtr->tp += 1;
            if ((argno = lookuparg(np, rtr->tp)) < 0)
            {
                error(ERROR, "# not followed by macro parameter");
                continue;
            }
            ntok = 1 + (rtr->tp - tp);
            rtr->tp = tp;
            insertrow(rtr, ntok, stringify(atr[argno]));
            continue;
        }
        if (rtr->tp->type == NAME
            && (argno = lookuparg(np, rtr->tp)) >= 0)
        {
            if (((rtr->tp + 1) < rtr->lp && (rtr->tp + 1)->type == DSHARP)
                || (rtr->tp != rtr->bp  && (rtr->tp - 1)->type == DSHARP))
            {
                copytokenrow(&tatr, atr[argno]);
                makespace(&tatr, rtr->tp);
                insertrow(rtr, 1, &tatr);
                dofree(tatr.bp);
            }
            else
            {
                copytokenrow(&tatr, atr[argno]);
                makespace(&tatr, rtr->tp);
                expandrow(&tatr, "<macro>");
                insertrow(rtr, 1, &tatr);
                dofree(tatr.bp);
            }
            continue;
        }
        rtr->tp++;
    }
}

/*
 * Evaluate the ## operators in a tokenrow
 */
void
    doconcat(Tokenrow * trp)
{
    Token *ltp, *ntp;
    Tokenrow ntr;
    int len;

    for (trp->tp = trp->bp; trp->tp < trp->lp; trp->tp++)
    {
        if (trp->tp->type == DSHARP1)
            trp->tp->type = DSHARP;
        else
            if (trp->tp->type == DSHARP)
            {
                int  i;
                char tt[NCONCAT];

                ltp = trp->tp - 1;
                ntp = trp->tp + 1;

                if (ltp < trp->bp || ntp >= trp->lp)
                {
                    error(ERROR, "## occurs at border of replacement");
                    continue;
                }

                ntp = ltp;
                i   = 1;
                len = 0;

                do
                {
                    if (len + ntp->len + ntp->wslen > sizeof(tt))
                    {
                        error(ERROR, "## string concatination buffer overrun");
                        break;
                    }

                    strncpy((char *) tt + len, (char *) ntp->t - ntp->wslen,
                            ntp->len + ntp->wslen);
                    len += ntp->len + ntp->wslen;

                    ntp = trp->tp + i;
                    i++;
                }
                while (ntp < trp->lp);

                tt[len] = '\0';
                setsource("<##>", -1, -1, tt, 0);
                maketokenrow(3, &ntr);
                gettokens(&ntr, 1);
                unsetsource();
                if (ntr.bp->type == UNCLASS)
                    error(WARNING, "Bad token %r produced by ##", &ntr);
                doconcat(&ntr);
                trp->tp = ltp;
                makespace(&ntr, ltp);
                insertrow(trp, ntp - ltp, &ntr);
                dofree(ntr.bp);
                trp->tp--;
            }
    }
}

/*
 * tp is a potential parameter name of macro mac;
 * look it up in mac's arglist, and if found, return the
 * corresponding index in the argname array.  Return -1 if not found.
 */
int
    lookuparg(Nlist * mac, Token * tp)
{
    Token *ap;

    if (tp->type != NAME || mac->ap == NULL)
        return -1;
    for (ap = mac->ap->bp; ap < mac->ap->lp; ap++)
    {
        if (ap->len == tp->len && strncmp((char *) ap->t, (char *) tp->t, ap->len) == 0)
            return ap - mac->ap->bp;
    }
    return -1;
}

/*
 * Return a quoted version of the tokenrow (from # arg)
 */
#define STRLEN  512
Tokenrow *
    stringify(Tokenrow * vp)
{
    static Token t = {STRING, 0, 0, 0, NULL};
    static Tokenrow tr = {&t, &t, &t + 1, 1};
    Token *tp;
    uchar s[STRLEN];
    uchar *sp = s, *cp;
    int i, instring;

    *sp++ = '"';
    for (tp = vp->bp; tp < vp->lp; tp++)
    {
        instring = tp->type == STRING || tp->type == CCON;
        if (sp + 2 * tp->len >= &s[STRLEN - 10])
        {
            error(ERROR, "Stringified macro arg is too long");
            break;
        }
        for (i = 0, cp = tp->t; (unsigned int)i < tp->len; i++)
        {
            if (instring && (*cp == '"' || *cp == '\\'))
                *sp++ = '\\';
            *sp++ = *cp++;
        }
    }
    *sp++ = '"';
    *sp = '\0';
    sp = s;
    t.len = strlen((char *) sp);
    t.t = newstring(sp, t.len, 0);
    return &tr;
}

/*
 * expand a builtin name
 */
void
    builtin(Tokenrow * trp, int biname)
{
    char *op;
    Token *tp;
    Source *s;

    tp = trp->tp;
    trp->tp++;
    /* need to find the real source */
    s = cursource;
    while (s && s->fd == -1)
        s = s->next;
    if (s == NULL)
        s = cursource;
    /* most are strings */
    tp->type = STRING;
    if (tp->wslen)
    {
        *outptr++ = ' ';
        tp->wslen = 1;
    }
    op = outptr;
    *op++ = '"';
    switch (biname)
    {

        case KLINENO:
            tp->type = NUMBER;
            op = outnum(op - 1, s->line);
            break;

        case KFILE:
            {
                char *src = s->filename;

                while ((*op++ = *src++) != 0)
                    if (src[-1] == '\\')
                        *op++ = '\\';
                op--;
                break;
            }

        case KDATE:
            strncpy(op, curtime + 4, 7);
            strncpy(op + 7, curtime + 20, 4);
            op += 11;
            break;

        case KTIME:
            strncpy(op, curtime + 11, 8);
            op += 8;
            break;

        default:
            error(ERROR, "cpp botch: unknown internal macro");
            return;
    }
    if (tp->type == STRING)
        *op++ = '"';
    tp->t = (uchar *) outptr;
    tp->len = op - outptr;
    outptr = op;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
