/*
 * Copyright (c) 2002, The Tendra Project <http://www.ten15.org/>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *    		 Crown Copyright (c) 1997
 *
 *    This TenDRA(r) Computer Program is subject to Copyright
 *    owned by the United Kingdom Secretary of State for Defence
 *    acting through the Defence Evaluation and Research Agency
 *    (DERA).  It is made available to Recipients with a
 *    royalty-free licence for its use, reproduction, transfer
 *    to other parties and amendment for any purpose not excluding
 *    product development provided that any such use et cetera
 *    shall be deemed to be acceptance of the following conditions:-
 *
 *        (1) Its Recipients shall ensure that this Notice is
 *        reproduced upon any copies or amended versions of it;
 *
 *        (2) Any amended version of it shall be clearly marked to
 *        show both the nature of and the organisation responsible
 *        for the relevant amendment or amendments;
 *
 *        (3) Its onward transfer from a recipient to another
 *        party shall be deemed to be that party's acceptance of
 *        these conditions;
 *
 *        (4) DERA gives no warranty or assurance as to its
 *        quality or suitability for any purpose and DERA accepts
 *        no liability whatsoever in relation to any use to which
 *        it may be put.
 *
 * $TenDRA: tendra/src/tools/tnc/analyser.c,v 1.8 2005/09/21 16:59:15 stefanf Exp $
 */


#include "config.h"
#include "msgcat.h"

#include "types.h"
#include "read_types.h"
#include "analyser.h"
#include "file.h"
#include "names.h"
#include "utility.h"


/*
 *    CURRENT AND PREVIOUS LINE NUMBERS
 *
 *    The current line in the file is recorded.  The previous line (where
 *    any errors were likely to have been) is also saved.
 */

long line_no = 1;


/*
 *    FORM OF INPUT
 *
 *    This flag controls whether the input should be lisp-like (default)
 *    or c-like.
 */

BoolT func_input = 0;


/*
 *    ANALYSE FLAGS
 *
 *    The looked_ahead flag is true to indicate that the next word has
 *    already been read.  The really_analyse flag is false to indicate
 *    that the next word may be ignored.
 */

boolean looked_ahead = 0;
static boolean really_analyse = 1;


/*
 *    INPUT BUFFER
 *
 *    The input is read into a buffer.
 */

#define BUFFSIZE	5000
static char word_buff [ BUFFSIZE ];


/*
 *    LAST WORD READ
 *
 *    The word just read from the input file is word.  It has length
 *    word_length and input type word_type.
 */

char *word = "";
long word_length;
int word_type = INPUT_EOF;


/*
 *    PENDING CHARACTER
 *
 *    In reading a word we almost always read one too many character.
 *    This is stored in pending.  A value of 0 indicates that there is
 *    no pending character.
 */

static int pending = 0;


/*
 *    READ THE NEXT WORD
 *
 *    The next word is read from the input file.
 */

void
read_word(void)
{
    int c;
    char *p;
    int negate = 0;
    unsigned base = 10;

    /* If we have looked ahead one, return last value */
    if (looked_ahead) {
		looked_ahead = 0;
		return;
    }

    /* Get the first letter */
    if (pending) {
		c = pending;
		if (c == EOF) {
			word_type = INPUT_EOF;
			return;
		}
		pending = 0;
    } else {
		c = getc (input);
		if (c == '\n') crt_line_no++;
    }

    /* Step over any white space and comments */
    while (white_space (c) || c == '#') {
		if (c == '#') {
			/* Comments go to the end of the line */
			while (c = getc (input), c != '\n') {
				if (c == EOF) {
					MSG_end_of_file_in_comment ();
					word_type = INPUT_EOF;
					pending = EOF;
					return;
				}
			}
			crt_line_no++;
		} else {
			c = getc (input);
			if (c == '\n') crt_line_no++;
		}
    }
    line_no = crt_line_no;

    /* Check for end of file */
    if (c == EOF) {
		word_type = INPUT_EOF;
		pending = EOF;
		return;
    }

    /* Check for open brackets */
    if (c == '(') {
		word = "(";
		word_type = INPUT_OPEN;
		return;
    }

    /* Check for close brackets */
    if (c == ')') {
		word = ")";
		word_type = INPUT_CLOSE;
		return;
    }

    if (func_input) {
		/* Check for commas (c-like input only) */
		if (c == ',') {
			word = ",";
			word_type = INPUT_COMMA;
			return;
		}

		/* Check for semicolons (c-like input only) */
		if (c == ';') {
			word = ";";
			word_type = INPUT_SEMICOLON;
			return;
		}
    }

    /* Check for strings */
    if (c == '"') {
		boolean escaped;
		p = word_buff;
		do {
			boolean ignore = 0;
			escaped = 0;
			c = getc (input);
			if (c == '\n') {
				MSG_new_line_in_string ();
				crt_line_no++;
				line_no = crt_line_no;
				ignore = 1;
			}
			if (c == '\\') {
				escaped = 1;
				c = getc (input);
				if (c == '\n') {
					crt_line_no++;
					line_no = crt_line_no;
					ignore = 1;
				} else if (c == 'n') {
					c = '\n';
				} else if (c == 't') {
					c = '\t';
				} else if (octal_digit (c)) {
					int e = (c - '0');
					c = getc (input);
					if (!octal_digit (c)) {
						MSG_invalid_escape_sequence ();
						c = '0';
					}
					e = 8 * e + (c - '0');
					c = getc (input);
					if (!octal_digit (c)) {
						MSG_invalid_escape_sequence ();
						c = '0';
					}
					e = 8 * e + (c - '0');
					c = e;
					if (c >= 256) {
						MSG_invalid_escape_sequence ();
						c = 0;
					}
				}
			}
			if (c == EOF) {
				MSG_end_of_file_in_string ();
				word_type = INPUT_EOF;
				pending = EOF;
				return;
			}
			if (!ignore) *(p++) = (char) c;
		} while (c != '"' || escaped);
		*(--p) = 0;
#if 0
		c = getc (input);
		if (c == '\n') crt_line_no++;
		if (!terminator (c)) {
			MSG_terminator_character_expected ();
		}
		pending = c;
#endif
		word = word_buff;
		word_length = (int) (p - word);
		word_type = INPUT_STRING;
		return;
    }

    /* Check for words */
    if (alpha (c)) {
		p = word_buff;
		do {
			*(p++) = (char) c;
			c = getc (input);
			if (c == '\n') crt_line_no++;
		} while (alphanum (c));
		*p = 0;
		if (!terminator (c)) {
			MSG_terminator_character_expected ();
		}
		pending = c;
		word = word_buff;
		word_type = INPUT_WORD;
		return;
    }

    /* Check for bars */
    if (c == '|') {
		c = getc (input);
		if (c == '\n') crt_line_no++;
		if (!terminator (c)) {
			MSG_terminator_character_expected ();
		}
		pending = c;
		word = "|" ;
		word_type = INPUT_BAR;
		return;
    }

    /* Check for a single dash and arrow */
    if (c == '-') {
		c = getc (input);
		if (c == '\n') crt_line_no++;
		if (terminator (c)) {
			pending = c;
			word = "-" ;
			word_type = INPUT_BLANK;
			return;
		}
		if (func_input && c == '>') {
			pending = 0;
			word = "->";
			word_type = INPUT_ARROW;
			return;
		}
		negate = 1;
    }

    /* Step over any signs */
    while (c == '-' || c == '+') {
		if (c == '-') negate = 1 - negate;
		c = getc (input);
		if (c == '\n') crt_line_no++;
    }

    /* Check for numbers */
    if (c == '0') {
		base = 8;
		c = getc (input);
		if (c == '\n') crt_line_no++;
		if (terminator (c)) {
			pending = c;
			word = "0";
			word_type = INPUT_NUMBER;
			return;
		}
		if (c == 'x' || c == 'X') {
			base = 16;
			c = getc (input);
			if (c == '\n') crt_line_no++;
		}
    } else if (!dec_digit (c)) {
		MSG_illegal_character ((unsigned char)c);
		pending = 0;
		read_word ();
		return;
    }

    /* Set up buffer */
    p = word_buff + BUFFSIZE;
    *(--p) = 0;
    *(--p) = '0';
    *(--p) = 0;

    /* Read the number */
    do {
		unsigned n;
		if (dec_digit (c)) {
			n = (unsigned) (c - '0');
		} else if (c >= 'A' && c <= 'F') {
			n = 10 + (unsigned) (c - 'A');
		} else if (c >= 'a' && c <= 'f') {
			n = 10 + (unsigned) (c - 'a');
		} else {
			MSG_illegal_digit ((char) c);
			n = 0;
		}
		if (n >= base) {
			MSG_illegal_digit ((char) c);
			n = 0;
		}
		if (really_analyse) {
			p = word_buff + (BUFFSIZE - 2);
			do {
				if (*p == 0) {
					*(p - 1) = 0;
				} else {
					n += base * (unsigned) (*p - '0');
				}
				*p = (char) ('0' + (n & 7));
				n >>= 3;
				p--;
			} while (n || *p);
		}
		c = getc (input);
		if (c == '\n') crt_line_no++;
    } while (!terminator (c));

    /* Find the start of the number */
    if (really_analyse) {
		for (p = word_buff + (BUFFSIZE - 2) ; *p ; p--) /* empty */;
		if (negate) *(p--) = '-';
    }
    pending = c;
    word = p + 1;
    if (streq (word, "-0")) word = "0";
    word_type = INPUT_NUMBER;
    return;
}


/*
 *    HOW MANY WORDS TO THE NEXT CLOSE BRACKET?
 *
 *    This routine skips over the input until the first closing bracket
 *    unmatched by an open bracket is read.  The routine returns the
 *    number of word read at the highest bracket level.  (Not currently
 *    used.)
 */

long
skip_words(void)
{
    long n = 0;
    int level = 1;
    really_analyse = 0;
    while (level) {
		read_word ();
		switch (word_type) {
	    case INPUT_OPEN : level++ ; break;
	    case INPUT_CLOSE : level-- ; break;
	    case INPUT_EOF : {
			MSG_unexpected_end_of_file ();
			return (n);
	    }
		}
		if (level == 1) n++;
    }
    really_analyse = 1;
    return (n);
}


/*
 *    STORE THE CURRENT POSITION
 *
 *    The current position in the input file is stored in p.
 */

void
store_position(position *p)
{
    p->line = crt_line_no;
    p->posn = ftell (input);
    p->pending = pending;
    p->ahead = looked_ahead;
    return;
}


/*
 *    SET THE CURRENT POSITION
 *
 *    The position in the input file is set from p.
 */

void
set_position(position *p)
{
    crt_line_no = p->line;
    pending = p->pending;
    looked_ahead = p->ahead;
    if (fseek (input, p->posn, SEEK_SET)) {
		MSG_illegal_seek_command ();
    }
    return;
}
