package rpn;

import java.io.*;

/** The Calculator Input class just reads the character stream 
	and accepts requests to read the input in chunks or
	change the current input source. 
	It does not try itself to interpret the incoming data. */

/* In the new version the input class just stores patterns. */

/* There are a small number of token types:

		-(digit)+
		 (digit)+
		-(digit)*.(digit)*
		 (digit)*.(digit)*
		-(digit)+/(digit)+
		 (digit)+/(digit)+
		 (letter)(letter|digit)*
		'(letter)(letter|digit)
		@(letter)(letter|digit)
		 single character other than space digit letter \ " # @
		 " ... except " or \n ... "
		 # ... \n
		 EOF
*/

public abstract class Input implements SimpleToken {

	static int code[] = {
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 

		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 
	};

	static {
		for (int i = 'a'; i <= 'z';i++) code[i] = LetterCode;
		for (int i = 'A'; i <= 'Z';i++) code[i] = LetterCode;
		for (int i = '0'; i <= '9';i++) code[i] = DigitCode;
		code['#'] = CommentCode;
		code['@'] = AtCode;
		code[39] = AtCode;
		code['-'] = MinusCode;
		code['='] = EqualsCode;
		code['/'] = FracCode;
		code['.'] = PointCode;
		code['"'] = QuoteCode;
		code['\n'] = LFCode;
		code[255] = EOFCode;
		code[' '] = SpaceCode;
		code['\t'] = SpaceCode;
		code['\r'] = SpaceCode;
	};

	// start = beginning of stuff we haven't used yet
	// eoi = as far as we have read, mark as far as we have processed 
	int start = 0, mark = 0, eoi = 0;
	int lineNo = 0;
	// m beyond start
	
	abstract char nextChar(int i);

	public int peek() {
		return(peek(0));
	}
	
	// abstract because we may have to load more
	abstract int peek(int n);
	
	public int getToken() {
		start = mark;
		int c;
		// gobble space
		// System.out.println("c = " + (char) (peek(0)));
		while (code[c = peek(0)] == SpaceCode) {
			start++;
		}
		int i;
		if (code[c] == EOFCode) {
			mark = start;
			return(EOFTok);
		}
		switch(code[c]) {
			case MinusCode: {
				if (code[peek(1)] == DigitCode) {
					// a negative integer, real or fraction
					i = 2;
					while (code[peek(i)] == DigitCode) {
						i++;
					}
					c = peek(i);
					if (code[c] != PointCode && code[c] != FracCode) {
						mark = start + i;
						return(NegIntTok);
					}
					if (code[c] == PointCode) {
						i++;
						while (code[peek(i)] == DigitCode) {
							i++;
						}
						mark = start + i;
						// print();
						return(NegRealTok);
					}
					if (code[c] == FracCode && code[peek(i+1)] == DigitCode) {
						i++;
						while (code[peek(i)] == DigitCode) {
							i++;
						}
						mark = start + i;
						return(NegFracTok);
					}
					else {
						mark = start + i;
						return(NegIntTok);
					}
					
				}
				else {
					mark = start+1;
					return(SingleTok);
				}
			}
			case DigitCode: {
				// a negative integer, real or fraction
				i = 1;
				while (code[peek(i)] == DigitCode) {
					i++;
				}
				c = peek(i);
				if (code[c] != PointCode && code[c] != FracCode) {
					mark = start + i;
					return(IntTok);
				}
				if (code[c] == PointCode) {
					i++;
					while (code[peek(i)] == DigitCode) {
						i++;
					}
					mark = start + i;
					return(RealTok);
				}
				if (code[c] == FracCode && code[peek(i+1)] == DigitCode) {
					i++;
					while (code[peek(i)] == DigitCode) {
						i++;
					}
					mark = start + i;
					return(FracTok);
				}
				else {
					mark = start + i;
					return(IntTok);
				}
			}
			case LetterCode: {
				// (letter)(letter|digit)*
				i = 1;
				while (code[c = peek(i)] == LetterCode 
					|| code[c] == DigitCode) {
					i++;
				}
				mark = start + i;
				return(WordTok);
			}
			case AtCode: {
				if(code[peek(1)] == LetterCode) {
					i = 2;
					// (letter)(letter|digit)*
					while (code[c = peek(i)] == LetterCode 
							|| code[c] == DigitCode) {
						i++;
					}
					mark = start + i;
					return(LiteralTok);
				}
				else {
					mark = start+1;
					return(SingleTok);
				}
			}
			case CommentCode: {
				// System.out.println("comment");
				i = 1;
				while (code[c = peek(i)] != EOFCode 
						&& code[c] != LFCode) {
					i++;
				}
				// c = \n or EOF
				if (code[c] == LFCode) lineNo++;
				mark = start + i;
				return(CommentTok);
			}
			case QuoteCode: {
				i = 1;
				while (code[c = peek(i)] != EOFCode 
					&& code[c] != QuoteCode 
					&& code[c] != LFCode) {
					i++;
				}
				if (code[c] == LFCode || code[c] == EOFCode) {
	 				mark = start + i;
					return(UntermStringTok);
				}
	 			mark = start + i + 1;
				return(StringTok);
			}
			case LFCode: {
				lineNo++;
				mark = start + 1;
				return(LFTok);
			}
			case EOFCode: {
				return(EOFTok);
			}
			case EqualsCode: {
				if (code[peek(1)] == EqualsCode) {
					mark = start + 2;
					return(WordTok);
				}
				else {
					mark = start + 1;
					return(SingleTok);
				}
			}
			case PointCode:
			case FracCode:
			case OtherCode: {
				// return a single character
				mark = start + 1;
				return(SingleTok);
			}
		}
		System.out.println("invalid character c = [" + ((char) c) + ":" + c + "]");
		return(ErrTok);

	}
	
	// - special types ----------------------------------------
	
	final static long MaxLong = ((1L << 62) - 1L) << 1 + 1L;
	final static long MaxInt = ((1L << 30) - 1L) << 1 + 1L;

	public int getInt() throws OverflowException {
		int M = 0, N = 0;
		for (int i=start;i<mark;i++) {
			M = nextChar(i) - '0';
			if (N <= (MaxInt - M)/10) {
				N = 10*N + M;
			}
			else throw new OverflowException();
		}
		return(N);
	}

	public int getNegInt() throws OverflowException {
		int M = 0, N = 0;
		for (int i=start+1;i<mark;i++) {
			M = nextChar(i) - '0';
			if (N <= (MaxInt - M)/10) {
				N = 10*N + M;
			}
			else throw new OverflowException();
		}
		return(-N);
	}

	public long getLong() throws OverflowException {
		long M = 0, N = 0;
		for (int i=start;i<mark;i++) {
			M = nextChar(i) - '0';
			if (N <= (MaxLong - M)/10) {
				N = 10*N + M;
			}
			else throw new OverflowException();
		}
		return(N);
	}

	public long getNegLong() throws OverflowException {
		long M = 0, N = 0;
		for (int i=start+1;i<mark;i++) {
			M = nextChar(i) - '0';
			if (N <= (MaxLong - M)/10) {
				N = 10*N + M;
			}
			else throw new OverflowException();
		}
		return(-N);
	}

	/* reads (digit)+ | (digit)*.(digit)* */

	public double getDouble() {
		double x = 0.0;
		double p = 0.1;
		int sign = 1;
		int i = start;
		char c = nextChar(i++);
		if (c == '-') {
			sign = -1;
			c = nextChar(i++);
		}
		while (c != '.' && i < mark) {
			x = 10.0*x + (double) (c - '0');
			c = nextChar(i++);
		}
		while (i < mark) {
			c = nextChar(i++);
			x += ((double) (c - '0'))*p;
			p *= 0.1;
		}
		return(sign*x);
	}

	/* reads (|-)(digit)+/(digit)+ as a double to handle overflow */

	public double getDoubleFrac() throws DivideByZeroException {
		int sign = 0;
		double x = 0.0;
		int i = start;
		char c;
		c = nextChar(i++);
		if (c == '-') {
			sign = -1;
			c = nextChar(i++);
		}
		while (c != '/') {
			x = 10.0*x + (double) (c - '0');
			c = nextChar(i++);
		}
		double y = 0.0;
		while (i < mark) {
			c = nextChar(i++);
			y = 10.0*y + (double) (c - '0');
		}
		if (y == 0.0) {
			throw new DivideByZeroException();
		}
		if (sign < 0) return(-x/y);
		return(x/y);
	}

	public String getString() {
		char b[] = new char[mark - start-2];
		for (int i=start+1;i<mark-1;i++) {
			b[i-start-1] = (char) nextChar(i);
		}
		return(new String(b));
	}
	
	public String getLiteral() {
		char b[] = new char[mark - start-1];
		for (int i=start+1;i<mark;i++) {
			b[i-start-1] = (char) nextChar(i);
		}
		return(new String(b));
	}
	
	public String getWord() {
		char b[] = new char[mark - start];
		for (int i=start;i<mark;i++) {
			b[i-start] = (char) nextChar(i);
		}
		return(new String(b));
	}
	
	public String toString() {
		String s = new String("<" + getWord() + ">");
		return(s);
	}
}
