#ifndef lint
static char *sccsid = "@(#)lex.c	1.2 (Steve Hill) 5/24/90";
#endif

/* lex.c
 *
 * Lexical analyser for a ray tracer input language.
 *
 * Tokens are white-space separated sequences of symbols.
 * White space include tabs, new-lines and ordinary spaces.
 */


#include <stdio.h>
#include <ctype.h>

#include "basetype.h"
#include "error.h"
#include "lex.h"


/* token, line_number
 *
 * Externally available variables holding the current token, the current
 * line number, and the type of the current token respectively.
 */

char		token[MAX_TOKEN_SIZE];
int		line_number;

/* lex_file, current_char, token_position
 *
 * Private variables holding the file pointer of the file being tokenised,
 * the current character, and the position in the current token where
 * characters are being placed.
 */

static FILE	*lex_file;
char		*lex_file_name;
static int	current_char,
		token_position;

/* include_level, lex_stack
 *
 * Depth of include nesting.
 * Stack of open files.
 */

int		include_level;

static lex_info_t	lex_stack[MAX_INCLUDE_LEVEL];

/* IsWhite
 *
 * Tests character against white space (ie. ignored) characters.
 */

static
bool_t
IsWhite(c)
int	c;
{
	return(c == ' ' || c == '\t' || c == '\n' ||
	       c == ',' || c == '('  || c == ')' ||
	       c == '=');
}


/* IsEOF
 *
 * Tests for EOF character.
 */

static
bool_t
IsEOF(c)
int	c;
{
	return(c == EOF);
}


/* IsTerminator
 *
 * Tests is a character is a token terminating character.
 */

bool_t
IsTerminator(c)
int	c;
{
	return(IsWhite(c) || IsEOF(c));
}


/* IsSpecial
 *
 * Special characters are tokens in their own right, so ther terminate
 * tokens, but are not white space.
 */

bool_t
IsSpecial(c)
int	c;
{
	return(c == '{' || c == '}');
}


/* GetChar
 *
 * Read next character into buffer.  If buffer held a new-line,
 * the advance line count.
 */

static
void
GetChar()
{
	if (current_char == '\n')
		line_number += 1;

	current_char = getc(lex_file);
}


/* AddToToken
 *
 * Add current character to the token.  If the token size is not
 * sufficient, then complain and exit.
 */

static
void
AddToToken()
{
	if (token_position >= MAX_TOKEN_SIZE)
	{
		fprintf(stderr, "Token too long at line %d\n", line_number);
		exit(1);
	}

	token[token_position] = current_char;
	token_position += 1;
}


/* NextToken
 *
 * Get the next token from the input.
 *
 * Skip white, then gather until white or end of file.
 */

static
void
NextToken()
{
	token_position = 0;

	while (IsWhite(current_char))
		GetChar();

	if (IsSpecial(current_char))
	{
		AddToToken();
		GetChar();
	}
	else
	{
		while (!(IsTerminator(current_char) || IsSpecial(current_char)))
		{
			AddToToken();
			GetChar();
		}
	}

	token[token_position] = '\0';
}


/* InitialiseLex
 *
 * Initialise lexical analyser.  Sets up file pointer and line number.
 * Fetches first character of file into the current_char buffer.
 */

void
InitialiseLex(name, file)
char	*name;
FILE	*file;
{
	lex_file      = file;
	lex_file_name = name;
	current_char  = getc(file);
	line_number   = 1;
	token[0]      = ' ';
}


/* GetToken
 *
 * Get the next non-comment token.
 */

void
GetToken()
{
	NextToken();

	if (TokenIs(START_COMMENT_TOKEN))
	{
		NextToken();
		while (TokenIsnt(STOP_COMMENT_TOKEN) && TokenIsnt(EOF_TOKEN))
			NextToken();
		NextToken();
	}
}


/* TokenIsIdentifier, TokenIsNumber
 *
 * Tests token to see what sort it is.  Fairly sloppy tests.
 */

bool_t
TokenIsIdentifier()
{
	return(isalpha(token[0]));
}


bool_t
TokenIsNumber()
{
	return(isdigit(token[0]) || token[0] == '.' || token[0] == '-');
}

void
StackFile()
{
	lex_info_t	*lex_info;

	if (include_level >= MAX_INCLUDE_LEVEL)
		ParseError("Includes too deeply nested");

	lex_info = &lex_stack[include_level];
	lex_info->name    = lex_file_name;
	lex_info->file    = lex_file;
	lex_info->current = current_char;
	lex_info->line    = line_number;
	include_level += 1;
}

void
PopFile()
{
	lex_info_t	*lex_info;

	if (include_level == 0)
		FatalError("PopFile: hit bottom of stack");

	include_level -= 1;
	lex_info = &lex_stack[include_level];
	lex_file_name = lex_info->name;
	lex_file      = lex_info->file;
	current_char  = lex_info->current;
	line_number   = lex_info->line;
	/* Should free name here */
}
