package edu.mit.ai.psg.jeva.examples;

import edu.mit.ai.psg.jeva.*;
import edu.mit.ai.psg.strings.*;
import java.util.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Modifier;

/** Useful methods for trace hooks.

    @author CarlManning, caroma@ai.mit.edu<br>
    Copyright (c) 1999 Massachusetts Institute of Technology **/ 
public class TraceMethods {
  /** Print out source expression for node, unindenting leading whitespace. **/
  public static void traceSource(PrintWriter iw, IJevaNode node,
				 Object evalHookdata) {
    String nodeString = Indent.trimNewlines(node.printToString());
    iw.println(Indent.unindent(Indent.leadingWhitespace(nodeString),
			       nodeString));
  }
  /** Print out bindings of free names of node. **/
  public static void traceFreeNames(PrintWriter iw, IJevaNode node,
				    Object evalHookData) {
    Collection freeNames = FreeSimpleNamesCollector.collect(node);
    if (freeNames.size() > 0) {
      IEnv env = ((EvalMethods.EvalHookData)evalHookData).getEnv();
      SortedMap map = new TreeMap();
      for (Iterator iter = freeNames.iterator(); iter.hasNext(); ) {
	// Evaluate name -- evaluating simple name will cause no mutations.
	JNName freeNameNode = (JNName) iter.next();
	try{ map.put(freeNameNode.getSimpleName(),
		     resultString(freeNameNode.eval(env)));}
	catch (Throwable e) {
	  map.put(freeNameNode.printToString(false), "?"); }  //debug
      }
      iw.println(Prettify.string(map.toString()));
    }
  }
  /** Print out source expression with values substituted for immediate 
      subexpressions.  Appropriate only for EvalMethods.PreApplyData. **/
  public static void traceApplications(PrintWriter pw, IJevaNode node,
				       Object preApplyData) {
    if (preApplyData instanceof EvalMethods.PreApplyData) {
      Object[] values = ((EvalMethods.PreApplyData)preApplyData).getValues();
      StringWriter sw = new StringWriter();
      PrintWriter bufw = new PrintWriter(sw);
      int valNum = 0;
      Token tok = node.getBeginToken();
      printSubstitutingExpressions(bufw, node, tok, values, valNum);
      bufw.flush(); sw.flush();
      String nodeString = Indent.trimNewlines(sw.toString());
      pw.println(Indent.unindent(Indent.leadingWhitespace(nodeString),
				 nodeString));
    }
  }
  /** Prints:
      <ul>
      <li><tt><i>value</i></tt> for expression results
      <li><tt><i>(nothing)</i></tt> for normally completed statements
      <li><tt>return <i>value</i></tt> for return abrupt completions
      <li><tt>throw <i>exception</i></tt> for throw abrupt completions
      <li><tt>break <i>label</i></tt> for break abrupt completions
      <li><tt>continue <i>label</i></tt> for continue abrupt completions
      </ul> 
      Appropriate only for EvalMethods.EvalHookPostData **/
  public static void tracePostData(PrintWriter pw, IJevaNode node,
				   Object evalHookPostData) {
    if (evalHookPostData instanceof EvalMethods.ResultData) {
      pw.println(resultString( ((EvalMethods.ResultData)
				evalHookPostData).getResult() ));
    } else if (evalHookPostData instanceof EvalMethods.ResultEnvData) {
      pw.println();
    } else if (evalHookPostData instanceof EvalMethods.ResultStoreData) {
      IStore store =((EvalMethods.ResultStoreData)evalHookPostData).getStore();
      pw.println(resultString(store));
    } else if (evalHookPostData instanceof EvalMethods.AbruptData) {
      AbruptCompletionException thrown =
	((EvalMethods.AbruptData)evalHookPostData).getThrown();
      if (thrown instanceof ReturnException) {
	pw.print("return ");
	PrintWriter iiw = new IndentWriter(pw, "       ");
	pw.println(Stringify.printToString
		    ( ((ReturnException)thrown).getValue() ));
      } else if (thrown instanceof ThrowException) {
	pw.print("throw ");
	PrintWriter iw = new IndentWriter(pw, "      ");
	iw.println(Stringify.printToString
		    ( ((ThrowException)thrown).getThrown() ));
      } else if (thrown instanceof BreakException) {
	pw.println("break "+((BreakException)thrown).getLabel());
      } else if (thrown instanceof ContinueException) {
	pw.println("continue "+((ContinueException)thrown).getLabel());
      } else {
	pw.println("<<unrecogized AbruptCompletion>>");
      }
    } else pw.println();
  }
  
  // ---- free names collector ----
  /** Collect free simple names in node by calling method
      {@link #collect(IJevaNode)} **/
  public static class FreeSimpleNamesCollector extends JevaVisitorBase {
    /** Entry point:  collects simple free names in node
	and returns the sorted set. **/
    public static Collection collect(IJevaNode node) {
      FreeSimpleNamesCollector collector = new FreeSimpleNamesCollector();
      node.jjtAccept(collector, null);
      return collector.freeNamesSet;
    }
    /** List of names defined locally within source expr.  Tails shared. **/
    public static class NameEnv {
      String name;
      NameEnv next;
      NameEnv(String name, NameEnv next) { this.name = name; this.next = next;}
      static boolean isDefinedName(String name, NameEnv env) {
	for (; env != null; env = env.next) 
	  if (env.name.equals(name)) return true;
	return false;
      }
      static String printToString(NameEnv env) {
	StringBuffer buf = new StringBuffer("{");
	for (; env != null; env = env.next)
	  buf.append(env.name + (env.next != null ? ", " : ""));
	return buf+"}";
      }
    }
    // --- field ---
    Set freeNamesSet = new HashSet();
    // --- constructor ---
    FreeSimpleNamesCollector() {}
    // ---- IJevaVisitor  ----
    /** default: explore all children **/
    public Object visit(IJevaNode node, Object data) {
      for (int i = 0; i < node.jjtGetNumChildren(); i++)
	data = node.jnGetChild(i).jjtAccept(this, data);
      return data;
    }
    /** Doesn't explore nested declarations **/
    public Object visit(IDeclarationNode node, Object data) {
      return data;
    }      
    /** Doesn't explore nested declarations **/
    public Object visit(IClassBodyDeclaration node, Object data) {
      return data;
    }      
    /** Doesn't explore type names **/
    public Object visit(JNType node, Object data) {
      return data; 
    }
    /** Collects only simple names not in data nameEnv */
    public Object visit(JNName node, Object data) {
      if (node.jjtGetNumChildren() == 0 &&
	  !(node.jjtGetParent() instanceof JNName)) {
	String simpleName = node.getSimpleName();
	if (! NameEnv.isDefinedName(simpleName, (NameEnv) data)) {
	  freeNamesSet.add(node);
	}
	return data;
      } else return visit( (IJevaNode)node, data);
    }      
    /** Skip class name node in static method calls **/
    public Object visit(JNEMethodInvocationExpression node, Object data) {
      IExpressionNode target = node.getTargetExpression();
      if (target == null ||
	  Modifier.isStatic(node.getMethodSignature().getModifiers()))
	return node.getArgumentExpressions().jjtAccept(this, data);
      else return ((IJevaVisitor)this).visit( (IExpressionNode) node, data);
    }
    /** Adds locally defined name to data nameEnv, and collects initializer**/
    public Object visit(JNSVariableDeclarator node, Object data){
      data = new NameEnv(node.getName(), (NameEnv) data);
      if (node.getInitializer() != null)
	return node.getInitializer().jjtAccept(this, data);
      else return data;
    }
    /** Doesn't pass name definitions out of blocks **/
    public Object visit(JNSBlock node, Object data){
      visit( (IJevaNode) node, data);
      return data; // not data extended by children
    }
  }
  // --- printing values ---
  /** limit on number of elements printed for collection and array results **/
  public static int resultStringLengthLimit = 3;
  /** limit on nesting depth printed for collection and array results **/
  public static int resultStringDepthLimit = 2;
  public static String resultString(Object result) {
    if (result instanceof IStore) {
      String typeName = result.getClass().getName();
      return "<"+typeName.substring(typeName.lastIndexOf('.')+1)+">";
    } else {
      return 
	Prettify.string(0, Stringify.printToString(resultStringLengthLimit,
						   result),
			Prettify.widthLimit, resultStringDepthLimit,
			resultStringLengthLimit, Prettify.shorterNotFaster,
			Prettify.defaultSyntax);
    }
  }
  // --- substituting values ---
  public static int
    printSubstitutingExpressions(PrintWriter pw, IJevaNode node,
				 Token fromToken, Object[] values, int valNum){
    try { 
      if (node instanceof JNEAssignmentExpression) {
	//special case: op= assignment has extra val: [store, old val, new val]
	pw.print(resultString( (IStore) values[valNum++]));
	pw.print(" = ");
	pw.print(resultString(values[valNum++]));
	String opTokenImage=(((JNEAssignmentExpression)node)
			     .getAssignmentOperatorNode()
			     .getBeginToken().image);
	if (! "=".equals(opTokenImage)) {
	  pw.print(" "+opTokenImage.substring(0,1)+" ");
	  pw.print(resultString(values[valNum++]));
	}
      } else {
	int firstChildNum = 0;
	int lastChildNum = node.jjtGetNumChildren() - 1;
	// exception: name used as class name in static method invocation
	if (node instanceof JNEMethodInvocationExpression &&
	    null != ((JNEMethodInvocationExpression)
		     node).getTargetExpression() &&
	    Modifier.isStatic(((JNEMethodInvocationExpression) node)
			      .getMethodSignature().getModifiers()))
	  firstChildNum++;
	// loop through child nodes, substituting for expressions
	for(int childNum= firstChildNum; childNum <= lastChildNum; childNum++){
	  IJevaNode child = node.jnGetChild(childNum);
	  if (child instanceof IExpressionNode) {
	    // print tokens up to child
	    for(;fromToken != child.getBeginToken(); fromToken= fromToken.next)
	      JevaNode.printToken(fromToken, pw, true);
	    // print special tokens before child
	    JevaNode.printSpecialTokens(fromToken, pw);
	    // substitute value for child
	    pw.print(resultString(values[valNum++]));
	    fromToken = child.getEndToken();
	  } else if (child instanceof JNEArguments) {
	    valNum = printSubstitutingExpressions(pw, child, fromToken, values,
						  valNum);
	    fromToken = child.getEndToken();
	  }
	}
	// print tokens after last child
	for (; fromToken != node.getEndToken(); fromToken = fromToken.next)
	  JevaNode.printToken(fromToken, pw, true);
      }
    } catch (Throwable t) { pw.println("<<"+t.getClass().getName()+">>"); }
    return valNum;
  }
}
