
using System;
using System.IO;
using System.Collections;

namespace Liss {

public class Token {
	public int kind;    // token kind
	public int pos;     // token position in the source text (starting at 0)
	public int col;     // token column (starting at 0)
	public int line;    // token line (starting at 1)
	public string val;  // token value
	public Token next;  // ML 2005-03-11 Tokens are kept in linked list
}

public class Buffer {
	public const char EOF = (char)256;
	const int MAX_BUFFER_LENGTH = 64 * 1024; // 64KB
	byte[] buf;         // input buffer
	int bufStart;       // position of first byte in buffer relative to input stream
	int bufLen;         // length of buffer
	int fileLen;        // length of input stream
	int pos;            // current position in buffer
	Stream stream;      // input stream (seekable)
	bool isUserStream;  // was the stream opened by the user?
	
	public Buffer (Stream s, bool isUserStream) {
		stream = s; this.isUserStream = isUserStream;
		fileLen = bufLen = (int) s.Length;
		if (stream.CanSeek && bufLen > MAX_BUFFER_LENGTH) bufLen = MAX_BUFFER_LENGTH;
		buf = new byte[bufLen];
		bufStart = Int32.MaxValue; // nothing in the buffer so far
		Pos = 0; // setup buffer to position 0 (start)
		if (bufLen == fileLen) Close();
	}
	
	~Buffer() { Close(); }
	
	void Close() {
		if (!isUserStream && stream != null) {
			stream.Close();
			stream = null;
		}
	}
	
	public int Read () {
		if (pos < bufLen) {
			return buf[pos++];
		} else if (Pos < fileLen) {
			Pos = Pos; // shift buffer start to Pos
			return buf[pos++];
		} else {
			return EOF;
		}
	}

	public int Peek () {
		if (pos < bufLen) {
			return buf[pos];
		} else if (Pos < fileLen) {
			Pos = Pos; // shift buffer start to Pos
			return buf[pos];
		} else {
			return EOF;
		}
	}
	
	public string GetString (int beg, int end) {
		int len = end - beg;
		char[] buf = new char[len];
		int oldPos = Pos;
		Pos = beg;
		for (int i = 0; i < len; i++) buf[i] = (char) Read();
		Pos = oldPos;
		return new String(buf);
	}

	public int Pos {
		get { return pos + bufStart; }
		set {
			if (value < 0) value = 0; 
			else if (value > fileLen) value = fileLen;
			if (value >= bufStart && value < bufStart + bufLen) { // already in buffer
				pos = value - bufStart;
			} else if (stream != null) { // must be swapped in
				stream.Seek(value, SeekOrigin.Begin);
				bufLen = stream.Read(buf, 0, buf.Length);
				bufStart = value; pos = 0;
			} else {
				pos = fileLen - bufStart; // make Pos return fileLen
			}
		}
	}
}

public class Scanner {
	const char EOL = '\n';
	const int eofSym = 0; /* pdt */
	const int charSetSize = 256;
	const int maxT = 72;
	const int noSym = 72;
	short[] start = {
	  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, 41,  0,  0,  0,  0, 21,  0, 23, 24, 43, 42, 12, 35, 31, 34,
	  2,  2,  2,  2,  2,  2,  2,  2,  2,  2, 36, 14, 37, 40, 38,  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, 16,  0, 17,  0,  0,
	  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
	  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, 10, 39, 11,  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,
	  -1};
	char valCh;       // current input character (for token.val)

	public Buffer buffer; // scanner buffer
	
	Token t;          // current token
	char ch;          // current input character
	int pos;          // column number of current character
	int line;         // line number of current character
	int lineStart;    // start position of current line
	int oldEols;      // EOLs that appeared in a comment;
	BitArray ignore;  // set of characters to be ignored by the scanner

	Token tokens;     // list of tokens already peeked (first token is a dummy)
	Token pt;         // current peek token
	
	char[] tval = new char[128]; // text of current token
	int tlen;         // length of current token
	
	public Scanner (string fileName) {
		try {
			Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
			buffer = new Buffer(stream, false);
			Init();
		} catch (IOException) {
			Console.WriteLine("--- Cannot open file {0}", fileName);
			System.Environment.Exit(1);
		}
	}
	
	public Scanner (Stream s) {
		buffer = new Buffer(s, true);
		Init();
	}
	
	void Init() {
		pos = -1; line = 1; lineStart = 0;
		oldEols = 0;
		NextCh();
		ignore = new BitArray(charSetSize+1);
		ignore[' '] = true;  // blanks are always white space
		ignore[9] = true; ignore[10] = true; ignore[13] = true; 
		pt = tokens = new Token();  // first token is a dummy
	}
	
	void NextCh() {
		if (oldEols > 0) { ch = EOL; oldEols--; } 
		else {
			ch = (char)buffer.Read(); pos++;
			// replace isolated '\r' by '\n' in order to make
			// eol handling uniform across Windows, Unix and Mac
			if (ch == '\r' && buffer.Peek() != '\n') ch = EOL;
			if (ch == EOL) { line++; lineStart = pos + 1; }
		}
		valCh = ch;
		if (ch != Buffer.EOF) ch = char.ToLower(ch);
	}

	void AddCh() {
		if (tlen >= tval.Length) {
			char[] newBuf = new char[2 * tval.Length];
			Array.Copy(tval, 0, newBuf, 0, tval.Length);
			tval = newBuf;
		}
		tval[tlen++] = valCh;
		NextCh();
	}




	void CheckLiteral() {
		switch (t.val.ToLower()) {
			case "program": t.kind = 3; break;
			case "declarations": t.kind = 5; break;
			case "statements": t.kind = 6; break;
			case "integer": t.kind = 12; break;
			case "boolean": t.kind = 13; break;
			case "set": t.kind = 14; break;
			case "sequence": t.kind = 15; break;
			case "array": t.kind = 16; break;
			case "size": t.kind = 17; break;
			case "true": t.kind = 18; break;
			case "false": t.kind = 19; break;
			case "succ": t.kind = 27; break;
			case "pred": t.kind = 28; break;
			case "write": t.kind = 31; break;
			case "writeln": t.kind = 32; break;
			case "tail": t.kind = 35; break;
			case "head": t.kind = 36; break;
			case "cons": t.kind = 37; break;
			case "del": t.kind = 38; break;
			case "copy": t.kind = 39; break;
			case "ismember": t.kind = 40; break;
			case "isempty": t.kind = 41; break;
			case "length": t.kind = 42; break;
			case "if": t.kind = 55; break;
			case "then": t.kind = 56; break;
			case "else": t.kind = 57; break;
			case "for": t.kind = 58; break;
			case "in": t.kind = 59; break;
			case "inarray": t.kind = 60; break;
			case "infunction": t.kind = 61; break;
			case "with": t.kind = 62; break;
			case "stepup": t.kind = 64; break;
			case "stepdown": t.kind = 65; break;
			case "satisfying": t.kind = 66; break;
			case "while": t.kind = 67; break;
			case "read": t.kind = 68; break;
			case "subprogram": t.kind = 69; break;
			case "return": t.kind = 71; break;
			default: break;
		}
	}

	Token NextToken() {
		while (ignore[ch]) NextCh();

		t = new Token();
		t.pos = pos; t.col = pos - lineStart + 1; t.line = line; 
		int state = start[ch];
		tlen = 0; AddCh();
		
		switch (state) {
			case -1: { t.kind = eofSym; break; } // NextCh already done
			case 0: { t.kind = noSym; break; }   // NextCh already done
			case 1:
				if ((ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z')) {AddCh(); goto case 1;}
				else {t.kind = 1; t.val = new String(tval, 0, tlen); CheckLiteral(); return t;}
			case 2:
				if ((ch >= '0' && ch <= '9')) {AddCh(); goto case 2;}
				else {t.kind = 2; break;}
			case 3:
				if (!(ch == '*') && ch != Buffer.EOF) {AddCh(); goto case 3;}
				else if (ch == '*') {AddCh(); goto case 5;}
				else {t.kind = noSym; break;}
			case 4:
				{t.kind = 73; break;}
			case 5:
				if ((ch <= ')' || ch >= '+' && ch <= '.' || ch >= '0' && ch <= 255)) {AddCh(); goto case 3;}
				else if (ch == '/') {AddCh(); goto case 4;}
				else if (ch == '*') {AddCh(); goto case 5;}
				else {t.kind = noSym; break;}
			case 6:
				if (ch == 13) {AddCh(); goto case 8;}
				else if ((ch <= 9 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 255)) {AddCh(); goto case 6;}
				else if (ch == 10) {AddCh(); goto case 4;}
				else {t.kind = noSym; break;}
			case 7:
				if ((ch <= 9 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 255)) {AddCh(); goto case 7;}
				else if (ch == 13) {AddCh(); goto case 9;}
				else if (ch == 10) {AddCh(); goto case 4;}
				else {t.kind = noSym; break;}
			case 8:
				if (ch == 13) {AddCh(); goto case 8;}
				else if ((ch <= 9 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 255)) {AddCh(); goto case 6;}
				else if (ch == 10) {AddCh(); goto case 4;}
				else {t.kind = noSym; break;}
			case 9:
				if (ch == 13) {AddCh(); goto case 9;}
				else if ((ch <= 9 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 255)) {AddCh(); goto case 7;}
				else if (ch == 10) {AddCh(); goto case 4;}
				else {t.kind = noSym; break;}
			case 10:
				{t.kind = 4; break;}
			case 11:
				{t.kind = 7; break;}
			case 12:
				{t.kind = 8; break;}
			case 13:
				{t.kind = 9; break;}
			case 14:
				{t.kind = 10; break;}
			case 15:
				{t.kind = 11; break;}
			case 16:
				{t.kind = 20; break;}
			case 17:
				{t.kind = 21; break;}
			case 18:
				{t.kind = 22; break;}
			case 19:
				{t.kind = 23; break;}
			case 20:
				{t.kind = 25; break;}
			case 21:
				if (ch == '&') {AddCh(); goto case 22;}
				else {t.kind = noSym; break;}
			case 22:
				{t.kind = 26; break;}
			case 23:
				{t.kind = 29; break;}
			case 24:
				{t.kind = 30; break;}
			case 25:
				{t.kind = 45; break;}
			case 26:
				{t.kind = 48; break;}
			case 27:
				{t.kind = 49; break;}
			case 28:
				{t.kind = 50; break;}
			case 29:
				{t.kind = 52; break;}
			case 30:
				{t.kind = 53; break;}
			case 31:
				if (ch == '.') {AddCh(); goto case 32;}
				else {t.kind = noSym; break;}
			case 32:
				{t.kind = 63; break;}
			case 33:
				{t.kind = 70; break;}
			case 34:
				if (ch == '*') {AddCh(); goto case 3;}
				else if (ch == '/') {AddCh(); goto case 6;}
				else {t.kind = 47; break;}
			case 35:
				if (ch == '-') {AddCh(); goto case 7;}
				else if (ch == '>') {AddCh(); goto case 13;}
				else {t.kind = 44; break;}
			case 36:
				if (ch == '=') {AddCh(); goto case 15;}
				else if (ch == ':') {AddCh(); goto case 33;}
				else {t.kind = noSym; break;}
			case 37:
				if (ch == '<') {AddCh(); goto case 18;}
				else if (ch == '=') {AddCh(); goto case 29;}
				else {t.kind = 51; break;}
			case 38:
				if (ch == '>') {AddCh(); goto case 19;}
				else if (ch == '=') {AddCh(); goto case 30;}
				else {t.kind = 54; break;}
			case 39:
				if (ch == '|') {AddCh(); goto case 20;}
				else {t.kind = 24; break;}
			case 40:
				if (ch == '=') {AddCh(); goto case 27;}
				else {t.kind = 33; break;}
			case 41:
				if (ch == '=') {AddCh(); goto case 28;}
				else {t.kind = 34; break;}
			case 42:
				if (ch == '+') {AddCh(); goto case 25;}
				else {t.kind = 43; break;}
			case 43:
				if (ch == '*') {AddCh(); goto case 26;}
				else {t.kind = 46; break;}

		}
		t.val = new String(tval, 0, tlen);
		return t;
	}
	
	// get the next token (possibly a token already seen during peeking)
	public Token Scan () {
		if (tokens.next == null) {
			return NextToken();
		} else {
			pt = tokens = tokens.next;
			return tokens;
		}
	}

	// peek for the next token, ignore pragmas
	public Token Peek () {
		if (pt.next == null) {
			do {
				pt = pt.next = NextToken();
			} while (pt.kind > maxT); // skip pragmas
		} else {
			do {
				pt = pt.next;
			} while (pt.kind > maxT);
		}
		return pt;
	}
	
	// make sure that peeking starts at the current scan position
	public void ResetPeek () { pt = tokens; }

} // end Scanner

}