/*
 * Copyright (c) 1999 The University of Utah and
 * the Computer Systems Laboratory at the University of Utah (CSL).
 *
 * This file is part of Flick, the Flexible IDL Compiler Kit.
 *
 * Flick is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Flick is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Flick; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place #330, Boston, MA 02111, USA.
 */

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

#include <mom/c/scml.hh>

scml_stream::scml_stream()
{
	this->flags = SSF_INPUT;
	this->include_directory_list = 0;
	this->desc = 0;
	this->data = 0;
	this->len = 0;
}

scml_stream::~scml_stream()
{
	if( this->data )
		free(this->data);
}

char *scml_stream::tag_ref()
{
	return( ptr_to_tag_ref("struct scml_stream *", this) );
}

struct scml_stream *scml_stream::ptr(char *ref)
{
	return( (struct scml_stream *)
		tag_ref_to_ptr("struct scml_stream *", ref) );
}

void scml_stream::set_flags(unsigned int the_flags)
{
	this->flags = the_flags;
}

unsigned int scml_stream::get_flags()
{
	return( this->flags );
}

void scml_stream::set_data(char *str)
{
	this->data = str;
	if( str )
		this->len = strlen(str);
	else
		this->len = 0;
}

char *scml_stream::get_data()
{
	return( this->data );
}

void scml_stream::set_desc(const char *the_desc)
{
	this->desc = the_desc;
}

const char *scml_stream::get_desc()
{
	return( this->desc );
}

void scml_stream::set_include_directory_list(const char * const *dir_list)
{
	this->include_directory_list = dir_list;
}

const char * const *scml_stream::get_include_directory_list()
{
	return( this->include_directory_list );
}

int scml_stream::get_length()
{
	return( this->len );
}

void scml_stream::set_file(FILE *the_file)
{
	this->file = the_file;
	if( this->flags & SSF_INPUT ) {
		if( fseek(this->file, 0, SEEK_END) == 0 ) {
			this->len = ftell(this->file);
			fseek(this->file, 0, SEEK_SET);
			this->data = (char *)mustmalloc( this->len + 1 );
			fread(this->data, this->len, 1, this->file);
			this->data[this->len] = 0;
		} else {
			scml_alert(0, SAF_ERROR|SAF_IO,
				   "Error accessing file");
		}
	}
}

FILE *scml_stream::get_file()
{
	return( this->file );
}

scml_stream_pos::scml_stream_pos()
{
	this->stream = 0;
	this->flags = 0;
	this->state = SSPS_NONE;
	this->cursor = 0;
	this->row = 0;
	this->column = 0;
}

scml_stream_pos::~scml_stream_pos()
{
}

void scml_stream_pos::set_stream(struct scml_stream *the_stream)
{
	this->stream = the_stream;
	if( the_stream ) {
		this->state = SSPS_COL_POS;
		this->cursor = the_stream->get_data();
		this->row = 1;
		this->column = 0;
	}
}

struct scml_stream *scml_stream_pos::get_stream()
{
	return( this->stream );
}

void scml_stream_pos::set_flags(unsigned int the_flags)
{
	this->flags = the_flags;
}

unsigned int scml_stream_pos::get_flags()
{
	return( this->flags );
}

void scml_stream_pos::set_state(int the_state)
{
	this->state = the_state;
}

int scml_stream_pos::get_state()
{
	return( this->state );
}

void scml_stream_pos::set_cursor(char *the_cursor)
{
	this->cursor = the_cursor;
}

char *scml_stream_pos::get_cursor()
{
	return( this->cursor );
}

void scml_stream_pos::set_row(int the_row)
{
	this->row = the_row;
}

int scml_stream_pos::get_row()
{
	return( this->row );
}

void scml_stream_pos::set_column(int the_column)
{
	this->column = the_column;
}

int scml_stream_pos::get_column()
{
	return( this->column );
}

char *scml_stream_pos::munge_string(char *str)
{
	char *retval;
	int len, r_pos, s_pos;
	
	len = strlen(str);
	retval = (char *)mustmalloc(len + 1);
	for( r_pos = 0, s_pos = 0; str[s_pos]; r_pos++, s_pos++ ) {
		if( str[s_pos] == '\\' ) {
			switch( str[s_pos + 1] ) {
			case 'n':
				retval[r_pos] = '\n';
				break;
			case 't':
				retval[r_pos] = '\t';
				break;
			case 'x': {
				int c_val;
				
				sscanf(&str[s_pos + 2], "%2x", &c_val);
				retval[r_pos] = c_val;
				s_pos += 2;
				break;
			}
			default:
				retval[r_pos] = str[s_pos + 1];
				break;
			}
			s_pos++;
		}
		else {
			retval[r_pos] = str[s_pos];
		}
	}
	retval[r_pos] = 0;
	return( retval );
}

int scml_stream_pos::scan(int kind, const char *str)
{
	int saved_row = 0, saved_col = 0;
	int str_pos = 0;
	int retval = 0;
	
	/*
	 * Record the last position so we can give the user a range
	 * of text to look at on an error
	 */
	this->last_row = this->row;
	this->last_column = this->column;
	for( ; *this->cursor && !retval; this->cursor++ ) {
		/* Track the cursor position */
		switch( *this->cursor ) {
		case '\n':
			this->row++;
			this->column = -1;
			break;
		case '\t':
			this->column += 7;
			break;
		default:
			break;
		}
		if( kind & SSPF_SCAN_SEQUENCE ) {
			/* Scan for a sequence of characters. */
			if( (*this->cursor == str[str_pos]) ) {
				if( str_pos == 0 ) {
					saved_row = this->row;
					saved_col = this->column;
				}
				str_pos++;
				if( str[str_pos] == 0 )
					retval = 1;
			} else {
				this->cursor -= str_pos;
				if( str_pos ) {
					this->row = saved_row;
					this->column = saved_col;
				}
				str_pos = 0;
			}
		} else if( kind & SSPF_SCAN_SET ) {
			int found = 0;
			
			/* Scan for a character in the set */
			for( str_pos = 0;
			     str[str_pos] && !found;
			     str_pos++ ) {
				if( *this->cursor == str[str_pos] )
					found = 1;
			}
			if( (found && (kind & SSPF_SCAN_IN)) ||
			    (!found && (kind & SSPF_SCAN_NOT_IN)) )
				retval = 1;
		}
		this->column++;
	}
	if( (*this->cursor == 0) && (this->flags & SSPF_IGNORE_EOF) ) {
		retval = 1;
	}
	if( (*this->cursor == 0) && (this->flags & SSPF_IGNORE_EOF) )
		this->flags |= SSPF_EOF;
	if( !retval ) {
		this->last_row = -1;
		this->last_column = -1;
	}
	return( retval );
}

int scml_stream_pos::get_last_row()
{
	return( this->last_row );
}

int scml_stream_pos::get_last_column()
{
	return( this->last_column );
}

int scml_stream_pos::get_number(struct scml_token *st)
{
	int retval = 1;
	char *start;
	
	if( (*(this->cursor - 1) == '0') && ((*this->cursor == 'x') ||
	     (*this->cursor == 'X')) ) {
		/* Its a hex number */
		this->cursor++;
		this->column++;
		start = this->cursor;
		if( this->scan(SSPF_SCAN_NOT_IN|SSPF_SCAN_SET,
			       "0123456789AaBbCcDdEeFf") &&
		    sscanf(start, "%x", &st->value.i) == 1 ) {
			st->kind = SCML_TERM_INT;
		} else {
			scml_alert(this, SAF_ERROR|SAF_LEXICAL,
				   "Not a hex number or"
				   "unexpected end of file");
			retval = 0;
		}
	} else {
		/* Its either an int or a float */
		start = this->cursor - 1;
		if( this->scan(SSPF_SCAN_NOT_IN|SSPF_SCAN_SET,
			       "0123456789") ) {
			/*
			 * We back up here because the scan will leave us at
			 * one character past the one that stopped the scan.
			 * Otherwise we could skip something interesting.
			 */
			this->cursor--;
			this->column--;
			if( *this->cursor == '\n' )
				this->row--;
			if( (*this->cursor == '.') ) {
				/* Try and get a float */
				this->cursor++;
				if( sscanf(start, "%f", &st->value.f) == 1 )
					st->kind = SCML_TERM_FLOAT;
				if( !this->scan(SSPF_SCAN_NOT_IN|SSPF_SCAN_SET,
						"0123456789") ) {
					scml_alert(this, SAF_ERROR|SAF_LEXICAL,
						   "Malformed float number");
					retval = 0;
				}
			} else if( sscanf(start, "%d", &st->value.i) == 1 ) {
				/* Get an int */
				st->kind = SCML_TERM_INT;
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "Not a number");
				retval = 0;
			}
		} else {
  			scml_alert(this, SAF_ERROR|SAF_LEXICAL,
				   "Not a number or unexpected end of file");
  			retval = 0;
		}
	}
	return( retval );
}

const char *scml_id_set
	= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";

struct scml_token scml_stream_pos::get_token()
{
	struct scml_token retval;
	
	if( (this->flags & SSPF_IGNORE_EOF) &&
	    (this->flags & SSPF_EOF) )
		this->state = SSPS_NONE;
	retval.kind = SCML_ERROR;
	switch( this->state ) {
	case SSPS_NONE:
	case SSPS_ERROR:
		retval.kind = SCML_DONE;
		break;
	case SSPS_COL_POS:
		/* Record the column position */
		retval.kind = SCML_COL_POS;
		retval.value.i = this->column;
		this->state = SSPS_ROW_POS;
		break;
	case SSPS_ROW_POS:
		/* Record the row position */
		retval.kind = SCML_ROW_POS;
		retval.value.i = this->row;
		this->state = SSPS_TEXT;
		break;
	case SSPS_ECOL_POS:
		retval.kind = SCML_COL_POS;
		retval.value.i = this->column - 1;
		this->state = SSPS_EROW_POS;
		break;
	case SSPS_EROW_POS:
		retval.kind = SCML_ROW_POS;
		retval.value.i = this->row;
		this->state = SSPS_ESCAPE;
		break;
	case SSPS_CCOL_POS:
		retval.kind = SCML_COL_POS;
		retval.value.i = this->column - 1;
		this->state = SSPS_CROW_POS;
		break;
	case SSPS_CROW_POS:
		retval.kind = SCML_ROW_POS;
		retval.value.i = this->row;
		this->state = SSPS_COMMAND;
		break;
	case SSPS_TEXT: {
		int done = 0;
		
		retval.kind = SCML_TERM_TEXT;
		retval.value.text = this->cursor;
		this->state = SSPS_TEXT;
		while( !done ) {
			/* Scan for a command, escape, or end of the input */
			if( this->scan(SSPF_SCAN_IN|SSPF_SCAN_SET, "<&") ) {
				if( (*(this->cursor - 1) == '&') ) {
					this->state = SSPS_ECOL_POS;
					*(this->cursor - 1) = 0;
					done = 1;
				} else if( !isspace(*this->cursor) ) {
					this->state = SSPS_CCOL_POS;
					*(this->cursor - 1) = 0;
					done = 1;
				}
			} else {
				done = 1;
				if( *retval.value.text == 0 ) {
					retval.kind = SCML_DONE;
				}
			}
		}
		break;
	}
	case SSPS_ESCAPE:
		retval.value.escape = this->cursor;
		if( this->scan(SSPF_SCAN_SEQUENCE, ";") ) {
			retval.kind = SCML_TERM_ESCAPE;
			*(this->cursor - 1) = 0;
			this->state = SSPS_COL_POS;
		} else {
			if( this->flags & SSPF_SINGLE_STATE )
				retval.kind = SCML_DONE;
			else
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "Escape sequence not terminated");
		}
		break;
	case SSPS_COMMAND: {
		/*
		 * Handle the special commands here and then let
		 * SSPS_EXPR take care of everything else
		 */
		switch( *this->cursor ) {
		case '!':
			retval.value.text = "";
			if( this->scan(SSPF_SCAN_SEQUENCE, "-->") ) {
				retval.kind = SCML_IGNORE;
				this->state = SSPS_COL_POS;
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "Comment not terminated");
			}
			break;
		case '|':
			this->cursor++;
			this->column++;
			retval.value.text = this->cursor;
			if( this->scan(SSPF_SCAN_SEQUENCE, "|>") ) {
				retval.kind = SCML_TERM_VERBATIM;
				*(this->cursor - 2) = 0;
				this->state = SSPS_COL_POS;
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "Verbatim not terminated");
			}
			break;
		case '(':
		case '/':
		default:
			retval.kind = SCML_TERM_LT;
			this->state = SSPS_EXPR;
			break;
		}
		break;
	}
	case SSPS_EXPR: {
		/* Scan through white space until we hit something */
		if( !this->scan(SSPF_SCAN_NOT_IN|SSPF_SCAN_SET, " \n\t") ) {
			if( this->flags & SSPF_SINGLE_STATE ) {
				retval.kind = SCML_DONE;
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "Unexpected end of file in "
					   "expression");
			}
			break;
		}
		this->state = SSPS_EXPR;
		switch( *(this->cursor - 1) ) {
		case '>':
			if( *this->cursor == '>' ) {
				retval.kind = SCML_NT_GT;
				this->cursor++;
				this->column++;
			} else if( *this->cursor == '=' ) {
				retval.kind = SCML_NT_GE;
				this->cursor++;
				this->column++;
			} else {
				this->state = SSPS_COL_POS;
				retval.kind = SCML_TERM_GT;
			}
			break;
		case '<':
			if( *this->cursor == '<' ) {
				retval.kind = SCML_NT_LT;
				this->cursor++;
				this->column++;
			} else if( *this->cursor == '=' ) {
				retval.kind = SCML_NT_LE;
				this->cursor++;
				this->column++;
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "'<' is not a valid symbol in "
					   "a command, use '<<' or '<='");
			}
			break;
		case '+':
			retval.kind = SCML_NT_PLUS;
			break;
		case '-':
			retval.kind = SCML_NT_MINUS;
			break;
		case '/':
			retval.kind = SCML_NT_DIV;
			break;
		case '%':
			retval.kind = SCML_NT_MOD;
			break;
		case '*':
			retval.kind = SCML_NT_MULT;
			break;
		case '?':
			retval.kind = SCML_NT_COND;
			break;
		case ':':
			if( *this->cursor == ':' ) {
				retval.kind = SCML_NT_SCOPE_RES;
				this->cursor++;
				this->column++;
			} else
				retval.kind = SCML_NT_COLON;
			break;
		case ',':
			retval.kind = SCML_NT_COMMA;
			break;
		case '=':
			if( *this->cursor == '=' ) {
				retval.kind = SCML_NT_EQUAL;
				this->cursor++;
				this->column++;
			}
			else
				retval.kind = SCML_NT_ASSIGN;
			break;
		case '!':
			if( *this->cursor == '=' ) {
				retval.kind = SCML_NT_NOT_EQUAL;
				this->cursor++;
				this->column++;
			}
			else
				retval.kind = SCML_NT_NOT;
			break;
		case '&':
			if( *this->cursor == '&' ) {
				retval.kind = SCML_NT_LAND;
				this->cursor++;
				this->column++;
			}
			else
				retval.kind = SCML_NT_AND;
			break;
		case '|':
			if( *this->cursor == '|' ) {
				retval.kind = SCML_NT_LOR;
				this->cursor++;
				this->column++;
			}
			else
				retval.kind = SCML_NT_OR;
			break;
		case '.':
			retval.kind = SCML_NT_DOT;
			break;
		case '(':
			retval.kind = SCML_TERM_LPAREN;
			break;
		case ')':
			retval.kind = SCML_TERM_RPAREN;
			break;
		case '[':
			retval.kind = SCML_TERM_LBRACE;
			break;
		case ']':
			retval.kind = SCML_TERM_RBRACE;
			break;
		case '{':
			retval.kind = SCML_TERM_LCURLY;
			break;
		case '}':
			retval.kind = SCML_TERM_RCURLY;
			break;
		case '\'': {
			int no_error;
			char *start;
			
			/* This is an id with special characters in it */
			start = this->cursor;
			while( (no_error = this->scan(SSPF_SCAN_SEQUENCE,
						      "\'" )) &&
			       (*(this->cursor - 2) == '\\') &&
			       (*(this->cursor - 3) != '\\') );
			if( no_error ) {
				*(this->cursor - 1) = 0;
				retval.kind = SCML_TERM_ID;
				retval.value.id = this->munge_string(start);
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "This is a not a valid id string");
			}
			break;
		}
		case '\"': {
			int no_error;
			char *start;
			
			/* Grab the string */
			start = this->cursor;
			while( (no_error = this->scan(SSPF_SCAN_SEQUENCE,
						      "\"" )) &&
			       (*(this->cursor - 2) == '\\') &&
			       (*(this->cursor - 3) != '\\') );
			if( no_error ) {
				tag_data *td;
				
				*(this->cursor - 1) = 0;
				retval.kind = SCML_TERM_STRING;
				retval.value.str = new scml_string;
				td = retval.value.str->add_component();
				td->kind = TAG_STRING;
				td->tag_data_u.str = this->munge_string(start);
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "This is a not a valid string");
			}
			break;
		}
		default:
			/* Its an ID or a number */
			if( isdigit( *(this->cursor - 1) ) ) {
				this->get_number(&retval);
			} else if( isalpha( *(this->cursor - 1) ) ||
				   (*(this->cursor - 1) == '_') ) {
				char *start = (this->cursor - 1);
				int len;
				
				if( this->scan(SSPF_SCAN_NOT_IN|SSPF_SCAN_SET,
					       scml_id_set) ) {
					/*
					 * We back up here because the scan
					 * will leave us at one character
					 * past the one that stopped the scan.
					 * Otherwise we could skip something
					 * interesting.
					 */
					this->cursor--;
					this->column--;
					if( *this->cursor == '\n' )
						this->row--;
					len = this->cursor - start;
					/* Test for special IDs */
					if( !strncmp("true", start, len) ) {
						retval.kind = SCML_TERM_BOOL;
						retval.value.b = 1;
					} else if( !strncmp("false",
							    start, len) ) {
						retval.kind = SCML_TERM_BOOL;
						retval.value.b = 0;
					} else {
						char *id;
						
						id = (char *)
							mustmalloc(len + 1);
						strncpy(id, start, len);
						id[len] = 0;
						
						retval.kind = SCML_TERM_ID;
						retval.value.id = id;
					}
				} else {
					scml_alert(this, SAF_ERROR|SAF_LEXICAL,
						   "Unexpeceted end of file "
						   "in identifier");
				}
			} else {
				scml_alert(this, SAF_ERROR|SAF_LEXICAL,
					   "Invalid character (0x%x) in input",
					   (int)(*(this->cursor - 1)));
			}
			break;
		}
		break;
	}
	default:
		panic("Unknown state in scml_stream_pos::get_token()");
		break;
	}
	if( retval.kind == SCML_ERROR )
		this->state = SSPS_ERROR;
	return( retval );
}

/* End of file. */



syntax highlighted by Code2HTML, v. 0.9.1