import java.io.*; 
import java.util.*;
  
// ----------------------------------------------------------------------------
// Environments
// ----------------------------------------------------------------------------
// Environments map string keys to data objects.
// (The type of data is arbitrary, but is fixed for each environment.)
// Once established, the mapping for each key is immutable.
// 
class Env<D> {
  // Environments are represented as linked lists.
  // Each link is one instance of an Env class.
  static private class Link<D> {
    String key;
    D datum;
    Link<D> next; 
    Link(String key, D datum, Link<D> next) {
      this.key = key; this.datum = datum; this.next = next;
    }
  };

  private Link<D> contents = null;

  private Env (Link<D> c) {
    contents = c;
  }

  static <D> Env<D> empty () {
    return new Env<D>(null);
  }

  Env<D> extend (String key, D datum) {
    return new Env<D>(new Link<D>(key,datum,contents));
  }

  // Failed lookups throw the NotFound exception.
  static class NotFound extends Exception {
    String key;
    NotFound(String key) {
      this.key = key;
    }
  };

  private Link<D> find (String k) throws NotFound {
    Link<D> c = contents;
    while (c != null) {
      if (c.key.equals(k))
	return c;
      else
	c = c.next;
    }
    throw new NotFound(k);
  }

  D lookup(String k) throws NotFound {
    Link<D> c = find(k);
    return c.datum;
  }

}


// ----------------------------------------------------------------------------
// Exceptions
// ----------------------------------------------------------------------------
class CheckedRTError extends Exception { 
  CheckedRTError(String err) {
    super(err);
  }
}

// ----------------------------------------------------------------------------
// Values 
// ----------------------------------------------------------------------------
abstract class Value {
  abstract int intValue() throws CheckedRTError;
  abstract PairValue pairValue() throws CheckedRTError;
  abstract FnValue fnValue() throws CheckedRTError;
}

class IntValue extends Value {
  int i;
  IntValue(int i) {
    this.i = i;
  }
  int intValue() {
    return i;
  }
  PairValue pairValue() throws CheckedRTError {
    throw new CheckedRTError("Integer found where pair expected");
  }
  FnValue fnValue() throws CheckedRTError {
    throw new CheckedRTError("Integer found where function expected");
  }
  public String toString() {
    return Integer.toString(i);
  }
}

class PairValue extends Value {
  Value left;
  Value right;
  PairValue(Value left, Value right) {
    this.left = left; this.right = right;
  }
  int intValue() throws CheckedRTError {
    throw new CheckedRTError("Pair found where integer expected");
  }
  FnValue fnValue() throws CheckedRTError {
    throw new CheckedRTError("Pair found where function expected");
  }
  PairValue pairValue() {
    return this;
  }
  public String toString() {
    return "(" + left + "." + right + ")";
  }
}

class FnValue extends Value {
  String param;
  Exp body;
  Env<Value> env;
  FnValue(String param, Exp body, Env<Value> env) {
    this.param = param; this.body = body; this.env = env; 
  }
  int intValue() throws CheckedRTError {
    throw new CheckedRTError("Function found where integer expected");
  }
  PairValue pairValue() throws CheckedRTError {
    throw new CheckedRTError("Function found where pair expected");
  }
  FnValue fnValue() {
    return this;
  }
  public String toString() {
    return "[" + param + "," + body + "," + env + "]";
  }
}

// ----------------------------------------------------------------------------
// Abstract Syntax, Evaluation, Printing 
// ----------------------------------------------------------------------------
//

class Prog {
  Exp e;
  Prog (Exp e) {
    this.e = e;
  }
  Value eval () throws CheckedRTError {
    return e.eval(Env.<Value>empty());
  }
  public String toString() {
    return e.toString();
  }
}

// Each kind of expression is a distinct subclass of Exp.
//
abstract class Exp {
  abstract Value eval(Env<Value> env) throws CheckedRTError;
}

class VarExp extends Exp {
  String x;
  VarExp (String x) {
    this.x = x;
  }
  Value eval(Env env) throws CheckedRTError {
    Value result = null;
    try {
      result = (Value) env.lookup(x);
    } catch (Env.NotFound nf) {
      throw new CheckedRTError ("Undefined variable:" + x);
    }
    return result;
  }
  public String toString() {
    return x;
  }
}


class IntExp extends Exp {
  int i;
  IntExp (int i) { 
    this.i = i;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    return new IntValue(i);
  }
  public String toString() {
    return Integer.toString(i);
  }
}

class IfExp extends Exp {
  Exp e1;
  Exp e2;
  Exp e3;
  IfExp (Exp e1, Exp e2, Exp e3) {
    this.e1 = e1; this.e2 = e2; this.e3 = e3;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    if (e1.eval(env).intValue() != 0)
      return e2.eval(env);
    else
      return e3.eval(env);
  }
  public String toString () {
    return "(if " + e1 + " " + e2 + " " + e3 + ")";
  }
}

class LetExp extends Exp {
  String x0;
  Exp e0;
  Exp e;
  LetExp (String x0, Exp e0, Exp e) {
    this.x0 = x0; this.e0 = e0; this.e = e;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    Value v0 = e0.eval(env);
    return e.eval(env.extend(x0,v0));
  }
  public String toString () {
    return "(let " + x0 + " " + e0 + " " + e + ")";
  }
}

class LetfunExp extends Exp {
  String f;
  String x0;
  Exp e0;
  Exp e;
  LetfunExp(String f, String x0, Exp e0, Exp e) {
    this.f = f; this.x0 = x0; this.e0 = e0; this.e = e;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    FnValue v = new FnValue(x0,e0,env);
    env = env.extend(f,v);
    v.env = env;
    return e.eval(env);
  }
  public String toString () {
    return "(letfun " + f + " " + x0 + " " + e0 + " " + e + ")";
  }
}

class AppExp extends Exp {
  Exp fun;
  Exp arg;
  AppExp (Exp fun, Exp arg) {
    this.fun = fun; this.arg = arg;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    FnValue fnv = fun.eval(env).fnValue();
    Value av = arg.eval(env);
    return fnv.body.eval(fnv.env.extend(fnv.param,av));
  }
  public String toString () {
    return "(@ " + fun + " " + arg + ")"; 
  }
}

class AddExp extends Exp {
  Exp e1;
  Exp e2;
  AddExp(Exp e1,Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    int i1 = e1.eval(env).intValue();
    int i2 = e2.eval(env).intValue();
    return new IntValue(i1+i2);
  }
  public String toString() {
    return "(+ " + e1 + " " + e2 + ")";
  }
}

class SubExp extends Exp {
  Exp e1;
  Exp e2;
  SubExp(Exp e1,Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    int i1 = e1.eval(env).intValue();
    int i2 = e2.eval(env).intValue();
    return new IntValue(i1-i2);
  }
  public String toString() {
    return "(- " + e1 + " " + e2 + ")";
  }
}

class MulExp extends Exp {
  Exp e1;
  Exp e2;
  MulExp(Exp e1,Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    int i1 = e1.eval(env).intValue();
    int i2 = e2.eval(env).intValue();
    return new IntValue(i1*i2);
  }
  public String toString() {
    return "(* " + e1 + " " + e2 + ")";
  }
}

class DivExp extends Exp {
  Exp e1;
  Exp e2;
  DivExp(Exp e1,Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    int i1 = e1.eval(env).intValue();
    int i2 = e2.eval(env).intValue();
    return new IntValue(i1/i2);
  }
  public String toString() {
    return "(/ " + e1 + " " + e2 + ")";
  }
}

class LeqExp extends Exp {
  Exp e1;
  Exp e2;
  LeqExp(Exp e1,Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    int i1 = e1.eval(env).intValue();
    int i2 = e2.eval(env).intValue();
    return new IntValue((i1 <= i2) ? 1 : 0);
  }
  public String toString() {
    return "(<= " + e1 + " " + e2 + ")";
  }
}

class PairExp extends Exp {
  Exp e1;
  Exp e2;
  PairExp (Exp e1, Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    Value v1 = e1.eval(env);
    Value v2 = e2.eval(env);
    return new PairValue(v1,v2);
  }
  public String toString() {
    return "(pair " + e1 + " " + e2 + ")";
  }
}

class FstExp extends Exp {
  Exp e;
  FstExp (Exp e) {
    this.e = e;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    return e.eval(env).pairValue().left;
  }
  public String toString() {
    return "(fst " + e + ")";
  }
}

class SndExp extends Exp {
  Exp e;
  SndExp (Exp e) {
    this.e = e;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    return e.eval(env).pairValue().right;
  }
  public String toString() {
    return "(snd " + e + ")";
  }
}

class IspairExp extends Exp {
  Exp e;
  IspairExp (Exp e) {
    this.e = e;
  }
  Value eval(Env<Value> env) throws CheckedRTError {
    return new IntValue(e.eval(env) instanceof PairValue ? 1 : 0); 
  }
  public String toString() {
    return "(ispair " + e + ")";
  }
}



// ----------------------------------------------------------------------------
// Parsing
// ----------------------------------------------------------------------------
//
class Parser {

  // Failed parses throw the ParseFailed exception.
  static class ParseFailed extends Exception {
    ParseFailed(String w,int lineno) {
      super("Line " + lineno + ": " + w);
    }
  }

  // Lexical analysis
    
  // Token objects
  static private abstract class Token {
    int lineno;
  }
  static private class EofToken extends Token {
    EofToken(int l) {
      lineno = l;
    }
  }
  static private class LpToken extends Token {
    LpToken(int l) {
      lineno = l;
    }
  }
  static private class RpToken extends Token {
    RpToken(int l) {
      lineno = l;
    }
  }
  static private class NumToken extends Token {
    int num;
    NumToken(int n,int l) {
      num = n;
      lineno = l;
    }
  }
  static private class OperToken extends Token {
    String str;
    OperToken(String s, int l) {
      str = s;
      lineno = l;
    }
    static boolean isOperChar(char c) {
      return (c == '+' || c == '-' || c == '*' || c == '/' 
	      || c == ':' || c == '=' || c == '<' || c == '@');
    }
  }

  static private class IdToken extends Token {
    String str;
    IdToken(String s, int l) {
      str = s;
      lineno = l;
    }
  }

  // Lexical analysis
  static private class Tokenizer {
    PushbackInputStream in;
    int lineno;
    
    Token putback;

    Tokenizer(InputStream inarg) {
      in = new PushbackInputStream(inarg);
      lineno = 1;
      putback = null;
    }

    static private final char EOF = (char) 0;

    private char readc() {
      int c;
      try {
	c = in.read();
      } catch (IOException exn) {
	throw new Error("IOException:" + exn.getMessage());
      }
      if (c == -1)
	return EOF;
      else 
	return (char)c;
    }
      
    private void unreadc(char c) {
      try {
	in.unread((int) c);
      } catch (IOException exn) {
	throw new Error("IOException:" + exn.getMessage());
      }
    }

    void putback(Token tok) {
      putback = tok;
    }

    Token next() throws ParseFailed {
      Token result = putback;
      putback = null;
      int comment_level = 0;
      while (result == null) {
	char c = readc();
	if (c == EOF) {
	  if (comment_level > 0)
	    throw new ParseFailed ("Unmatched open comment",lineno);
	  result = new EofToken(lineno);
	} else if (c == '\n') {
	  lineno++;
	  continue;
	} else if (c == '{') {
	  comment_level++;
	  continue;
	} else if (c == '}') {
	  if (comment_level > 0)
	    comment_level--;
	  else 
	    throw new ParseFailed ("Unmatched close comment",lineno);
	  continue;
	} else if (comment_level > 0)
	  continue;
	else if (Character.isWhitespace(c)) 
	  continue;
	else if (c == '(')
	  result = new LpToken(lineno);
	else if (c == ')')
	  result = new RpToken(lineno);
	else if (Character.isDigit(c)) {
	  String s = "" + c;
	  while (Character.isDigit((c = readc())))
	    s = s + (char)c;
	  unreadc(c);
	  try {
	    int v = Integer.parseInt(s);
	    result = new NumToken(v,lineno);
	  } catch (NumberFormatException exn) {
	    throw new ParseFailed ("Invalid number: " + s,lineno);
	  }
	} else if (Character.isLetter(c)) {
	  String s = "" + c;
	  while (Character.isLetter(c = readc()) || Character.isDigit(c))
	    s = s + c;
	  unreadc(c);
	  result = new IdToken(s,lineno);
	} else if (OperToken.isOperChar(c)) {
	  String s = "" + c;
	  while (OperToken.isOperChar(c = readc()))
	    s = s + c;
	  unreadc(c);
	  result = new OperToken(s,lineno);
	} else // invalid character
	  throw new ParseFailed ("Invalid character: " + c,lineno);
      }
      return result;
    }
  }

  // The parser itself.
    
  // Parse (
  static private void parseLp(Tokenizer tokens) throws ParseFailed {
    Token tok = tokens.next();
    if (tok instanceof LpToken)
      return;
    else
      throw new ParseFailed ("Missing (",tok.lineno);
  }

  // Parse )
  static private void parseRp(Tokenizer tokens) throws ParseFailed {
    Token tok = tokens.next();
    if (tok instanceof RpToken)
      return;
    else
      throw new ParseFailed ("Missing )",tok.lineno);
  }


  // Parse variable or function name.
  static private String parseName(Tokenizer tokens) throws ParseFailed {
    Token tok = tokens.next();
    if (tok instanceof IdToken) 
      return ((IdToken)tok).str;
    else
      throw new ParseFailed ("Missing name",tok.lineno);
  }

  // Parse expression.
  static private Exp parseExp(Tokenizer tokens) throws ParseFailed { 
    Exp result = null;
    Token tok = tokens.next();
    if (tok instanceof NumToken) 
      result = new IntExp(((NumToken)tok).num);
    else if (tok instanceof IdToken)
      result = new VarExp(((IdToken)tok).str);
    else if (tok instanceof LpToken) {
      tok = tokens.next();
      if (tok instanceof IdToken) {
	String keywd = ((IdToken)tok).str;
	if (keywd.equals("if")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  Exp e3 = parseExp(tokens);
	  result = new IfExp(e1,e2,e3);
	} else if (keywd.equals("let")) {
	  String x0 = parseName(tokens);
	  Exp e0 = parseExp(tokens);
	  Exp e = parseExp(tokens);
	  result = new LetExp(x0,e0,e);
	} else if (keywd.equals("letfun")) {
	  String f = parseName(tokens);
	  String x0 = parseName(tokens);
	  Exp e0 = parseExp(tokens);
	  Exp e = parseExp(tokens);
	  result = new LetfunExp(f,x0,e0,e);
	} else if (keywd.equals("pair")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new PairExp(e1,e2);
	} else if (keywd.equals("fst")) {
	  Exp e = parseExp(tokens);
	  result = new FstExp(e);
	} else if (keywd.equals("snd")) {
	  Exp e = parseExp(tokens);
	  result = new SndExp(e);
	} else if (keywd.equals("ispair")) {
	  Exp e = parseExp(tokens);
	  result = new IspairExp(e);
	} else
	  throw new ParseFailed ("Missing or invalid expression",tok.lineno);
      } else if (tok instanceof OperToken) {
	String oper = ((OperToken)tok).str;
	if (oper.equals("@")) {
	  Exp fun = parseExp(tokens);
	  Exp arg = parseExp(tokens);
	  result = new AppExp(fun,arg);
	} else if (oper.equals("+")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new AddExp(e1,e2);
	} else if (oper.equals("-")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new SubExp(e1,e2);
	} else if (oper.equals("*")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new MulExp(e1,e2);
	} else if (oper.equals("/")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new DivExp(e1,e2);
	} else if (oper.equals("<=")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new LeqExp(e1,e2);
	} else
	  throw new ParseFailed ("Missing or invalid expression",tok.lineno);
      } else
	throw new ParseFailed ("Missing or invalid expression",tok.lineno);
      parseRp(tokens);
    } else
      throw new ParseFailed ("Missing or invalid expression",tok.lineno);
    return result;
  }      

  // Parse program.
  static Prog parse(InputStream is) throws ParseFailed {
    Tokenizer tokens = new Tokenizer(is);
    Exp exp = parseExp(tokens);
    Token tok = tokens.next();
    if (!(tok instanceof EofToken))
      throw new ParseFailed ("Extraneous characters at end of program",tok.lineno);
    return new Prog(exp);
  }
}

// ----------------------------------------------------------------------------
// Top-level class and main function.
// ----------------------------------------------------------------------------

class Interp {

  public static void main (String args[]) throws IOException {
    FileInputStream is = new FileInputStream(args[0]);
    try {
      Prog p = Parser.parse(is);
      System.out.println("Program:\n" + p); 
      Value result = p.eval();
      System.out.println ("Evaluates to: " + result);
    } catch (Parser.ParseFailed e) {
      System.out.println ("Parse failed: " + e.getMessage());
    } catch (CheckedRTError e) {
      System.out.println ("Checked Runtime Error: " + e.getMessage());
    }
    is.close();
  }

}
    
