package Iota;

import java.io.InputStream;

%%

%init{
    lineSeparator = System.getProperty("line.separator", "\n");
%init}

%{

private int lastId = -1;
private String filename = "";
private String lineSeparator;

public Lexer(InputStream in, String filename) {
	this(in);
	this.filename = filename;
}

private void error(String message) throws LexicalError {
	throw new LexicalError(filename, yyline+1, message);
}

private Token t(int id, Object value) {
	lastId = id;
	return new Token(id, filename, yyline + 1, yychar, yychar + yylength(), value);
}

private Token t(int id) {
	return t(id, yytext());
}

private String interp_string(String s) throws LexicalError {
	String token = "";
	int index = 0;
	int code, i;
	while (index < s.length()) {
		try {
			char c = s.charAt(index++);
			switch (c) {
			case '\\':
				c = s.charAt(index++);
				switch (c) {
				case 'n':
					token += '\n';
					break;
				case 't':
					token += '\t';
					break;
				case 'N': 
					token += lineSeparator;
					break;
				case '\\':
					token += '\\';
					break;
				case '\"':
					token += '\"';
					break;
				case '^':		// "Ctrl-" codes
					c = s.charAt(index++);
					if (c < 64 || c > 126) {
						error("Illegal control character : " + c);
					} else {
						token += (char) (c % 32);
					}
					break;
				case ' ' :
				case '\r':
				case '\n':     //Eat formatting characters
				case '\t':
				case '\f':
					while (c!='\\') {
						c=s.charAt(index++);
					}
					break;
				case '0':		//ASCII codes
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					if (index+2 > s.length()) {
						error("Illegal ASCII escape code");
						index += 2;
						break;
					}
					String asciiCode = c + s.substring(index, index+2);
					try {
						code = Integer.parseInt(asciiCode);
					} catch (NumberFormatException e) {
						error("Illegal ASCII escape code : " + asciiCode);
						index += 2;
						break;
					}
					if (code < 0 || code > 127) {
						error("Illegal ASCII escape code : " + code);
						index += 2;
						break;
					}
					token += (char) code;
					index += 2;
					break;
				default:
					error("Illegal escape code : " + c);
				}
				break;
			default:
				token += c;
				break;
			}
		} catch (IndexOutOfBoundsException e) {
			error("Illegal string literal");
		}
	}
	return token;
}

%}

%eofval{
    return t(Constant.EOF, null);
%eofval}

%yylexthrow{
	LexicalError
%yylexthrow}

%class Lexer
%type Token
%function getToken
%public
%char
%line
%state COMMENT
%state STRING

digits = 0|[1-9][0-9]*
letter = [A-Za-z]
identifier = {letter}({letter}|[0-9_])*
white_space_char = [\ \t\n\r\f]
whitespace = {white_space_char}+
slashcomment = //.*
blockcomment=[^"*"]*"*"("*"|[^/"*"][^"*"]*"*")*/
PC=[\040-\041\043-\133\135-\176]
ES=\\(N|n|t|"^"[\100-\176]|[0-9][0-9][0-9]|\"|\\|{whitespace}\\)
string_lit=({PC}|{ES})*
string_lit_quote={string_lit}\"
string_lit_slash={string_lit}\\

%%

<YYINITIAL> "uses"	{ return t(Constant.USES); }
<YYINITIAL> "int"	{ return t(Constant.INT_TYPE); }
<YYINITIAL> "string"	{ return t(Constant.STRING_TYPE); }
<YYINITIAL> "bool"	{ return t(Constant.BOOLEAN_TYPE); }
<YYINITIAL> "array"	{ return t(Constant.ARRAY); }
<YYINITIAL> "object"	{ return t (Constant.OBJECT_TYPE); /* Iota+ */ }
<YYINITIAL> "interface"	{ return t (Constant.INTERFACE); /* Iota+ */ }
<YYINITIAL> "implements" {return t (Constant.IMPLEMENTS); /* Iota+ */}
<YYINITIAL> "extends" {return t (Constant.EXTENDS); /* Iota+ */}
<YYINITIAL> "this"	{ return t (Constant.THIS); /* Iota+ */ }
<YYINITIAL> "null"	{ return t (Constant.NULL_CONST); /* Iota+ */ }
<YYINITIAL> "cast"	{ return t (Constant.CAST); /* Iota+ */ }
<YYINITIAL> "class"	{ return t (Constant.CLASS); /* Iota+ */ }
<YYINITIAL> "<="	{ return t (Constant.LEQ); /* Iota+ */ }
<YYINITIAL> ">="	{ return t (Constant.GEQ); /* Iota+ */ }
<YYINITIAL> "!="	{ return t (Constant.NEQ); /* Iota+ */ }
<YYINITIAL> "++"	{ return t (Constant.INCREMENT); /* Iota+ */ }
<YYINITIAL> "--"	{ return t (Constant.DECREMENT); /* Iota+ */ }
<YYINITIAL> "break"	{ return t (Constant.BREAK); /* Iota+ */ }
<YYINITIAL> "true"	{ return t(Constant.TRUE_CONST, Boolean.TRUE); }
<YYINITIAL> "false"	{ return t(Constant.FALSE_CONST, Boolean.FALSE); }
<YYINITIAL> "length"	{ return t(Constant.LENGTH); }
<YYINITIAL> "if"	{ return t(Constant.IF); }
<YYINITIAL> "while"	{ return t(Constant.WHILE); }
<YYINITIAL> "else"	{ return t(Constant.ELSE); }
<YYINITIAL> "return"	{ return t(Constant.RETURN); }
<YYINITIAL> "new"	{ return t(Constant.NEW); }
<YYINITIAL> "=="	{ return t(Constant.EQ); }
<YYINITIAL> "&"		{ return t(Constant.AND); }
<YYINITIAL> "|"		{ return t(Constant.OR); }
<YYINITIAL> "<"		{ return t(Constant.LT); }
<YYINITIAL> ">"		{ return t(Constant.GT); }
<YYINITIAL> "+"		{ return t(Constant.PLUS); }
<YYINITIAL> "-"		{ return t(Constant.MINUS); }
<YYINITIAL> "*"		{ return t(Constant.TIMES); }
<YYINITIAL> "/"		{ return t(Constant.DIVIDE); }
<YYINITIAL> "%"		{ return t(Constant.MOD); }
<YYINITIAL> "!"		{ return t(Constant.NOT); }
<YYINITIAL> ","		{ return t(Constant.COMMA); }
<YYINITIAL> "("		{ return t(Constant.LPAREN); }
<YYINITIAL> ")"		{ return t(Constant.RPAREN); }
<YYINITIAL> "="		{ return t(Constant.ASSIGN); }
<YYINITIAL> ";"		{ return t(Constant.SEMI); }
<YYINITIAL> "."		{ return t(Constant.DOT); }
<YYINITIAL> "{"		{ return t (Constant.LCURLY); }
<YYINITIAL> "}"		{ return t (Constant.RCURLY); }
<YYINITIAL> "["		{ return t(Constant.LSQUARE); }
<YYINITIAL> "]"		{ return t(Constant.RSQUARE); }
<YYINITIAL> ":"		{ return t(Constant.COLON); }
<YYINITIAL> {identifier}	{ return t(Constant.IDENTIFIER, yytext().intern()); }
					    
<YYINITIAL> {digits}	{ 
	Integer newInt = null;
	try {
		if (lastId == Constant.MINUS) {
			newInt = new Integer("-" + yytext());
		} else {
			newInt = new Integer(yytext());
		}
	} catch (NumberFormatException e) {
		error("Invalid integer : " + yytext());
	}	
	return t(Constant.INT_CONST, yytext()); 
}

<YYINITIAL> \"		{yybegin(STRING);}

<STRING> {string_lit_quote} { 
	yybegin(YYINITIAL);
	return t(Constant.STRING_CONST, interp_string(yytext().substring(0,yytext().length()-1)));
}

<STRING> {string_lit} {
	error("Unclosed string literal");	
}

<STRING> {string_lit_slash} {
	error("Illegal escape character");
}

<STRING> . {
	error("Illegal character in string literal : " + yytext());
}

<YYINITIAL> {slashcomment}	{}
<YYINITIAL> {whitespace}	{}
<YYINITIAL> "/*"	{ yybegin (COMMENT); }

<COMMENT> {blockcomment} {yybegin (YYINITIAL);}
<COMMENT> . {
	error("Illegal comment");
}

<YYINITIAL> .		{ 
	error("Invalid character : " + yytext());
}