/*
 *      dnsutl - utilities to make DNS easier to configure
 *      Copyright (C) 1991-1993, 1995, 1999, 2006, 2007 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 <ac/ctype.h>
#include <ac/stddef.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <math.h>

#include <arglex.h>
#include <error.h>
#include <mem.h>

static arglex_table_ty table[] =
{
    { "-", arglex_token_stdio },
    { "-Help", arglex_token_help },
    { "-VERSion", arglex_token_version },
    { 0, 0 } /* end marker */
};

static int      argc;
static char   **argv;
arglex_value_ty arglex_value;
arglex_token_ty arglex_token;
char           *progname;
static arglex_table_ty *utable;
static const char *partial;


static char *
base_name(char *s)
{
    char            *bp;
    char            *ep;

    bp = s;
    ep = 0;
    while (*s)
    {
        if (s[0] == '/' && s[1] && s[1] != '/')
            bp = s + 1;
        if (s > bp && s[0] == '/' && s[-1] != '/')
            ep = s;
        ++s;
    }
    if (!ep)
        ep = s;
    *s = 0;
    return bp;
}

void
arglex_init(int ac, char **av, arglex_table_ty * tp)
{
    progname = base_name(av[0]);

    argc = ac - 1;
    argv = av + 1;
    utable = tp;
}


int
argcmp(const char *formal, const char *actual)
{
    char            fc;
    char            ac;
    int             result;

    for (;;)
    {
        ac = *actual++;
        if (isupper(ac))
            ac = tolower(ac);
        fc = *formal++;
        switch (fc)
        {
        case 0:
            result = !ac;
            goto ret;

        case '_':
            if (ac == '-')
                break;
            /* fall through... */

        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':
            /*
             * optional characters
             */
            if (ac == fc && argcmp(formal, actual))
            {
                result = 1;
                goto ret;
            }
            /*
             * skip forward to next
             * mandatory character, or after '_'
             */
            while (islower(*formal))
                ++formal;
            if (*formal == '_')
            {
                ++formal;
                if (ac == '_' || ac == '-')
                    ++actual;
            }
            --actual;
            break;

        case '*':
            /*
             * This is a hack, it should really
             * check for a match match the stuff after
             * the '*', too, a la glob.
             */
            if (!ac)
            {
                result = 0;
                goto ret;
            }
            partial = actual - 1;
            result = 1;
            goto ret;

        case '\\':
            if (actual[-1] != *formal++)
            {
                result = 0;
                goto ret;
            }
            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':
            fc = tolower(fc);
            /* fall through... */

        default:
            /*
             * mandatory characters
             */
            if (fc != ac)
            {
                result = 0;
                goto ret;
            }
            break;
        }
    }
  ret:
    return result;
}


static int
is_a_number(const char *s)
{
    long            n;
    int             sign;

    n = 0;
    switch (*s)
    {
    default:
        sign = 1;
        break;

    case '+':
        s++;
        sign = 1;
        break;

    case '-':
        s++;
        sign = -1;
        break;
    }
    switch (*s)
    {
    case '0':
        if ((s[1] == 'x' || s[1] == 'X') && s[2])
        {
            s += 2;
            for (;;)
            {
                switch (*s)
                {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    n = n * 16 + *s++ - '0';
                    continue;

                case 'A':
                case 'B':
                case 'C':
                case 'D':
                case 'E':
                case 'F':
                    n = n * 16 + *s++ - 'A' + 10;
                    continue;

                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                    n = n * 16 + *s++ - 'a' + 10;
                    continue;
                }
                break;
            }
        }
        else
        {
            for (;;)
            {
                switch (*s)
                {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                    n = n * 8 + *s++ - '0';
                    continue;
                }
                break;
            }
        }
        break;

    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        for (;;)
        {
            switch (*s)
            {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                n = n * 10 + *s++ - '0';
                continue;
            }
            break;
        }
        break;

    default:
        return 0;
    }
    if (*s)
        return 0;
    arglex_value.alv_number = n * sign;
    return 1;
}


arglex_token_ty
arglex(void)
{
    arglex_table_ty *tp;
    int             j;
    arglex_table_ty *hit[20];
    int             nhit;
    static const char *pushback[3];
    static int      pushback_depth;
    const char      *arg;

    if (pushback_depth)
    {
        /*
         * the second half of a "-foo=bar" style argument, usually
         */
        arg = pushback[--pushback_depth];
    }
    else
    {
        if (argc <= 0)
        {
            arglex_token = arglex_token_eoln;
            arg = "";
            goto ret;
        }
        arg = argv[0];
        argc--;
        argv++;

        /*
         * See if it looks like a GNU "-foo=bar" option.
         * Split it at the '=' to make it something the
         * rest of the code understands.
         */
        if (arg[0] == '-' && arg[1] != '=')
        {
            char            *eqp;

            eqp = strchr(arg, '=');
            if (eqp)
            {
                pushback[pushback_depth++] = eqp + 1;
                *eqp = 0;
            }
        }

        /*
         * Turn the GNU-style leading "--"
         * into "-" if necessary.
         */
        if (arg[0] == '-' && arg[1] == '-' && arg[2] && !is_a_number(arg + 1))
            ++arg;
    }

    if (is_a_number(arg))
    {
        arglex_token = arglex_token_number;
        goto ret;
    }

    nhit = 0;
    partial = 0;
    for (tp = table; tp->name; tp++)
    {
        if (argcmp(tp->name, arg))
            hit[nhit++] = tp;
    }
    if (utable)
    {
        for (tp = utable; tp->name; tp++)
        {
            if (argcmp(tp->name, arg))
                hit[nhit++] = tp;
        }
    }
    switch (nhit)
    {
    case 0:
        break;

    case 1:
        if (partial)
            pushback[pushback_depth++] = partial;
        arg = hit[0]->name;
        arglex_token = hit[0]->token;
        goto ret;

    default:
    {
        size_t          len;
        char            *buf;

        len = strlen(hit[0]->name + 1);
        for (j = 1; j < nhit; ++j)
            len += strlen(hit[j]->name) + 2;
        buf = mem_alloc(len);
        strcpy(buf, hit[0]->name);
        for (j = 1; j < nhit; ++j)
        {
            strcat(buf, ", ");
            strcat(buf, hit[j]->name);
        }
        fatal("option \"%s\" ambiguous (%s)", arg, buf);
    }
    }

    /* not found in the table */
    if (arg[0] == '-')
        arglex_token = arglex_token_option;
    else
        arglex_token = arglex_token_string;

  ret:
#if 0
    printf("arglex %d\n", arglex_token);
#endif
    arglex_value.alv_string = arg;
    return arglex_token;
}


syntax highlighted by Code2HTML, v. 0.9.1