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

/*
 * Copyright (c) 1990-2005 by Wayne C. Gramlich.
 * All rights reserved.
 */

/* Implementation of file I/O data abstraction: */

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

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

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

#ifndef LIBC_EXPORTS_H
#include "libc_exports.h"
#endif

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

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

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

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

#ifndef UNIX_FCNTL_H
#include "unix_fcntl.h"
#endif

#ifndef UNIX_UNISTD_H
#include "unix_unistd.h"
#endif

/* LINTLIBRARY */

/*
 * file_argument_read(file, status, heap)
 *	This routine will read an argument from "file" and return it,
 *	where an argument is defined as a sequence of printing characters.
 *	The returned argument is allocated from "heap".
 *	If "status" is a non-null pointer, status information is returned.
 *	If no argument is encountered and "status" is
 *	FILE_ARGUMENT_NONE a fatal error is generated.
 */
Str
file_argument_read(
	File		file,
	File_argument	*status,
	Heap		heap)
{
	int		chr;
	static Strvec	strvec = (Strvec)0;

	chr = file_chr_non_white_peek(file);
	switch (chr) {
	    case '\n':
	    case EOF:
		if (status == FILE_ARGUMENT_NONE) {
			error_fatal("No argument encountered");
		} else {
			*status = File_argument_none;
			return (Str)0;
		}
	}

	if (strvec == (Strvec)0) {
		strvec = strvec_create(heap);
	} else {
		strvec_erase(strvec);
	}

	for (;;) {
		chr = file_chr_peek(file);
		if (isspace(chr) || (chr == EOF)) {
			break;
		}
		strvec_chr_append(strvec, chr);
		(void)file_chr_read(file);
	}

	if (status != FILE_ARGUMENT_NONE) {
		*status = File_argument_ok;
	}
	return strvec_str_get(strvec, heap);
}

/*
 * file_chr_actual_read(file)
 *	This routine will actually read the next character from "file".
 */
int
file_chr_actual_read(
	File		file)
{
	int		chr;
	int		remaining;

	if (file->remaining == 0) {
		remaining = read(file->fd, file->buffer, file->size);
		if (remaining <= 0) {
			assert(remaining == 0);
			return EOF;
		}
		file->pointer = file->buffer;
		file->remaining = remaining;
	}
	file->remaining--;
	chr = *file->pointer++;
	return chr;
}

/*
 * file_chr_read(file)
 *	This routine will return next character from "file".  FILE_NO_CHR
 *	is returned if there are no more characters.
 */
int
file_chr_read(
	File		file)
{
	int		chr;

	if (file->previous == FILE_NO_CHR) {
		chr = file_chr_actual_read(file);
	} else {
		chr = file->previous;
		file->previous = FILE_NO_CHR;
	}
	switch (chr) {
	    case '\t':
		file->line_column = (file->line_column | 7) + 1;
		break;
	    case '\n':
		file->line_number++;
		file->line_column = 0;
		break;
	    case EOF:
		break;
	    default:
		file->line_column++;
	}
	file->position++;
	return chr;
}

/*
 * file_chr_non_white_peek(file)
 *	This routine will return the next non-whitespace character from "file"
 *	without actually reading it.  FILE_NO_CHR is returned if there is no
 *	next character in "file".
 */
int
file_chr_non_white_peek(
	File		file)
{
	int		chr;

	for (;;) {
		chr = file_chr_peek(file);
		if ((chr == '\n') || !isspace(chr)) {
			return chr;
		}
		(void)file_chr_read(file);
	}
	
}

/*
 * file_chr_peek(file)
 *	This routine will return the next character from "file" without
 *	actually reading it.  FILE_NO_CHR is returned if there is no
 *	next character in "file".
 */
int
file_chr_peek(
	File		file)
{
	if (file->previous == FILE_NO_CHR) {
		file->previous = file_chr_actual_read(file);
	}
	return file->previous;
}

/*
 * file_chr_read_peek(file)
 *	This routine will perform a file_chr_read followed by a file_chr_peek.
 */
int
file_chr_read_peek(
	File		file)
{
	(void)file_chr_read(file);
	return file_chr_peek(file);
}

/*
 * file_close(file)
 *	This routine will close "file".
 */
void
file_close(
	File		file)
{
	assert(close(file->fd) == 0);
	heap_free(file->heap, file->buffer);
	heap_free(file->heap, (Pointer)file);
}

/*
 * file_integer_read(file, status)
 *	This routine will read an integer from "file" and return it.
 *	If "status" is a non-null pointer, status information is returned.
 *	If an erroneous integer is encountered and "status" is
 *	FILE_INTEGER_NONE a fatal error is generated.
 */
int
file_integer_read(
	File		file,
	File_integer	*status)
{
	int		chr;
	int		first;
	File_integer	mode;
	int		negative;
	int		number;
	int		radix;

	first = 1;
	mode = File_integer_decimal;
	negative = 0;
	number = 0;
	radix = 10;
	for (;;) {
		chr = file_chr_peek(file);
		switch (chr) {
		    case '0':
			if (first) {
				first = 0;
				chr = file_chr_read_peek(file);
				if ((chr == 'x') || (chr == 'X')) {
					mode = File_integer_hexadecimal;
					radix = 16;
				} else {
					mode = File_integer_octal;
					radix = 8;
					continue;
				}
			} else {
				number *= radix;
			}
			break;
		    case '1':
		    case '2':
		    case '3':
		    case '4':
		    case '5':
		    case '6':
		    case '7':
			number = number * radix + (chr - '0');
			break;
		    case '8':
		    case '9':
			if (radix >= 10) {
				number = number * radix + (chr - '0');
			} else {
				mode = File_integer_bad_digit;
				goto done;
			}
			break;
		    case 'a':
		    case 'b':
		    case 'c':
		    case 'd':
		    case 'e':
		    case 'f':
			if (radix == 16) {
				number = (number << 4) + 10 + (chr - 'a');
			} else {
				mode = File_integer_bad_digit;
				goto done;
			}
			break;
		    case 'A':
		    case 'B':
		    case 'C':
		    case 'D':
		    case 'E':
		    case 'F':
			if (radix == 16) {
				number = (number << 4) + 10 + (chr - 'A');
			} else {
				mode = File_integer_bad_digit;
				goto done;
			}
			break;
		    case ' ':
		    case '\t':
			if (first) {
				(void)file_chr_read(file);
			} else {
				goto done;
			}
			continue;
		    case '-':
			if (first) {
				negative = 1;
			} else {
				goto done;
			}
			break;
		    default:
			goto done;
		}
		first = 0;
		(void)file_chr_read(file);
	}
    done:
	if (first) {
		mode = File_integer_none;
	}
	if (status == FILE_INTEGER_NONE) {
		switch (mode) {
		    case File_integer_decimal:
		    case File_integer_hexadecimal:
		    case File_integer_octal:
			return (negative ? -number : number);
		    case File_integer_bad_digit:
			error_fatal("Bad number digit encountered");
			/* NOTREACHED */
		    case File_integer_none:
			error_fatal("No number encountered");
			/* NOTREACHED */
		}
	} else {
		*status = mode;
	}
	return (negative ? -number : number);
}

/*
 * file_xopen(file_name, abort)
 *	This routine will open the file "file_name" and return an associated
 *	file object.  If "file_name" does not exist and "abort" is 1,
 *	a fatal error message is generated; otherwise FILE_NONE is returned.
 */
File
file_xopen(
	Str		file_name,
	int		abort)
{
	Heap		heap;

	heap = heap_standard_create();
	return file_open(file_name, abort, heap);
}

/*
 * file_open(file_name, abort, heap)
 *	This routine will open the file "file_name" and return an associated
 *	file object.  If "file_name" does not exist and "abort" is 1,
 *	a fatal error message is generated; otherwise FILE_NONE is returned.
 */
File
file_open(
	Str		file_name,
	int		abort,
	Heap		heap)
{
	int		fd;
	File		file;

	fd = open(file_name, O_RDONLY, 0);
	if (fd < 0) {
		if (abort) {
			error_fatal("Could not open %s\n", file_name);
		} else {
			return FILE_NONE;
		}
	}

	file = heap_allocate(heap, File);
	file->buffer = (Str)heap_alloc(heap, FILE_BUFFER_SIZE);
	file->fd = fd;
	file->file_name = strdupl(file_name, heap);
	file->heap = heap;
	file->line_number = 1;
	file->line_column = 0;
	file->pointer = file->buffer;
	file->position = 0;
	file->previous = FILE_NO_CHR;
	file->remaining = 0;
	file->size = FILE_BUFFER_SIZE;
	return file;
}

/*
 * file_name_get(file)
 *	This routine will return the file name of "file".
 */
Str
file_name_get(
	File		file)
{
	return file->file_name;
}

/*
 * file_seek(file, position)
 *	This routine will seek "file" to "position". 0 is returned if
 *	there are no errors; otherwise, 1 is returned.
 */
int
file_seek(
	File		file,
	long		position)
{
	file->position = position;
	file->pointer = file->buffer;
	file->remaining = 0;
	/* Should use SEEK_SET instead of 0! */
	return (lseek(file->fd, (off_t)position, 0) >= 0) ? 0 : 1;
}

/*
 * file_standard_input_open(heap)
 *	This routine will return an input file connected to standard input
 *	allocated from "heap".
 */
File
file_standard_input_open(
	Heap		heap)
{
	File		file;

	file = heap_allocate(heap, File);
	file->buffer = (Str)heap_alloc(heap, FILE_BUFFER_SIZE);
	file->fd = 0;
	file->file_name = "/dev/tty";
	file->heap = heap;
	file->line_number = 1;
	file->line_column = 0;
	file->pointer = file->buffer;
	file->position = 0;
	file->previous = FILE_NO_CHR;
	file->remaining = 0;
	file->size = FILE_BUFFER_SIZE;
	return file;
}

/*
 * file_string_read(file, status, heap)
 *	This routine will read and parse a quoted string from "file" and
 *	returns the result as a Str allocated from "heap".
 *	If "status" is a non-null pointer, status information is returned.
 *	If an erroneous string is encountered and "status" is
 *	FILE_STRING_NONE, a fatal error is generated.
 */
Str
file_string_read(
	File		file,
	File_string	*status,
	Heap		heap)
{
	int		chr;
	File_string	mode;
	int		number;
	char		quote;
	static Strvec	string = (Strvec)0;

	if (string == (Strvec)0) {
		string = strvec_create(heap);
	}
	mode = File_string_none;
	string = strvec_create(heap);
	for (;;) {
		chr = file_chr_peek(file);
		switch (chr) {
		    case '\'':
			mode = File_string_single_quoted;
			goto cont;
		    case '"':
			mode = File_string_double_quoted;
			goto cont;
		    case ' ':
		    case '\t':
			(void)file_chr_read(file);
			break;
		    default:
			goto done;
		}
	}
    cont:
	quote = file_chr_read(file);
	for (;;) {
		chr = file_chr_peek(file);
		switch (chr) {
		    case '\'':
		    case '"':
			(void)file_chr_read(file);
			if (chr == quote) {
				goto done;
			} else {
				strvec_chr_append(string, chr);
			}
			break;
		    case EOF:
		    case '\n':
			mode = File_string_unterminated;
			goto done;
		    case '\t':
			mode = File_string_embedded_tab;
			strvec_chr_append(string, chr);
			(void)file_chr_read(file);
			break;
		    case '\\':
			chr = file_chr_read_peek(file);
			switch (chr) {
			    case 'n':
				chr = '\n';
				(void)file_chr_read(file);
				break;
			    case 't':
				chr = '\t';
				(void)file_chr_read(file);
				break;
			    case '\\':
			    case '\'':
			    case '"':
				(void)file_chr_read(file);
				break;
			    case '0':
			    case '1':
			    case '2':
			    case '3':
			    case '4':
			    case '5':
			    case '6':
			    case '7':
				number = chr - '0';
				chr = file_chr_read_peek(file);
				if (('0' <= chr) && (chr <= '7')) {
					number <<= 3;
					number += chr - '0';
					chr = file_chr_read_peek(file);
					if (('0' <= chr) && (chr <= '7')) {
						number <<= 3;
						number += chr - '0';
						(void)file_chr_read(file);
					}
				}
				chr = number;
				break;
			    default:
				mode = File_string_bad_backslash;
				(void)file_chr_read(file);
			}
			strvec_chr_append(string, chr);
			break;
		    default:
			if (isprint(chr)) {
				strvec_chr_append(string, chr);
				(void)file_chr_read(file);
			} else {
				mode = File_string_bad_character;
				goto done;
			}
		}
	}
    done:
	if (status == FILE_STRING_NONE) {
		switch (mode) {
		    case File_string_double_quoted:
		    case File_string_single_quoted:
			goto ret;
		    case File_string_bad_backslash:
			error_fatal("Unrecognized backslash character");
			break;
		    case File_string_bad_character:
			error_fatal("Non-printing character in string");
			break;
		    case File_string_embedded_tab:
			error_fatal(
			    "You must use '\\t' instead of embedded tabs");
			break;
		    case File_string_none:
			error_fatal("No string encountered");
			break;
		    case File_string_unterminated:
			error_fatal("Unterminated string");
			break;
		}
		/* NOTREACHED */
	} else {
		*status = mode;
	}
    ret:
	return strvec_str_get(string, heap);
}

/*
 * file_symbol_read(file, status, heap)
 *	This routine will read and parse a symbol string from "file" and
 *	return the result as a Str allocated from "heap".  If "status"
 *	is a non-null pointer, status information is returned.  If an
 *	erroneous symbol is encountered and "status" is FILE_SYMBOL_NONE,
 *	a fatal error is generated.
 */
Str
file_symbol_read(
	File		file,
	File_symbol	*status,
	Heap		heap)
{
	int		chr;
	File_symbol	mode;
	static Strvec	symbol = (Strvec)0;

	if (symbol == (Strvec)0) {
		symbol = strvec_create(heap);
	} else {
		strvec_erase(symbol);
	}
	mode = File_symbol_ok;
	for (;;) {
		chr = file_chr_peek(file);
		if (isalpha(chr)) {
			break;
		} else if (isspace(chr) && (chr != '\n')) {
			chr = file_chr_read_peek(file);
		} else {
			mode = File_symbol_none;
			goto done;
		}
	}
	do {
		strvec_chr_append(symbol, chr);
		chr = file_chr_read_peek(file);
	} while (isalnum(chr) || (chr == '_'));
    done:
	if (status == FILE_SYMBOL_NONE) {
		switch (mode) {
		    case File_symbol_ok:
			goto ret;
		    case File_symbol_none:
			error_fatal("No symbol encountered");
		}
	} else {
		*status = mode;
	}
     ret:
	return strvec_str_get(symbol, heap);
}

/*
 * file_tell(file)
 *	This will return the current position in "file".
 */
long
file_tell(
	File		file)
{
	return file->position;
}

