/* %Z%%M% %I% %E% */

/*
 * Copyright (c) 1990, 1991, 1992, 1993, 1995 by Wayne C. Gramlich.
 * All rights reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * for any purpose is hereby granted without fee provided that the above
 * copyright notice and this permission are retained.  The author makes
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

/* This file contains code used for parsing: */

#ifndef ERROR_EXPORTS_H
#include "error_exports.h"
#endif

#ifndef FILE_DEFS_H
#include "file_defs.h"
#endif

#ifndef FLAGS_DEFS_H
#include "flags_defs.h"
#endif

#ifndef HEAP_EXPORTS_H
#include "heap_exports.h"
#endif

#ifndef INT_EXPORTS_H
#include "int_exports.h"
#endif

#ifndef KEYWORD_DEFS_H
#include "keyword_defs.h"
#endif

#ifndef LINT_H
#include "lint.h"
#endif

#ifndef MSG_DEFS_H
#include "msg_defs.h"
#endif

#ifndef OUT_EXPORTS_H
#include "out_exports.h"
#endif

#ifndef PORT_H
#include "port.h"
#endif

#ifndef PARSER_DEFS_H
#include "parser_defs.h"
#endif

#ifndef STR_EXPORTS_H
#include "str_exports.h"
#endif

#ifndef STRVEC_EXPORTS_H
#include "strvec_exports.h"
#endif

#ifndef TABLE_EXPORTS_H
#include "table_exports.h"
#endif

#ifndef TOKEN_DEFS_H
#include "token_defs.h"
#endif

#ifndef UNIX_ASSERT_H
#include "unix_assert.h"
#endif

#ifndef UNIX_CTYPE_H
#include "unix_ctype.h"
#endif

#ifndef VECTOR_DEFS_H
#include "vector_defs.h"
#endif

LOCAL int	parser_column_read(Parser);
LOCAL Str	parser_operators();
LOCAL Token	parser_token_create(Heap);

/*
 * parser_column_read(parser)
 * 	This routine will skip over spaces and tabs.  The column position
 *	is returned.
 */
int
parser_column_read(
	Parser		parser)
{
	int		column;
	File		file;
	Msg		msg;

	if (parser->column >= 0) {
		column = parser->column;
		parser->column = -1;
		return column;
	}
	msg = parser->msg;
	file = parser->file;
	column = 0;
	for (;;) {
		switch (file_chr_peek(file)) {
		    case '\t':
			column = (column | 7) + 1;
			(void)file_chr_read(file);
			break;
		    case ' ':
			column++;
			(void)file_chr_read(file);
			break;
		    case '\n':
			column = 0;
			(void)file_chr_read(file);
			msg_line_append(msg, file->position);
			break;
		    case EOF:
			return 0;
		    default:
			return file->line_column;
		}
	}
	/* NOTREACHED */
}

/*
 * parser_comma_list_parse(parser, routine, message)
 *	This routine will parse and return comma separated list of
 *	objects using "routine(parser)" to parse each object.  If
 *	"message" is not (Str)0 and parsed list is empty, an
 *	an error message is generated.
 */
Vec(Pointer)
parser_comma_list_parse(
	Parser		parser,
	Parser_routine	routine,
	Str		message)
{
	Pointer		item;
	Vec(Pointer)	list;
	Token		token;

	list = vec_create(Pointer, parser->heap);
	for (;;) {
		token = parser_token_peek(parser);
		switch (token->type) {
		    case Token_type_eol:
		    case Token_type_comma:
			msg_out(parser->msg, parser->file->position,
				     "Bad list");
			return list;
		}
		item = (*routine)(parser);
		if (item == (Pointer)0) {
			msg_out(parser->msg, token->position, "Bad list");
			break;
		}
		vec_append(Pointer, list, item);
		token = parser_token_peek(parser);
		if (token->type == Token_type_comma) {
			(void)parser_token_read(parser);
		} else {
			break;
		}
	}
	if (vec_empty(Pointer, list)) {
		msg_out(parser->msg, parser->file->position, message);
	}
	return list;
}

/*
 * parser_eol_read(parser)
 *	This routine will read up to and including an end-of-line.  If there
 *	is any end of line comment, it is returned as a Str; otherwise, (Str)0
 *	is returned.
 */
Str
parser_eol_read(
	Parser		parser)
{
	int		chr;
	File		file;
	Msg		msg;
	static Strvec	strvec = (Strvec)0;

	msg = parser->msg;
	parser->peek_token = TOKEN_NONE;
	file = parser->file;
	for (;;) {
		chr = file_chr_read(file);
		switch (chr) {
		    case '\n':
			msg_line_append(msg, file->position);
			return (Str)0;
		    case '\t':
		    case ' ':
			continue;
		    case '#':
		    case ';':
			break;
		    default:
			msg_out(parser->msg, (parser->previous == TOKEN_NONE) ?
					parser->file->position :
					parser->previous->position,
				  "Garbage at end-of-line");
			do {
				chr = file_chr_read(file);
			} while ((chr != '\n') && (chr != EOF));
			if (chr == '\n') {
				msg_line_append(msg, file->position);
			}
			return (Str)0;
		}
		break;
	}
	if (strvec == (Strvec)0) {
		strvec = strvec_create(parser->heap);
	} else {
		strvec_erase(strvec);
	}
	strvec_chr_append(strvec, chr);
	for (;;) {
		chr = file_chr_read(file);
		switch (chr) {
		    case '\n':
			msg_line_append(msg, file->position);
			/* FALLTHROUGH */
		    case EOF:
			return strvec_str_get(strvec, parser->heap);
		}
		strvec_chr_append(strvec, chr);
	}
	/* NOTREACHED */
}

/*
 * parser_file_open(file_name, msg, abort, type_tables, flags, heap)
 *	This routine will open "file_name" for reading of lexical tokens.
 *	If "file_name" could not be opened and "abort" is 1 a fatal error
 *	is generated; otherwise PARSER_NONE is returned;
 */
Parser
parser_file_open(
	Str		file_name,
	Msg		msg,
	int		abort,
	Type_tables	type_tables,
	Flags		flags,
	Heap		heap)
{
	File		file;
	Parser		parser;

	file = file_open(file_name, abort, heap);
	if (file == FILE_NONE) {
		return PARSER_NONE;
	}
	parser = heap_allocate(heap, Parser);
	parser->buffer = vec_create(char, heap);
	parser->column = -1;
	parser->file = file;
	parser->flags = flags;
	parser->gen_file = (Stdio)0;
	parser->heap = heap;
	parser->loop_number = 0;
	parser->keywords = keyword_table_get(heap);
	parser->msg = msg;
	parser->level = 0;
	parser->levels = vec_create(Node, heap);
	parser->operators = parser_operators();
	parser->out_file = (Stdio)0;
	parser->parameter_table = table_create(Str, Type_ref,
					       10, strequal, strhash,
					       (Type_ref)0, heap);
	parser->peek_token = TOKEN_NONE;
	parser->previous = TOKEN_NONE;
	parser->type_tables = type_tables;
	return parser;
}

/*
 * parser_indented_list_parse(parser, routine, message)
 *	This routine will start a new lexical level and repeatedly call
 *	"routine(parser)" to get successive nodes until "end-of-file" is
 *	encountered.  Any indentation errors are detected and skipped over.
 *	A list node of type "type" contining the node list is returned.
 *	If "message" is not equal to (Str)0 and the resultant node list
 *	is empty, "message" is emited as an error message.
 */
Vec(Pointer)
parser_indented_list_parse(
	Parser		parser,
	Parser_routine	routine,
	Str		message)
{
	Heap		heap;
	Pointer		item;
	Vec(Pointer)	list;

	heap = parser->heap;
	list = vec_create(Pointer, heap);
	PARSER_LOOP(parser) {
		item = (*routine)(parser);
		if (item != (Pointer)0) {
			vec_append(Pointer, list, item);
		}
	}
	if (vec_empty(Pointer, list)) {
		msg_out(parser->msg, parser->file->position, message);
	}
	return list;
}

/*
 * parser_keyword_parse(parser, eol_ok, none_ok)
 *	This routine will return the next keyword read from "parser".  If
 *	eol_ok is 1 and the next token is an end-of-line, Key_eol is returned;
 *	otherwise and error is generted.  If "none_ok" is 1 and the next token
 *	is neither a keyword nor an end-of-line, Key_none is returned;
 *	otherwise, an error is generated.
 */
Keyword
parser_keyword_parse(
	Parser		parser,
	int		eol_ok,
	int		none_ok)
{
	Token		token;
	Keyword		keyword;
	
	token = parser_token_peek(parser);
	switch (token->type) {
	    case Token_type_eol:
		if (eol_ok) {
			return Key_eol;
		} else {
			goto error;
		}
	    case Token_type_symbol:
		keyword =  table_lookup(Str, Keyword,
				 	parser->keywords, token->value.symbol);
		if (keyword != Key_none) {
			(void)parser_token_read(parser);
			return keyword;
		} else if (none_ok) {
			return Key_none;
		} else {
			goto error;
		}
	    default:
		if (!none_ok) {
			goto error;
		}
		return Key_none;
	}
    error:
	msg_out(parser->msg, parser->file->position,
		     "Keyword expected but not found");
	return Key_none;
}

/*
 * parser_loop_init(parser)
 *	This routine will start a new level of indentation on "parser".  The
 *	next character in "parser" should be at the beginning of a line.
 */
void
parser_loop_init(
	Parser		parser)
{
	int		column;
	int		level;

	level = parser->level;
	column = parser_column_read(parser);
	parser->level = column;
	if (column <= level) {
		msg_out(parser->msg, parser->file->position,
			     "Missing indentation level");
		parser->level++;
	}
	vec_append(int, parser->levels, level);
	parser->column = column;
}

/*
 * parser_loop_next(parser)
 *	This routine will return 1 if the next statement is at the same
 *	indentation level.  0 is returned if the next statement is at
 *	lesser indentation.  If a greater indentation level is encountered,
 *	an error message is generated and it is skipped over.  The next
 *	character in "parser" should be at the beginning of a line.
 */
int
parser_loop_next(
	Parser		parser)
{
	int		chr;
	int		column;
	int		error_printed;
	File		file;
	int		level;
	Msg		msg;
	int		position;

	error_printed = 0;
	file = parser->file;
	level = parser->level;
	msg = parser->msg;
	for (;;) {
		column = parser_column_read(parser);
		if (column == level) {
			if ((level == 0) && (file_chr_peek(file) == EOF)) {
				if (parser->gen_file != (Stdio)0) {
					(void)fclose(parser->gen_file);
					parser->gen_file = (Stdio)0;
					position = file->position;
					file = file_open(
						parser->flags->gen_file->full,
						1, parser->heap);
					file->position = position;
					parser->file = file;
					parser->msg->position_split = position;
					continue;
				}
				return 0;
			} else {
				return 1;
			}
		}
		if (column < level) {
			parser->column = column;
			parser->level = vec_pop(int, parser->levels);
			return 0;
		}
		if (!error_printed) {
			error_printed = 1;
			msg_out(parser->msg, (parser->previous == TOKEN_NONE) ?
						parser->file->position :
						parser->previous->position,
						"Extraneous indentation level");
		}
		do {
			chr = file_chr_read(file);
		} while ((chr != '\n') && (chr != EOF));
		if (chr == '\n') {
			msg_line_append(msg, file->position);
		}
	}
	/* NOTREACHED */
}

/*
 * parser_operators()
 *	This routine will return a vector flags for each lexical type.
 */
Str
parser_operators(PORT_NO_ARGS)
{
	static char	operators[Token_type_size];

	int		index;

	operators[(int)Token_type_add] = PARSER_PREC_ADD;
	operators[(int)Token_type_add_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_add_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_and] = PARSER_PREC_AND;
	operators[(int)Token_type_and_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_and_if] = PARSER_PREC_AND_IF;
	operators[(int)Token_type_and_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_at] = PARSER_PREC_AT;
	operators[(int)Token_type_at_parenthesis] = PARSER_PREC_DOT;
	operators[(int)Token_type_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_colon] = PARSER_PREC_COLON;
	operators[(int)Token_type_comma] = PARSER_PREC_COMMA;
	operators[(int)Token_type_decrement] = PARSER_PREC_UNARY;
	operators[(int)Token_type_define] = PARSER_PREC_DEFINE;
	operators[(int)Token_type_define_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_define_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_dot] = PARSER_PREC_DOT;
	operators[(int)Token_type_equal] = PARSER_PREC_EQUAL;
	operators[(int)Token_type_divide] = PARSER_PREC_MULTIPLY;
	operators[(int)Token_type_divide_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_divide_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_eol] = PARSER_PREC_END;
	operators[(int)Token_type_greater_than] = PARSER_PREC_COMPARE;
	operators[(int)Token_type_greater_than_or_equal] = PARSER_PREC_COMPARE;
	operators[(int)Token_type_identical] = PARSER_PREC_IDENTICAL;
	operators[(int)Token_type_increment] = PARSER_PREC_UNARY;
	operators[(int)Token_type_integer] = PARSER_PREC_LEAF;
	operators[(int)Token_type_if] = PARSER_PREC_IF;
	operators[(int)Token_type_left_bracket] = PARSER_PREC_CALL;
	operators[(int)Token_type_left_paren] = PARSER_PREC_CALL;
	operators[(int)Token_type_left_shift] = PARSER_PREC_SHIFT;
	operators[(int)Token_type_left_shift_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_left_shift_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_less_than] = PARSER_PREC_COMPARE;
	operators[(int)Token_type_less_than_or_equal] = PARSER_PREC_COMPARE;
	operators[(int)Token_type_not] = PARSER_PREC_UNARY;
	operators[(int)Token_type_not_equal] = PARSER_PREC_EQUAL;
	operators[(int)Token_type_not_identical] = PARSER_PREC_EQUAL;
	operators[(int)Token_type_multiply] = PARSER_PREC_MULTIPLY;
	operators[(int)Token_type_multiply_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_multiply_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_or] = PARSER_PREC_OR;
	operators[(int)Token_type_or_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_or_if] = PARSER_PREC_OR_IF;
	operators[(int)Token_type_or_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_power] = PARSER_PREC_POWER;
	operators[(int)Token_type_power_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_power_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_remainder] = PARSER_PREC_MULTIPLY;
	operators[(int)Token_type_remainder_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_remainder_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_right_bracket] = PARSER_PREC_END;
	operators[(int)Token_type_right_paren] = PARSER_PREC_END;
	operators[(int)Token_type_right_shift] = PARSER_PREC_SHIFT;
	operators[(int)Token_type_right_shift_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_right_shift_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_string] = PARSER_PREC_LEAF;
	operators[(int)Token_type_subtract] = PARSER_PREC_ADD;
	operators[(int)Token_type_subtract_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_subtract_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_symbol] = PARSER_PREC_LEAF;
	operators[(int)Token_type_text] = PARSER_PREC_LEAF;
	operators[(int)Token_type_twiddle] = PARSER_PREC_DOT;
	operators[(int)Token_type_xor] = PARSER_PREC_XOR;
	operators[(int)Token_type_xor_assign] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_xor_result] = PARSER_PREC_ASSIGN;
	operators[(int)Token_type_zilch] = PARSER_PREC_LEAF;

	for (index = 0; index < (int)Token_type_size; index++) {
		if (operators[index] == 0) {
			out(stderr, "Unitinialized token %d\n", index);
		}
	}

	return operators;
}

/*
 * parser_string_parse(parser, token)
 *	This routine will
 */
static void
parser_string_parse(
	Parser		parser,
	Token		token)
{
	Vec(char)	buffer;
	int		chr;
	File		file;
	int		index;
	int		quote_chr;
	int		size;
	Str		string;

	file = parser->file;
	quote_chr = file_chr_read(file);
	token->type = (quote_chr == '"') ? Token_type_string : Token_type_text;
	token->value.string = "";
	buffer = parser->buffer;
	vec_trim(char, buffer, 0);
	for (;;) {
		chr = file_chr_peek(file);
		switch (chr) {
		    case EOF:
		    case '\n':
			msg_out(parser->msg, token->position,
				"Unterminated string");
			return;
		    case '\t':
			msg_out(parser->msg, token->position,
				"Tab character found in string,"
				" use `\\t\\' instead");
			return;
		    case '"':
		    case '\'':
			if (chr == quote_chr) {
				goto done;
			}
			(void)file_chr_read(file);
			vec_append(char, buffer, chr);
			break;
		    case '\\':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '\\') {
				(void)file_chr_read(file);
				vec_append(char, buffer, '\\');
				continue;
			}
			for (;;) {
				chr = file_chr_peek(file);
				if (isdigit(chr)) {
					int	digit;
					int	number;
					int	radix;

					radix = 10;
					if (chr == '0') {
						radix = 8;
						(void)file_chr_read(file);
						chr = file_chr_peek(file);
						if ((chr == 'x') ||
						    (chr == 'X')) {
							radix = 16;
							(void)file_chr_read(
									file);
							chr = file_chr_peek(
									file);
						}
					}
					number = 0;
					for (;;) {
						if (('0' <= chr) &&
						    (chr <= '9')) {
							digit = chr - '0';
						} else if (('a' <= chr) &&
							   (chr <= 'f')) {
							digit = 10 + chr - 'a';
						} else if (('A' <= chr) &&
							   (chr <= 'F')) {
							digit = 16 + chr - 'A';
						} else {
							break;
						}
						number = number * radix + digit;
						(void)file_chr_read(file);
						chr = file_chr_peek(file);
					}
					vec_append(char, buffer, number);
				} else if (isalpha(chr)) {
					(void)file_chr_read(file);
					switch (chr) {
					    case 't':
						chr = '\t';
						break;
					    case 'n':
						chr = '\n';
						break;
					    case 'q':
						chr = quote_chr;
						break;
					    default:
						msg_out(parser->msg,
							token->position,
							"Bad escape character");
						return;
					}
					vec_append(char, buffer, chr);
				} else {
					/* Illegal character in escape: */
					msg_out(parser->msg,
					  token->position,
					  "Illegal character in string escape");
					return;
				}

				/* See whether to get another character: */
				chr = file_chr_peek(file);
				if (chr == ',') {
					(void)file_chr_read(file);
				} else if (chr == '\\') {
					(void)file_chr_read(file);
					break;
				} else {
					msg_out(parser->msg, token->position,
						"Bad escape sequence");
					return;
				}
			}
			break;
		    default:
			if (isprint(chr)) {
				chr = file_chr_read(file);
				vec_append(char, buffer, chr);
			} else {
				msg_out(parser->msg, token->position,
					"Non-printing character in string");
				return;
			}
		}
	}

    done:
	(void)file_chr_read(file);

	/* Create the final string: */
	size = vec_size(char, buffer);
	string = (Str)heap_alloc(parser->heap, size + 1);
	for (index = 0; index < size; index++) {
		string[index] = (char)vec_fetch(int, buffer, index);
	}
	string[size] = '\0';
	token->value.string = string;
}

/*
 * parser_symbol_parse(parser)
 *	This routine will read a symbol from "parser" or emit an error.
 */
Token
parser_symbol_parse(
	Parser		parser)
{
	Token		token;

	token = parser_token_peek(parser);
	if (token->type == Token_type_symbol) {
		(void)parser_token_read(parser);
		return token;
	} else {
		msg_out(parser->msg, parser->file->position,
			"Could not find identifier");
		token->type = Token_type_symbol;
		token->value.symbol = "";
		return token;
	}
}

/*
 * parser_token_create(heap)
 *	This routine will create and return a new token.
 */
Token
parser_token_create(
	Heap		heap)
{
	Token		token;

	token = heap_allocate(heap, Token);
	token->type = Token_type_integer;
	token->value.integer = 0;
	return token;
}

/*
 * parser_token_peek(parser)
 *	This routine will return the next lexical token from "parser" without
 *	actually reading it.
 */
Token
parser_token_peek(
	Parser		parser)
{
	if (parser->peek_token == TOKEN_NONE) {
		parser->peek_token = parser_token_read(parser);
	}
	return parser->peek_token;
}

/*
 * parser_token_read(parser)
 *	This routine will read and return the next lexical token from "parser".
 */
Token
parser_token_read(
	Parser		parser)
{
	Token_type	assign;
	int		chr;
	int		column;
	File		file;
	File_integer	file_integer;
	int		is_result;
	Msg		msg;
	Token		previous;
	Token_type	result;
	Token		token;

	if (parser->peek_token != TOKEN_NONE) {
		token = parser->peek_token;
		parser->peek_token = TOKEN_NONE;
		parser->previous = token;
		return token;
	}
	previous = parser->previous;
	token = parser_token_create(parser->heap);
	file = parser->file;
	msg = parser->msg;
	for (;;) {
		chr = file_chr_peek(file);
		token->position = file->position;
		switch (chr) {
		    case '\n':
			/* Only parser_eol_read() can advance over '\n' */
			if ((previous != TOKEN_NONE) &&
			    ((parser->operators[(int)previous->type] &
						PARSER_BINARY) != 0)) {
				(void)file_chr_read(file);
				msg_line_append(msg, file->position);
				column = parser_column_read(parser);
				if (column > parser->level) {
					continue;
				} else {
					parser->column = column;
					msg_out(parser->msg, token->position,
						  "Bad continuation line");
					(void)parser_eol_read(parser);
				}
			}
			token->type = Token_type_eol;
			return token;
		    case EOF:
		    case ';':
		    case '#':
			token->type = Token_type_eol;
			return token;
		    case '\t':
		    case ' ':
			(void)file_chr_read(file);
			break;
		    case '!':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '=') {
				token->type = Token_type_not_equal;
				(void)file_chr_read(file);
				chr = file_chr_peek(file);
				if (chr == '=') {
					token->type = Token_type_not_identical;
					(void)file_chr_read(file);
				}
			} else {
				token->type = Token_type_not;
			}
			return token;
		    case '%':
			token->type = Token_type_remainder;
			(void)file_chr_read(file);
			return token;
		    case '&':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '&') {
				token->type = Token_type_and_if;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_and;
			}
			return token;
		    case '(':
			token->type = Token_type_left_paren;
			(void)file_chr_read(file);
			return token;
		    case ')':
			token->type = Token_type_right_paren;
			(void)file_chr_read(file);
			return token;
		    case '*':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '*') {
				token->type = Token_type_power;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_multiply;
			}
			return token;
		    case '+':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '+') {
				token->type = Token_type_increment;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_add;
			}
			return token;
		    case ',':
			token->type = Token_type_comma;
			(void)file_chr_read(file);
			return token;
		    case '-':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '-') {
				token->type = Token_type_decrement;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_subtract;
			}
			return token;
		    case '.':
			token->type = Token_type_dot;
			(void)file_chr_read(file);
			return token;
		    case '/':
			token->type = Token_type_divide;
			(void)file_chr_read(file);
			return token;
		    case ':':
			token->type = Token_type_colon;
			chr = file_chr_read_peek(file);
			if (chr == ':') {
				is_result = 1;
				chr = file_chr_read_peek(file);
			} else {
				is_result = 0;
			}
			switch (chr) {
			    case '=':
				(void)file_chr_read(file);
				token->type = (is_result ? Token_type_result :
							   Token_type_assign);
				return token;
			    case '%':
				assign = Token_type_remainder_assign;
				result = Token_type_remainder_result;
				break;
			    case '&':
				assign = Token_type_and_assign;
				result = Token_type_and_result;
				break;
			    case '*':
				chr = file_chr_read_peek(file);
				if (chr == '*') {
					(void)file_chr_read(file);
					assign = Token_type_power_assign;
					result = Token_type_power_result;
				} else {
					assign = Token_type_multiply_assign;
					result = Token_type_multiply_result;
				}
				break;
			    case '+':
				assign = Token_type_add_assign;
				result = Token_type_add_result;
				break;
			    case '-':
				assign = Token_type_subtract_assign;
				result = Token_type_subtract_result;
				break;
			    case '/':
				assign = Token_type_divide_assign;
				result = Token_type_divide_result;
				break;
			    case '<':
				chr = file_chr_read_peek(file);
				if (chr == '<') {
					assign = Token_type_left_shift_assign;
					result = Token_type_left_shift_result;
					break;
				}
				token->type = Token_type_left_shift_assign;
				msg_out(parser->msg, token->position,
					     "Bad operator");
				return token;
			    case '>':
				chr = file_chr_read_peek(file);
				if (chr == '>') {
					assign = Token_type_right_shift_assign;
					result = Token_type_right_shift_result;
					break;
				}
				token->type = Token_type_right_shift_assign;
				msg_out(parser->msg, token->position,
					     "Bad operator");
				return token;
			    case '@':
				assign = Token_type_define_assign;
				result = Token_type_define_result;
				break;
			    case '^':
				assign = Token_type_xor_assign;
				result = Token_type_xor_result;
				break;
			    case '|':
				assign = Token_type_or_assign;
				result = Token_type_or_result;
				break;
			    default:
				token->type = is_result ? Token_type_define :
							  Token_type_colon;
				return token;
			}
			token->type = (is_result ? result : assign);
			chr = file_chr_read_peek(file);
			if (chr == '=') {
				(void)file_chr_read(file);
			} else {
				msg_out(parser->msg, token->position,
					  "Bad assignment operator");
			}
			return token;
		    case '<':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '=') {
				token->type = Token_type_less_than_or_equal;
				(void)file_chr_read(file);
			} else if (chr == '<') {
				token->type = Token_type_left_shift;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_less_than;
			}
			return token;
		    case '=':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '=') {
				token->type = Token_type_identical;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_equal;
			}
			return token;
		    case '>':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '=') {
				token->type = Token_type_greater_than_or_equal;
				(void)file_chr_read(file);
			} else if (chr == '>') {
				token->type = Token_type_right_shift;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_greater_than;
			}
			return token;
		    case '?':
			(void)file_chr_read(file);
			if (file_chr_peek(file) == '?') {
				(void)file_chr_read(file);
				token->type = Token_type_zilch;
			} else {
				token->type = Token_type_if;
			}
			return token;
		    case '@':
			(void)file_chr_read(file);
			if (file_chr_peek(file) == '(') {
				(void)file_chr_read(file);
				token->type = Token_type_at_parenthesis;
			} else {
				token->type = Token_type_at;
			}
			return token;
		    case '[':
			token->type = Token_type_left_bracket;
			(void)file_chr_read(file);
			return token;
		    case ']':
			token->type = Token_type_right_bracket;
			(void)file_chr_read(file);
			return token;
		    case '^':
			token->type = Token_type_xor;
			(void)file_chr_read(file);
			return token;
		    case '|':
			(void)file_chr_read(file);
			chr = file_chr_peek(file);
			if (chr == '|') {
				token->type = Token_type_or_if;
				(void)file_chr_read(file);
			} else {
				token->type = Token_type_or;
			}
			return token;
		    case '\'':
		    case '"':
			parser_string_parse(parser, token);
			return token;
		    case '0':
		    case '1':
		    case '2':
		    case '3':
		    case '4':
		    case '5':
		    case '6':
		    case '7':
		    case '8':
		    case '9':
			token->type = Token_type_integer;
			token->value.integer =
				file_integer_read(file, &file_integer);
			switch (file_integer) {
			    case File_integer_decimal:
			    case File_integer_hexadecimal:
			    case File_integer_octal:
				break;
			    case File_integer_bad_digit:
				msg_out(parser->msg, token->position,
					  "Bad digit in integer");
				break;
			    default:
				msg_out(parser->msg, token->position,
					     "Bad integer");
			}
			return token;
		    case '~':
			token->type = Token_type_twiddle;
			(void)file_chr_read(file);
			return token;
		    default:
			if (isalpha(chr)) {
				token->type = Token_type_symbol;
				token->value.symbol = file_symbol_read(file,
						FILE_SYMBOL_NONE, parser->heap);
				return token;
			} else {
				msg_out(parser->msg, token->position,
					  "Illegal character");
				(void)file_chr_read(file);
			}
		}
	}
	/* NOTREACHED */
}

/*
 * parser_untranslated_string_read(parser)
 *	This routine will parse a string enclosed in double quotes from
 *	"parser" and return it.  If no such string is encountered, an
 *	error message is output and (Str)0 is returned.
 */
Str
parser_untranslated_string_read(
	Parser		parser)
{
	Token		token;

	token = parser_token_peek(parser);
	switch (token->type) {
	    case Token_type_string:
		(void)parser_token_read(parser);
		return token->value.string;
	    case Token_type_text:
		msg_out(parser->msg, token->position,
			"Only untranslated strings (in double quotes)"
			" are permitted");
		break;
	    default:
		msg_out(parser->msg, token->position,
			"Missing untranslated string");
		break;
	}
	return (Str)0;
}

