/*
 * dnsutl - utilities to make DNS easier to configure
 * Copyright (C) 1999-2001, 2003, 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/stdarg.h>
#include <ac/stdio.h>
#include <ac/string.h>

#include <error.h>
#include <lex.h>
#include <mem.h>
#include <mprintf.h>
#include <str.h>
#include <gram.gen.h> /* must be after str.h */


static const char *fn;
static FILE     *fp;
static int      linum;
static int      incriment_line_number;
static int      non_printing_whine;
extern gram_STYPE gram_lval;
static int      error_count;
static int      word_on_line;


void
lex_open(const char *filename)
{
    if (filename)
    {
        fn = filename;
        fp = fopen(fn, "r");
        if (!fp)
            nfatal("open \"%s\"", fn);
    }
    else
    {
        fn = "(stdin)";
        fp = stdin;
    }
    linum = 1;
    word_on_line = 0;
}


void
lex_close(void)
{
    if (error_count)
    {
        fatal
        (
            "%s: found %d fatal error%s",
            fn,
            error_count,
            (error_count == 1 ? "" : "s")
        );
    }
    if (fp != stdin)
        fclose(fp);
    fn = 0;
    fp = 0;
    linum = 0;
    error_count = 0;
    incriment_line_number = 0;
}


static int
lex_getc(void)
{
    int             c;

    for (;;)
    {
        c = getc(fp);
        switch (c)
        {
        case EOF:
            if (ferror(fp))
                nfatal("read \"%s\"", fn);
            break;

#if 0
        case '\\':
            c = getc(fp);
            if (c == '\n')
            {
                ++incriment_line_number;
                continue;
            }
            if (c != EOF)
                ungetc(c, fp);
            c = '\\';
            break;
#endif
        }
        return c;
    }
}


static void
lex_ungetc(int c)
{
    if (c != EOF)
        ungetc(c, fp);
}


static int
inner_lex(void)
{
    int             c;
    int             bufpos;
    static int      bufmax;
    static char     *buffer;

    if (incriment_line_number)
    {
        linum += incriment_line_number;
        incriment_line_number = 0;
    }
    for (;;)
    {
        c = lex_getc();
        switch (c)
        {
        case EOF:
            if (ferror(fp))
                nfatal("read \"%s\"", fn);
            return 0;

        case '\n':
            ++incriment_line_number;
            non_printing_whine = 0;
            return EOLN;

        case ' ':
        case '\t':
            break;

        default:
            bufpos = 0;
            non_printing_whine = 0;
            for (;;)
            {
                if (!isprint(c) && !non_printing_whine)
                    gram_error("line contains non printing character");
                if (bufpos >= bufmax)
                {
                    bufmax = bufmax * 2 + 16;
                    buffer = mem_change_size(buffer, bufmax);
                }
                buffer[bufpos++] = c;

                c = lex_getc();
                if (c == '\n' || c == ' ' || c == '\t'
                    || c == EOF || c == ';')
                {
                    lex_ungetc(c);
                    break;
                }
            }
            gram_lval.lv_string = str_n_from_c(buffer, bufpos);
            return STRING;

        case ';':
            /*
             * throw comments away
             */
            for (;;)
            {
                c = lex_getc();
                if (c == '\n' || c == EOF)
                {
                    lex_ungetc(c);
                    break;
                }
            }
            break;
        }
    }
}


typedef struct table_ty table_ty;
struct table_ty
{
    const char      *name;
    int             token;
};

static table_ty table[] =
{
    { "cache", CACHE, },
    { "directory", DIRECTORY, },
    { "domain", DOMAIN, },
    { "forwarders", FORWARDERS, },
    { "options", OPTIONS, },
    { "primary", PRIMARY, },
    { "secondary", SECONDARY, },
};


static int
reserved(string_ty *s)
{
    table_ty        *tp;

    /* This is slow.  I'll speed it up if it's ever a problem. */
    for (tp = table; tp < ENDOF(table); ++tp)
    {
        if (0 == strcmp(s->str_text, tp->name))
            return tp->token;
    }
    return STRING;
}


int
gram_lex(void)
{
    int n = inner_lex();
    if (word_on_line == 0 && n == STRING)
    {
        n = reserved(gram_lval.lv_string);
        if (n != STRING)
            str_free(gram_lval.lv_string);
    }
    if (n == EOLN)
        word_on_line = 0;
    else
        ++word_on_line;
    return n;
}


void
gram_error(const char *s, ...)
{
    va_list         ap;
    char            *msg;

    va_start(ap, s);
    msg = vmprintf(s, ap);
    va_end(ap);
    msg = mem_copy_string(msg);
    error("%s: %d: %s", fn, linum, msg);
    mem_free(msg);
    ++error_count;
    if (error_count >= 20)
        fatal("%s: too many fatal errors, aborting", fn);
}


void
lex_warning(const char *s, ...)
{
    va_list         ap;
    char            *msg;

    va_start(ap, s);
    msg = vmprintf(s, ap);
    va_end(ap);
    msg = mem_copy_string(msg);
    error("%s: %d: warning: %s", fn, linum, msg);
    mem_free(msg);
}


syntax highlighted by Code2HTML, v. 0.9.1