/********************************************
error.c
copyright 2008-2023,2024 Thomas E. Dickey
copyright 1991-1994,1995 Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/

/*
 * $MawkId: error.c,v 1.31 2024/12/14 12:53:14 tom Exp $
 */

#define Visible_CELL
#define Visible_STRING
#define Visible_SYMTAB

#include <mawk.h>
#include <scan.h>
#include <bi_vars.h>

#ifdef DEBUG
#define FLUSH() fflush(stdout)
#else
#define FLUSH()			/* nothing */
#endif

/* for run time error messages only */
unsigned rt_nr, rt_fnr;
/* *INDENT-OFF* */
static const struct token_str {
    short token;
    const char str[12];
} token_str[] = {
    { EOF,                "end of file" },
    { NL,                 "end of line" },
    { SEMI_COLON,         ";" },
    { LBRACE,             "{" },
    { RBRACE,             "}" },
    { SC_FAKE_SEMI_COLON, "}" },
    { LPAREN,             "(" },
    { RPAREN,             ")" },
    { LBOX,               "[" },
    { RBOX,               "]" },
    { QMARK,              "?" },
    { COLON,              ":" },
    { OR,                 "||" },
    { AND,                "&&" },
    { ASSIGN,             "=" },
    { ADD_ASG,            "+=" },
    { SUB_ASG,            "-=" },
    { MUL_ASG,            "*=" },
    { DIV_ASG,            "/=" },
    { MOD_ASG,            "%=" },
    { POW_ASG,            "^=" },
    { EQ,                 "==" },
    { NEQ,                "!=" },
    { LT,                 "<" },
    { LTE,                "<=" },
    { GT,                 ">" },
    { GTE,                ">=" },
    { MATCH,              "" },	/* string_buff */
    { PLUS,               "+" },
    { MINUS,              "-" },
    { MUL,                "*" },
    { DIV,                "/" },
    { MOD,                "%" },
    { POW,                "^" },
    { NOT,                "!" },
    { COMMA,              "," },
    { INC_or_DEC,         "" },	/* string_buff */
    { DOUBLE,             "" },	/* string_buff */
    { STRING_,            "" },	/* string_buff */
    { ID,                 "" },	/* string_buff */
    { FUNCT_ID,           "" },	/* string_buff */
    { BUILTIN,            "" },	/* string_buff */
    { IO_OUT,             "" },	/* string_buff */
    { IO_IN,              "<" },
    { PIPE,               "|" },
    { DOLLAR,             "$" },
    { FIELD,              "$" },
    { 0,                  "" }
};
/* *INDENT-ON* */

/* if paren_cnt >0 and we see one of these, we are missing a ')' */
static const int missing_rparen[] =
{
    EOF, NL, SEMI_COLON, SC_FAKE_SEMI_COLON, RBRACE, 0
};

/* ditto for '}' */
static const int missing_rbrace[] =
{
    EOF, BEGIN, END, 0
};

static void
missing(int c, const char *n, unsigned ln)
{
    const char *s0, *s1;

    if (pfile_name) {
	s0 = pfile_name;
	s1 = ": ";
    } else
	s0 = s1 = "";

    errmsg(0, "%s%sline %u: missing %c near %s", s0, s1, ln, c, n);

    if (++compile_error_count >= MAX_COMPILE_ERRORS)
	mawk_exit(2);
}

void
yyerror(const char *s GCC_UNUSED)
{
    const char *ss = NULL;
    const struct token_str *p;
    const int *ip;

    for (p = token_str; p->token; p++)
	if (current_token == p->token) {
	    ss = p->str[0] ? p->str : string_buff;
	    break;
	}

    if (!ss)			/* search the keywords */
	ss = find_kw_str(current_token);

    if (ss) {
	if (paren_cnt)
	    for (ip = missing_rparen; *ip; ip++)
		if (*ip == current_token) {
		    missing(')', ss, token_lineno);
		    paren_cnt = 0;
		    return;
		}

	if (brace_cnt)
	    for (ip = missing_rbrace; *ip; ip++)
		if (*ip == current_token) {
		    missing('}', ss, token_lineno);
		    brace_cnt = 0;
		    return;
		}

	compile_error("syntax error at or near %s", ss);

    } else {			/* special cases */
	switch (current_token) {
	case UNEXPECTED:
	    unexpected_char();
	    break;

#ifndef LCOV_UNUSED
	case BAD_DECIMAL:
	    compile_error(
			     "syntax error in decimal constant %s",
			     string_buff);
	    break;

	case RE:
	    compile_error(
			     "syntax error at or near /%s/",
			     string_buff);
	    break;
#endif

	default:
	    compile_error("syntax error");
	    break;
	}
    }
}

/* generic error message with a hook into the system error
   messages if errnum > 0 */

void
errmsg(int errnum, const char *format, ...)
{
    va_list args;

    FLUSH();
    fprintf(stderr, "%s: ", progname);

#if OPT_TRACE > 0
    va_start(args, format);
    Trace("\n?? errmsg: ");
    TraceVA(format, args);
    Trace("\n");
    va_end(args);
#endif

    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);

    if (errnum > 0)
	fprintf(stderr, " (%s)", strerror(errnum));

    fprintf(stderr, "\n");
}

void
compile_error(const char *format, ...)
{
    va_list args;
    const char *s0, *s1;

    /* with multiple program files put program name in
       error message */
    if (pfile_name) {
	s0 = pfile_name;
	s1 = ": ";
    } else {
	s0 = s1 = "";
    }

    FLUSH();
    fprintf(stderr, "%s: %s%sline %u: ", progname, s0, s1, token_lineno);
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fprintf(stderr, "\n");
    if (++compile_error_count >= MAX_COMPILE_ERRORS)
	mawk_exit(2);
}

void
bozo(const char *s)
{
    errmsg(0, "bozo: %s", s);
    mawk_exit(3);
}

void
overflow(const char *s, unsigned size)
{
    errmsg(0, "program limit exceeded: %s size=%u", s, size);
    mawk_exit(2);
}

/* print as much as we know about where a rt error occurred */

static void
rt_where(void)
{
    if (FILENAME->type != C_STRING)
	cast1_to_s(FILENAME);

    fprintf(stderr, "\tFILENAME=\"%s\" FNR=%u NR=%u\n",
	    string(FILENAME)->str, rt_fnr, rt_nr);
}

void
rt_error(const char *format, ...)
{
    va_list args;

    FLUSH();
    fprintf(stderr, "%s: run time error: ", progname);
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    putc('\n', stderr);
    rt_where();
    mawk_exit(2);
}

/* run time */
void
rt_overflow(const char *s, unsigned size)
{
    errmsg(0, "program limit exceeded: %s size=%u", s, size);
    rt_where();
    mawk_exit(2);
}

void
unexpected_char(void)
{
    int c = yylval.ival;

    FLUSH();
    fprintf(stderr, "%s: %u: ", progname, token_lineno);
    if (c > ' ' && c < 127)
	fprintf(stderr, "unexpected character '%c'\n", c);
    else
	fprintf(stderr, "unexpected character 0x%02x\n", c);
    if (++compile_error_count >= MAX_COMPILE_ERRORS)
	mawk_exit(2);
}

const char *
type_to_str(int type)
{
    const char *retval = NULL;

    switch (type) {
    case ST_NONE:
	retval = "untyped variable";
	break;
    case ST_VAR:
	retval = "variable";
	break;
#ifndef LCOV_UNUSED
    case ST_KEYWORD:
	retval = "keyword";
	break;
    case ST_BUILTIN:
	retval = "builtin";
	break;
#endif
    case ST_ARRAY:
	retval = "array";
	break;
#ifndef LCOV_UNUSED
    case ST_FIELD:
	retval = "field";
	break;
    case ST_NR:
	retval = "NR";
	break;
    case ST_ENV:
	retval = "ENVIRON";
	break;
    case ST_FUNCT:
	retval = "function";
	break;
#endif
    case ST_LOCAL_VAR:
	retval = "local variable";
	break;
    case ST_LOCAL_NONE:
	retval = "local untyped variable";
	break;
    case ST_LOCAL_ARRAY:
	retval = "local array";
	break;
    default:
	bozo("type_to_str");
    }
    return retval;
}

/* emit an error message about a type clash */
void
type_error(SYMTAB * p)
{
    compile_error("illegal reference to %s %s",
		  type_to_str(p->type), p->name);
}
