import java.util.*;
import java.io.*;

/** CS321 Fall'04 Homework 1: Checker and interpreter for E language.
 *  @author Andrew Tolmach
 *  @version 23Sept04
 */


/** -------------- ASTs, Checking, Interpretation ------------------- */

/* This class describes abstract syntax trees for E language programs. 
 */
final class Prog {
    private Exp e;

    Prog(Exp e) {
	this.e = e;
    }

    /** Check that all variables in program are
     * declared before use. 
     */
    boolean check() {
	return e.check(Env.empty);
    }
}

/** This class and its sub-classes describe abstract syntax trees 
 *  for E language expressions and functions on them. 
 */
abstract class Exp {

    /** Check that all variables in expression are declared before use, 
     *	using specified environment.
     *	If any undeclared variable is detected, prints a message to 
     *  standard error.
     *  @param env environment containing defined variables
     *	@return true if expression uses only declared variables
    */
    abstract boolean check(Env env);
}

final class VarExp extends Exp {
    private String i;
    VarExp (String i) {
	this.i = i;
    }
    public String toString() {
	return i;
    }
    boolean check(Env env) {
	if (Env.find(env,i) != null)
	    return true;
	else {
	    System.err.println 
		("Checking error: Variable \"" 
		   + i + "\" used but not declared");
	    return false;
	}
    }
}

final class ConstExp extends Exp {
    private int c;
    ConstExp (int c) {
	this.c = c;
    }   
    public String toString() {
	return Integer.toString(c);
    }
    boolean check(Env env) {
	return true;
    }
}

final class PlusExp extends Exp {
    private Exp l;
    private Exp r;
    PlusExp (Exp l, Exp r) {
	this.l = l; this.r = r;
    }
    public String toString () {
	return "(+ " + l + " " + r + ")";
    }
    boolean check(Env env) {
	return l.check(env) && r.check(env);
    }
}


final class SetExp extends Exp {
    /** Variable to set. */
    private String i; // identifier
    /** RHS expression. */
    private Exp e;
    SetExp (String i, Exp e) {
	this.i = i; this.e = e;
    }
    public String toString () {
	return "(set " + i + " " + e + ")";
    }
    boolean check(Env env) {
	if (Env.find(env,i) != null)
	    return e.check(env);
	else {
	    System.err.println 
		("Checking error: Variable \"" 
		 + i + "\" set but not declared");
	    return false;
	}
    }
}

final class BlockExp extends Exp {
    /** Declared identifiers. */
    private List is;  // contains Strings (representing identifiers)
    /** Sequence of expressions. */
    private List es;  // contains Exps

    BlockExp (List is, List es) {
	this.is = is; this.es = es;
    }
    public String toString() {
	String r = "(block ( ";
	for (Iterator it = is.iterator(); it.hasNext(); ) 
	    r += (String) it.next() + " ";
	r += ") ( ";
	for (Iterator it = es.iterator(); it.hasNext(); ) 
	    r += ((Exp) it.next()) + " ";
	r += "))";
	return r;
    }
    boolean check(Env env) {
	for (Iterator it = is.iterator(); it.hasNext(); ) {
	    String id = (String) it.next();
	    env = new Env(id,env); 
	}
	for (Iterator it = es.iterator(); it.hasNext(); ) {
	    Exp exp = (Exp) it.next();
	    if (!exp.check(env))
		return false;
	}
	return true;
    }
}

/** Environments describe sets of identifiers.
 *  Implemented by linked lists.
 */
class Env {
    String key;
    Env next;

    /** Empty environment is just a null Env. */
    static final Env empty = null;

    /** Creates new Env by extending an existing Env with a new key.
     */
    Env(String key, Env next) {
	this.key = key; this.next = next;
    }

    public String toString() {
	String r = "[ ";
	for (Env env = this; env != null; env = env.next) 
	    r +=  env.key + " ";
	r += "]";
	return r;
    }

    /** Find key in environment.
     *  @param env environment to search
     *  @param targetKey key to look for
     *	@return link containing key or null if not found.
    */
    static Env find(Env env,String targetKey) {
	for (; env != null; env = env.next)
	    if (env.key.equals(targetKey))
	        return env;
	return null;
    }

}



/** -------------------------- Parsing -------------------------- */

/** Recursive descent parser for E language.
*/
class Parser {

    /** Lexer instance we're using. */
    static Lexer lexer;
    
    /** Current token. */
    static byte tok;

    /** Create new Parser using specified input source.
     *	@param rd Reader giving input source.
     */
    Parser(Reader rd) {
	this.lexer = new Lexer(rd);
    }

    /** Throw an error when something unexpected is encountered.
     *	@param expected description of expected token(s).
     *  @throws ParseError always
    */
    static void expectedError(String expected) throws ParseError {
	throw new ParseError("Expected: " + expected + 
			     "  Found: " + Token.stringOf[tok],
			     lexer.lineno);
    }

    /** Accept the current token unconditionally.
     *	@throws ParseError if lexing error occurs.
     */
    static void acceptIt() throws ParseError {
	tok = lexer.lex();
    }

    /** Accept the current token iff it is of the expected kind. 
     *  @param expectedKind token kind expected.
     *  @throws ParseError if token is not of expected kind, 
     *          or if lexing error occurs.
    */
    static void accept(byte expectedKind) throws ParseError {
	if (tok == expectedKind) 
	    tok = lexer.lex();
	else
	    expectedError(Token.stringOf[expectedKind]);
    }

    /** Parse an E program.
     *  @return AST for parsed program.
     *  @throws ParseError if any error occurs while parsing or lexing.
    */
    static Prog parse() throws ParseError {
	tok = lexer.lex();
	Exp e = parseExp();
	accept(Token.EOF);
	return new Prog(e);
    }

    /** Parse an identifier.
     *  @return parsed identifier
     *  @throws ParseError if identifier not found, or if lexing error 
     *               occurs.
     */ 
    static String parseId() throws ParseError {
	if (tok == Token.ID) {
	    String i = (String) lexer.attribute;
	    acceptIt();
	    return i;
	} else {
	    expectedError(Token.stringOf[Token.ID]);
	    return "";  // to keep javac compiler happy
	}
    }

    /** Parse an expression.
     *  @return AST for parsed Exp
     *  @throws ParseError if expression not found, or if lexing error 
     *               occurs.
     */
    static Exp parseExp() throws ParseError {
	if (tok == Token.SET) {
	    acceptIt();
	    String i = parseId();
	    accept(Token.EQ);
	    Exp e = parseExp();
	    return new SetExp(i,e);
	} else {
	    Exp el = parseTerm();
	    if (tok == Token.PLUS) {
		acceptIt();
		Exp er = parseTerm();
		return new PlusExp(el,er);
	    } else
		return el;
	}
    }

    /** Parse a term (into an AST expression).
     *  @return AST for parsed term
     *  @throws ParseError if term not found, or if lexing error 
     *               occurs.
     */
    static Exp parseTerm() throws ParseError {
	if (tok == Token.NUM) {
	    int n = ((Integer) lexer.attribute).intValue();
	    acceptIt();
	    return new ConstExp(n);
	} else if (tok == Token.LBRA) {
	    acceptIt();
	    ArrayList is = new ArrayList();
	    if (tok == Token.VAR) {
		acceptIt();
		String id = parseId();
		is.add(id);
		while (tok != Token.SEMI) {
		    id = parseId();
		    is.add(id);
		}
		acceptIt();  // read past SEMI
	    }
	    ArrayList es = new ArrayList();
	    Exp e = parseExp();
	    es.add(e);
	    while (tok != Token.RBRA) {
		accept(Token.SEMI);
		e = parseExp();
		es.add(e);
	    }
	    acceptIt();  // read past RBRA
	    return new BlockExp(is,es);
	} else if (tok == Token.LPAR) {
	    acceptIt();
	    Exp e = parseExp();
	    accept(Token.RPAR);
	    return e;
	} else {
	    String i = parseId();
	    return new VarExp(i);
	}
    }
}	    


/** Definitions of token kinds and associated data.
*/
abstract class Token {

    /** Token kinds.
     *  Note: This list must match <TT>stringOf</TT> array.
     *        Keywords and symbols must be kept together, and
     *        their order must match corresponding
     *        <TT>keywords</TT> and <TT>symbols</TT> arrays.
     */
    private static byte code = 0;  // Make Java do the numbering for us.
    final static byte 
        firstKeyword = code, 
	VAR = code++,    // 'var' keyword
	SET = code++,    // 'set' keyword
	ID = code++,     // identifier
	NUM = code++,    // numeric literal
	firstSymbol = code,
	EQ = code++,     // =
	LBRA = code++,   // {
	RBRA = code++,   // }
	LPAR = code++,   // (
	RPAR = code++,   // )
	PLUS = code++,   // +
	SEMI = code++,   // ;
	EOF = code++;    // end of file
  
    /** Printable representations of token kinds.  */
    final static String[] stringOf = {
	"'var'", "'set'", 
	"identifier", "number", 
	"'='", "'{'", "'}'", "'('", "')'", "'+'", "';'",
	"end of file" };

    /** Keyword patterns. */
    final static String[] keywords = {
	"var","set"
    };

    /** Symbol patterns. */ 
    final static char[] symbols = {
	'=','{','}','(',')','+',';'
    };
}



/** Lexical analyzer for E language.
 *  This class is intended to be instantiated just once,
 *  for a particular input source.
 */
class Lexer {
    /** Current line number. */
    int lineno;

    /** Attribute for most recently returned token (or null if none). */
    Object attribute = null;

    /** Source from which we're reading. */
    private PushbackReader pbrd;

    /** Newline character, determined in a portable way. */
    static final char newline = 
	System.getProperty("line.separator").charAt(0);  

    /** Create new Lexer using specified input source. 
     *  @param rd Reader giving input source.
     */
    Lexer(Reader rd) {
	pbrd = new PushbackReader(rd);
	lineno = 1;
    }

    /** Return kind code for next token, reading from input source 
     *  as needed. */
    byte lex() throws ParseError {  
	try {
	    attribute = null;
	    while (true) {
		char c = (char) pbrd.read();
		if (c == (char) (-1))
		    return Token.EOF;
		else if (c == newline) {
		    lineno++;
		    continue;
		} else if (Character.isWhitespace(c))
		    continue;
		else if (Character.isLetter(c)) {
		    StringBuffer lexeme = new StringBuffer();
		    lexeme.append(c);
		    c = (char) pbrd.read();
		    while (Character.isLetter(c)) {
			lexeme.append(c);
			c = (char) pbrd.read();
		    };
		    pbrd.unread(c);
		    for (byte i = 0; i < Token.keywords.length; i++)
			if (Token.keywords[i].contentEquals(lexeme)) 
			    return (byte) (Token.firstKeyword + i);
		    attribute = lexeme.toString();
		    return Token.ID;
		} else if (Character.isDigit(c)) {
		    StringBuffer lexeme = new StringBuffer();
		    lexeme.append(c);
		    c = (char) pbrd.read();
		    while (Character.isDigit(c)) {
			lexeme.append(c);
			c = (char) pbrd.read();
		    }
		    pbrd.unread(c);
		    attribute = Integer.valueOf(lexeme.toString());
		    return Token.NUM;
		} else {
		    for (byte i = 0; i < Token.symbols.length; i++)
			if (Token.symbols[i] == c)
			    return (byte) (Token.firstSymbol + i);
		    throw new ParseError
			("Invalid character \'"  + c + "\'", lineno);
		}
	    }
	} catch (IOException exn) { // from read or unread
	    throw 
               new ParseError("I/O error: " + exn.getMessage(),lineno);
	}
    }
}

/** Exception describing parse errors. */
class ParseError extends Exception {
    ParseError (String message, int lineno) {
	super("Line " + lineno + ": " + message);
    }
}



/** ------------------------  Driver ------------------------------ */


/** Main driver for reading, checking, and interpreting E programs. */
class Main {
    public static void main (String argv[]) {
	try {
	    Reader rd = new FileReader(argv[0]); 
	    Prog prog = new Parser(rd).parse(); 
	    boolean ok = prog.check();
	    // uncomment this in your solution 
	    // if (ok) {
	    //     int result = prog.interp();
	    //     System.out.println (result);
	    // }
	} catch (FileNotFoundException fnfexn) {
	    System.err.println ("File not found: " + argv[0]);
	} catch (ParseError pexn) {
	    System.err.println ("Parse error: " + pexn.getMessage());
	}
    }
}
