import java.io.*; 
import java.util.*;
  
// ----------------------------------------------------------------------------
// Store
// ----------------------------------------------------------------------------
// The store consists of mutable Locations containing Values.
// Locations are allocated as objects on the Java heap, allowing
// the interpreter to update the store by directly updating the 
// value field, and passing responsibility for garbage-collecting
// dead locations to Java's garbage collector. 
//
class Location {
  Value value;
  Location (Value value) {
    value.assignInto(this);
  }
}

// ----------------------------------------------------------------------------
// 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 void assignInto(Location target);
}

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");
  }
  void assignInto (Location loc) {
    loc.value = this;
  }
  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");
  }
  PairValue pairValue() {
    return this;
  }
  void assignInto (Location loc) {
    loc.value = new PairValue(this.left,this.right);
  }
  public String toString() {
    return "(" + left + "." + right + ")";
  }
}

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

// Package together the three environments that define evaluation context.
class Envs {
  Env<FunDef> funs; // functions
  Env<Location> globals; // global variables
  Env<Location> vars; // formal parameters
  Envs(Env<FunDef> funs, Env<Location> globals, Env<Location> vars) {
    this.funs = funs; this.globals = globals; this.vars = vars;
  }
  static Envs empty() {
    return new Envs(Env.<FunDef>empty(),Env.<Location>empty(),Env.<Location>empty());
  }    
}

class Prog {
  Def[] ds;
  Exp e;
  Prog (Def[] ds, Exp e) {
    this.ds = ds; this.e = e;
  }
  Prog (List<Def> ds, Exp e) {  //  more useful for parsing
    this(ds.toArray(new Def[0]), e);
  }
  Value eval () throws CheckedRTError {
    Envs envs = Envs.empty();
    for (int i = 0; i < ds.length; i++) 
      envs = ds[i].elab(envs);
    return e.eval(envs);
  }
  public String toString() {
    String r = "(\n";
    for (int i = 0; i < ds.length; i++)
      r += " " + ds[i] + "\n";
    r += ")\n" + e;
    return r;
  }
}
    
abstract class Def {
  abstract Envs elab(Envs envs) throws CheckedRTError;
}  

class GlobalDef extends Def {
  String x;
  Exp e;
  GlobalDef (String x, Exp e) {
    this.x = x; this.e = e;
  }
  Envs elab(Envs envs) throws CheckedRTError {
    Env<Location> globals = envs.globals.extend(x,new Location(e.eval(envs)));
    return new Envs(envs.funs,globals,envs.vars);
  }
  public String toString() {
    return "(global " + x.toString() + " " + e.toString() + ")";
  }
}

class FunDef extends Def {
  String f;
  String[] params;
  Exp e;
  FunDef (String f, String[] params, Exp e) {
    this.f = f; this.params = params; this.e = e;
  }
  FunDef (String f, List<String> params, Exp e) {  // more useful for parsing
    this(f,params.toArray(new String[0]),e);
  }
  Envs elab(Envs envs) throws CheckedRTError {
    return new Envs(envs.funs.extend(f,this),envs.globals,envs.vars);
  }
  public String toString() {
    String r =  "(fun " + f + " (";
    if (params.length > 0) {
      r += params[0];
      for (int i = 1; i < params.length; i++)
	r += " " +  params[i];
    }
    r += ") " + e + ")";
    return r;
  }
}

// Each kind of expression is a distinct subclass of Exp.
//
abstract class Exp {
  abstract Value eval(Envs envs) throws CheckedRTError;
}

class VarExp extends Exp {
  String x;
  VarExp (String x) {
    this.x = x;
  }
  Value eval(Envs envs) throws CheckedRTError {
    Location l = null;
    try {
      l = envs.vars.lookup(x);
    } catch (Env.NotFound nf) {
      try {
	l = envs.globals.lookup(x);
      } catch (Env.NotFound nf2) {
	throw new CheckedRTError ("Undefined variable:" + x);
      }
    }
    return l.value;
  }
  public String toString() {
    return x;
  }
}


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

class AsgnExp extends Exp {
  String x;
  Exp e;
  AsgnExp(String x, Exp e) {
    this.x = x; this.e = e;
  }
  Value eval(Envs envs) throws CheckedRTError {
    Value v = e.eval(envs);
    Location l = null;
    try {
      l = envs.vars.lookup(x); 
    } catch (Env.NotFound nf) {
      try {
	l = envs.globals.lookup(x);
      } catch (Env.NotFound nf2) {
	throw new CheckedRTError ("Undefined variable:" + x);
      }
    };
    v.assignInto(l);
    return l.value;
  }
  public String toString () {
    return "(:= " + x + " " + e + ")";
  }
}

class WhileExp extends Exp {
  Exp e1;
  Exp e2;
  WhileExp (Exp e1, Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Envs envs) throws CheckedRTError {
    while (e1.eval(envs).intValue() != 0)
      e2.eval(envs);
    return new IntValue(0);
  }
  public String toString () {
    return "(while " + e1 + " " + e2 + ")";
  }
}

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(Envs envs) throws CheckedRTError {
    if (e1.eval(envs).intValue() != 0)
      return e2.eval(envs);
    else
      return e3.eval(envs);
  }
  public String toString () {
    return "(if " + e1 + " " + e2 + " " + e3 + ")";
  }
}

class WriteExp extends Exp {
  Exp e;
  WriteExp (Exp e) {
    this.e = e;
  }
  Value eval(Envs envs) throws CheckedRTError {
    Value v = e.eval(envs);
    System.out.println(v);
    return v;
  }
  public String toString () {
    return "(write " + e + ")";
  }
}

class LocalExp extends Exp {
  String[] xs;
  Exp[] es;
  Exp e;
  LocalExp (String[] xs, Exp[] es, Exp e) {
    this.xs = xs; this.es = es; this.e = e;
  }
  LocalExp (List<String> xs, List<Exp> es, Exp e) {   // more useful for parsing
    this(xs.toArray(new String[0]),
	 es.toArray(new Exp[0]), 
	 e);
  }
  Value eval(Envs envs) throws CheckedRTError {
    Env<Location> vars = envs.vars;
    for ( int i = 0; i < xs.length; i++ )
      vars = vars.extend(xs[i], new Location(es[i].eval(envs)));  
    return e.eval(new Envs(envs.funs,envs.globals,vars));
  }
  public String toString () {
    String r = "(local (";
    if (xs.length > 0) {
      r += xs[0] + " " + es[0];
      for (int i = 1; i < xs.length; i++)
	r += " " + xs[i] + " " + es[i];
    }
    r += ") " + e + ")";
    return r;
  }
}

class BlockExp extends Exp {
  Exp[] es;
  BlockExp (Exp[] es) {  
    this.es = es;
  }
  BlockExp (List<Exp> es) {  
    this(es.toArray(new Exp[0]));
  }
  Value eval(Envs envs) throws CheckedRTError {
    Value v = new IntValue(0);
    for (int i = 0; i < es.length; i++) 
      v = es[i].eval(envs);
    return v;
  }
  public String toString () {
    String r = "(block";
    for (int i = 0; i < es.length; i++) 
	r += " " + es[i];
    r += ")";
    return r;
  }
}

class AppExp extends Exp {
  String f;
  Exp[] args;
  AppExp (String f, Exp[] args) {
    this.f = f; this.args = args;
  }
  AppExp (String f, List<Exp> args) { // more useful for parsing
    this(f, args.toArray(new Exp[0])); 
  }
  Value eval(Envs envs) throws CheckedRTError {
    FunDef fdef = null;
    try {
      fdef = envs.funs.lookup(f);
    } catch (Env.NotFound nf) {
      throw new CheckedRTError("Function undefined:" + f);
    }
    if (fdef.params.length != args.length)
      throw new CheckedRTError("Wrong number of arguments to function:" + f);
    Location[] ls = new Location[args.length];
    for (int i = 0; i < args.length; i++)
      ls[i] = new Location(args[i].eval(envs));
    Env<Location> vars = Env.empty();
    for (int i = 0; i < args.length; i++)
      vars = vars.extend(fdef.params[i],ls[i]);
    return fdef.e.eval(new Envs(envs.funs,envs.globals,vars));
  }
  public String toString () {
    String r = "(@ " + f;
    for (int i = 0; i < args.length; i++)
      r += " " + args[i];
    r += ")";
    return r;
  }
}

class AddExp extends Exp {
  Exp e1;
  Exp e2;
  AddExp(Exp e1,Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Envs envs) throws CheckedRTError {
    int i1 = e1.eval(envs).intValue();
    int i2 = e2.eval(envs).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(Envs envs) throws CheckedRTError {
    int i1 = e1.eval(envs).intValue();
    int i2 = e2.eval(envs).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(Envs envs) throws CheckedRTError {
    int i1 = e1.eval(envs).intValue();
    int i2 = e2.eval(envs).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(Envs envs) throws CheckedRTError {
    int i1 = e1.eval(envs).intValue();
    int i2 = e2.eval(envs).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(Envs envs) throws CheckedRTError {
    int i1 = e1.eval(envs).intValue();
    int i2 = e2.eval(envs).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(Envs envs) throws CheckedRTError {
    Value v1 = e1.eval(envs);
    Value v2 = e2.eval(envs);
    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(Envs envs) throws CheckedRTError {
    return e.eval(envs).pairValue().left;
  }
  public String toString() {
    return "(fst " + e + ")";
  }
}

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

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

class SetfstExp extends Exp {
  Exp e1;
  Exp e2;
  SetfstExp (Exp e1, Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Envs envs) throws CheckedRTError {
    PairValue v1 =  e1.eval(envs).pairValue();
    Value v2 = e2.eval(envs);
    v1.left = v2;
    return v1;
  }
  public String toString() {
    return "(setfst " + e1 + " " + e2 + ")";
  }
}

class SetsndExp extends Exp {
  Exp e1;
  Exp e2;
  SetsndExp (Exp e1, Exp e2) {
    this.e1 = e1; this.e2 = e2;
  }
  Value eval(Envs envs) throws CheckedRTError {
    PairValue v1 =  e1.eval(envs).pairValue();
    Value v2 = e2.eval(envs);
    v1.right = v2;
    return v1;
  }
  public String toString() {
    return "(setsnd " + e1 + " " + e2 + ")";
  }
}


// ----------------------------------------------------------------------------
// 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("while")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new WhileExp(e1,e2);
	} else 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("write")) {
	  Exp e = parseExp(tokens);
	  result = new WriteExp(e);
	} else if (keywd.equals("local")) {
	  List<String> xs = new ArrayList<String>();
	  List<Exp> es = new ArrayList<Exp>();
	  parseLp(tokens);
	  tok = tokens.next();
	  while (!(tok instanceof RpToken)) {
	    tokens.putback(tok);
	    xs.add(parseName(tokens));
	    es.add(parseExp(tokens));
	    tok = tokens.next();
	  }
	  Exp e = parseExp(tokens);
	  result = new LocalExp(xs,es,e);
	} else if (keywd.equals("block")) {
	  List<Exp> es = new ArrayList<Exp>();
	  tok = tokens.next();
	  while (!(tok instanceof RpToken)) {
	    tokens.putback(tok);
	    es.add(parseExp(tokens));
	    tok = tokens.next();
	  }
	  tokens.putback(tok);
	  result = new BlockExp(es);
	} 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 if (keywd.equals("setfst")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new SetfstExp(e1,e2);
	} else if (keywd.equals("setsnd")) {
	  Exp e1 = parseExp(tokens);
	  Exp e2 = parseExp(tokens);
	  result = new SetsndExp(e1,e2);
	} else
	  throw new ParseFailed ("Missing or invalid expression",tok.lineno);
      } else if (tok instanceof OperToken) {
	String oper = ((OperToken)tok).str;
	if (oper.equals(":=")) {
	  String x = parseName(tokens);
	  Exp e = parseExp(tokens);
	  result = new AsgnExp(x,e);
	} else if (oper.equals("@")) {
	  String f = parseName(tokens);
	  List<Exp> es = new ArrayList<Exp>();
	  tok = tokens.next();
	  while (!(tok instanceof RpToken)) {
	    tokens.putback(tok);
	    es.add(parseExp(tokens));
	    tok = tokens.next();
	  }
	  tokens.putback(tok);
	  result = new AppExp(f,es);
	} 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 def.
  static Def parseDef(Tokenizer tokens) throws ParseFailed {
    Def result = null;
    parseLp(tokens);
    Token tok = tokens.next();
    if (tok instanceof IdToken) {
      String keywd = ((IdToken)tok).str;
      if (keywd.equals("global")) {
	String x = parseName(tokens);
	Exp e = parseExp(tokens);
	result = new GlobalDef(x,e);
      } else if (keywd.equals("fun")) {
	String f = parseName(tokens);
	List<String> args = new ArrayList<String>();
	parseLp(tokens);
	tok = tokens.next();
	while (!(tok instanceof RpToken)) {
	  tokens.putback(tok);
	  args.add(parseName(tokens));
	  tok = tokens.next();
	}
	Exp e = parseExp(tokens);
	result = new FunDef(f,args,e);
      } 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);
    parseLp(tokens);
    List<Def> ds = new ArrayList<Def>();
    Token tok = tokens.next();
    while (!(tok instanceof RpToken)) {
      tokens.putback(tok);
      ds.add(parseDef(tokens));
      tok = tokens.next();
    }
    Exp exp = parseExp(tokens);
    tok = tokens.next();
    if (!(tok instanceof EofToken))
      throw new ParseFailed ("Extraneous characters at end of program",tok.lineno);
    return new Prog(ds,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();
  }

}
    
