//
//  EXPParser.m
//  ParserTest
//
//  Created by Ashley on 29/05/2007.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "EXPParser.h"
#import "EXPError.h"
#import "EXPLoader.h"

#define TAGUNKNOWN -2
#define TAGENDOFFILE -1
#define TAGBINARYOP 0
#define TAGUNARYOP 1
#define TAGLITERAL 2
#define TAGIDENTIFIER 3
#define TAGSTRINGLITERAL 4
#define TAGOTHER 5

@implementation EXPParser

- (id) init
{
	self = [super init];
	if (self==nil) {	
		return nil;
	}
	
	_string = nil;
	_length = 0;
	_symbolType = TAGENDOFFILE;
	_ch = '\0';
	_echo = YES;
	_isCaseSensitive = YES;
	_recovering = NO;

	return self;
}

- (void) setString:(id)string
{
	[string retain];
	[_string release];
	_string = string;

	_count = 0;
	_length = [_string length];
	_symbol = nil;
//	_haveSymbol = NO;
 
	_skipComments = YES;
	_newLine = YES;
	_lineNumber = 1;
	[self readChar];
	[self readSymbol];
}

- (id) string
{
	return _string;
}

- (void) setEcho:(BOOL)echo
{
	_echo = echo;
}

- (BOOL) echo
{
	return _echo;
}

- (void) setIsCaseSensitive:(BOOL)caseSensitive
{
	_isCaseSensitive = caseSensitive;
}

- (BOOL) isCaseSensitive
{
	return _isCaseSensitive;
}

- (void) setIsRecovering:(BOOL)recovering
{
	_recovering = recovering;
}

- (BOOL) isRecovering
{
	return _recovering;
}

- (void) setLoader:(id)loader
{
	_loader = loader;
}

- (id) loader
{
	return _loader;
}

- (void) readChar
{
//	unichar ch;
	if (_count>=_length) {
		_ch = '\0';
	} else {
		_ch = [_string characterAtIndex:_count++];
/*		if ((_ch>='a') && (_ch<='z')) {
			_ch = _ch -  'a' + 'A';
		} */
	}
	
	if (_echo) {
		if (_newLine) {
			printf("%04d: ", _lineNumber++);
		}
		if (_ch!='\0') {
			putchar((int)_ch);
		}
		_newLine = (_ch=='\n');
	}
	
}

- (unichar) nextChar
{
	unichar ch;
	if (_count>=_length) {
		ch = '\0';
	} else {
		ch = [_string characterAtIndex:(_count)];
/*		if ((ch>='a') && (ch<='z')) {
			ch = ch -  'a' + 'A';
		} */
	} 
	
	return ch;
}

- (void) readSymbol
{
	_readPastEol = NO;

	while ((_ch==' ') || (_ch=='\t') || (_ch=='\r') || (_ch=='\n')) {
		if ((_ch=='\n') || (_ch=='\r')) {
			_readPastEol = YES;
		}
		[self readChar]; 
	}
		
	if (_skipComments) {
		for(;;) {
			while ((_ch==' ') || (_ch=='\t') || (_ch=='\r') || (_ch=='\n')) {
				if ((_ch=='\n') || (_ch=='\r')) {
					_readPastEol = YES;
				}
				[self readChar]; 
			}
/*			if (!((_ch=='/') && (([self nextChar]=='/') || ([self nextChar]=='*')))) {
				break;
			} */
			BOOL haveComment = ((_ch=='/') && (([self nextChar]=='/') || ([self nextChar]=='*'))) || (_ch=='#');
			if (!haveComment) {
				break;
			}
			if (_ch=='/') {
				[self readChar]; 
			}
			if ((_ch=='/') || (_ch=='#')) {
				[self readChar];
				while ((_ch!='\0') && (_ch!='\n')) {
					[self readChar];
				}
			} else {
				[self readChar];
			}
		}

	}
	
	_floatLiteral = NO;
	
	NSMutableString *symbol = [[NSMutableString alloc] init];
	NSString *str;
	
	if (isalpha(_ch) || (_ch=='_')) {
		str = [[NSString alloc] initWithCharacters:&_ch length:1];
		[symbol appendString:str];
		[str release];
		for(;;) {
			[self readChar];
			if (!isalpha(_ch) && !isdigit(_ch) && (_ch!='_')) {
				break;
			}
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
		}
		if (_ch=='!') {
			[symbol appendString:@"!"];
			for(;;) {
				[self readChar];
				if (!isalpha(_ch) && !isdigit(_ch) && (_ch!='_')) {
					break;
				}
				str = [[NSString alloc] initWithCharacters:&_ch length:1];
				[symbol appendString:str];
				[str release];
			}
		}
		BOOL _comparisonMask = _isCaseSensitive?0:NSCaseInsensitiveSearch;
		if ([symbol compare:@"and" options:_comparisonMask]==NSOrderedSame || [symbol compare:@"or" options:_comparisonMask]==NSOrderedSame) {
			_symbolType = TAGBINARYOP;
		} else {
			_symbolType = TAGIDENTIFIER;
		}
		
	} else if (isdigit(_ch) || (_ch=='.')) {
//		NSString *str;
		while (isdigit(_ch)) {
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
			[self readChar];
		}

		if (_ch=='.') {
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
			[self readChar];
			_floatLiteral = YES;
		}

		while (isdigit(_ch)) {
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
			[self readChar];
		}

		if ((_ch=='e') || (_ch=='E')) {
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
			[self readChar];
			if ((_ch=='+') || (_ch=='-')) {
				str = [[NSString alloc] initWithCharacters:&_ch length:1];
				[symbol appendString:str];
				[str release];
				[self readChar];
			}
			while (isdigit(_ch)) {
				str = [[NSString alloc] initWithCharacters:&_ch length:1];
				[symbol appendString:str];
				[str release];
				[self readChar];
			}
			_floatLiteral = YES;

		}
		_symbolType = TAGLITERAL;
		
	} else if ((_ch=='=') || (_ch=='>') || (_ch=='<') || (_ch=='!')) {
		str = [[NSString alloc] initWithCharacters:&_ch length:1];
		[symbol appendString:str];
		[str release];
		[self readChar];
		if (_ch=='=') {
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
			[self readChar]; 
		}
		_symbolType = TAGBINARYOP; 
		
	} else if ((_ch=='-') || (_ch=='*') || (_ch=='/') || (_ch=='^') || (_ch=='+') || (_ch=='?') || (_ch==':') ) {
		str = [[NSString alloc] initWithCharacters:&_ch length:1];
		[symbol appendString:str];
		[str release];
		[self readChar];
		_symbolType = TAGBINARYOP;

	} else if ((_ch=='(')  || (_ch==')') || (_ch=='[') || (_ch==']') || (_ch=='{') || (_ch=='}') || 
			   (_ch==';') || (_ch==',') || (_ch=='\'') || (_ch=='\'') || (_ch=='#')) {
		str = [[NSString alloc] initWithCharacters:&_ch length:1];
		[symbol appendString:str];
		[str release];
		[self readChar];
		_symbolType = TAGOTHER;

	} else if (_ch=='"') {
		[self readChar];
		while ((_ch!='"') && (_ch!='\n') && (_ch!='\0')) {
			str = [[NSString alloc] initWithCharacters:&_ch length:1];
			[symbol appendString:str];
			[str release];
			[self readChar];
		}
		if (_ch!='\0') {
			[self readChar];
		}
		_symbolType = TAGSTRINGLITERAL;

	} else if (_ch=='\0') {
		_symbolType = TAGENDOFFILE;
		
	} else {
		str = [[NSString alloc] initWithCharacters:&_ch length:1];
		[symbol appendString:str];
		[str release];
		[self readChar];
		_symbolType = TAGUNKNOWN;
	}
	
	[_symbol release];
	_symbol = [[NSString alloc] initWithString:symbol];
	[symbol release];
}

- (BOOL) testSymbol:(NSString *)symbol
{	
	BOOL _comparisonMask = _isCaseSensitive?0:NSCaseInsensitiveSearch;
	BOOL success = [[self symbol] compare:symbol options:_comparisonMask]==NSOrderedSame;
	if (success) {
		[self readSymbol];
	}
	
	return success;
}

- (BOOL) checkSymbol:(NSString *)symbol
{
	BOOL success;
	BOOL recovering = [self isRecovering];
	
	if (recovering) {
	} else {
		success = [self testSymbol:symbol];
	
		id loader = [self loader];
		if (!success && (loader!=nil)) {
			EXPError *error = (EXPError *)[loader error];
			int lineNumber = [self lineNumber];
			NSString *message = [[NSString alloc] initWithFormat:@"Error: '%@' found where '%@' expected", [self symbol], symbol];
			success = [error addError:message atLine:lineNumber];
			[message release];
		}
	}
	
	return success;
}

- (id) symbol
{
	return _symbol;
}

- (int) lineNumber
{
	return _lineNumber;
}

- (BOOL) isFloatLiteral
{
	return _floatLiteral;
}

- (BOOL) isBinaryOp
{
	return (_symbolType==TAGBINARYOP);
}

- (BOOL) isLiteral
{
	return (_symbolType==TAGLITERAL);
}

- (BOOL) isStringLiteral
{
	return (_symbolType==TAGSTRINGLITERAL);
}

- (BOOL)isIdentifier
{
	return (_symbolType==TAGIDENTIFIER);
}

- (BOOL) isEndOfLine
{
	return _readPastEol;
}

- (BOOL) isEndOfFile
{
	return (_ch=='\0');
}

- (void) dealloc
{
	[_symbol release];
	[_string release];
	[super dealloc];
}

@end