import edu.mit.ai.psg.jeva.*;

/** Demo: Reparsing vs. Evaluating with different environments.

    Parsing is much more expensive than evaluating (it generally
    constructs many more objects), so to evaluate the same expression
    with different identifier bindings, parse the expression once and
    evaluate it multiple times, supplying a different environment of
    identifier bindings each time. 

    This example evaluates a simple expression nExprs*nEvals times in
    two ways, timing each case:<ol>
    <li>by reparsing for each eval:  constructs the expression with 
        literal values, and then parses and evaluates it, all 
        repeated nExprs*nEvals times, and
    <li>by making env for each eval: parses the expression with 
        identifiers nExprs times, and evaluates each parse nEvals 
        times by constructing environments binding identifiers to
	values.</ol>
    
    Evaluating without reparsing may have an order of magnitude lower
    marginal cost per evaluation, depending on the number of evals per
    expression, modulo cost of any calls made by the expression.
    (I.e., evaluating can be roughly a factor of 10 faster than
    parsing if the same expression is parsed once and evaluated many
    times, leaving out the cost of any methods called by the
    expression.)

    @author CarlManning, caroma@ai.mit.edu<br>
    Copyright (c) 1999 Massachusetts Institute of Technology
**/

public class DemoEvalWithEnv {
  /** For each of nExprs expressions, evaluates it nEvals times by 
      recreating expression from value literals, and then reparsing and
      evaluating it. **/
  public static int runByReparsingForEachEval(int nExprs, int nEvals) {
    int total = 0;
    try { 
      for (int iExpr = 0; iExpr < nExprs; iExpr++) { 
	for (int iEval = 0; iEval < nEvals; iEval++) {
	  int pr = 100*(1+iEval);
	  double tx = 0.03+(0.01*iEval);
	  Integer result = (Integer)
	    Jeva.parseEvalStringExpression("(int)("+pr+"*(1.0+"+tx+"))");
	  total += result.intValue();
	}
      }
    }
    catch (ParseException e) { throw new Error(""+e); }
    catch (VerifyingException e) { throw new Error(""+e); }
    catch (ThrowException e) { throw new Error(""+e); }
   
    return total;
  }
  /** For each of nExprs expressions, parses the expression once, and
      evaluates it nEvals times.  For each eval, evaluates the expression
      using an environment that binds the identfiers to values. **/
  public static int runByMakingEnvForEachEval(int nExprs, int nEvals) { 
    int total = 0;
    IEnv defaultEnv = Jeva.makeDefaultEnv();
    try { 
      for (int iExpr = 0; iExpr < nExprs; iExpr++) { 
	IExpressionNode expr =
	  Jeva.parseStringExpression("(int)(price*(1.0+tax))");
	for (int iEval = 0; iEval < nEvals; iEval++) {
	  int pr = 100*(1+iEval);
	  double tx = 0.03+(0.01*iEval);
	  Integer result = (Integer)
	    EvalMethods.evaluate(expr,
				 Jeva.extendEnv
				 (int.class, "price", new Integer(pr),
				  Jeva.extendEnv
				  (double.class, "tax", new Double(tx),
				   defaultEnv)));
	  total += result.intValue();
	}
      }
    }
    catch (ParseException e) { throw new Error(""+e); }
    catch (VerifyingException e) { throw new Error(""+e); }
    catch (ThrowException e) { throw new Error(""+e); }
   
    return total;
  }
   
  public static void main(String[] args) {
    // number of expressions to evaluate.  
    int nExprs = 100;
    // number of times to evaluate each expression (with different values)
    int nEvals = 10;
   
    // preload classes, run enough to cause JIT complation on Jeva classes
    System.out.println
      ("pre-running to load classes and invoke JIT on Jeva classes");
    runByReparsingForEachEval(10, 30);
    runByMakingEnvForEachEval(10, 30);
    System.out.println("...done");
   
    System.out.println("--- Timed runs ---");
    // first try it by parsing and evaluating each time:
    timeRunByReparsingForEachEval(nExprs, nEvals);
    // now try it parsing nExprs times, and evaluating each parse nEvals times:
    timeRunByMakingEnvForEachEval(nExprs, nEvals);
   
    System.out.println("--- Repeat to check for consistency between runs ---");
    timeRunByReparsingForEachEval(nExprs, nEvals);
    timeRunByMakingEnvForEachEval(nExprs, nEvals);
   
    System.out.println("--- Check for consistency between methods ---");
    timeRunByMakingEnvForEachEval(nExprs*nEvals, 1);
    
    System.out.println("--- Vary to show marginal costs: Reparsing ---");
    timeRunByReparsingForEachEval(nExprs, nEvals);
    timeRunByReparsingForEachEval(2*nExprs, nEvals);
    timeRunByReparsingForEachEval(3*nExprs, nEvals);
   
    System.out.println("--- Vary to show marginal costs: Making Envs ---");
    System.out.println("Vary nExprs:");
    timeRunByMakingEnvForEachEval(nExprs, nEvals);
    timeRunByMakingEnvForEachEval(2*nExprs, nEvals);   
    timeRunByMakingEnvForEachEval(3*nExprs, nEvals);   
    System.out.println("Vary nEvals:");
    timeRunByMakingEnvForEachEval(nExprs, nEvals);
    timeRunByMakingEnvForEachEval(nExprs, 2*nEvals);   
    timeRunByMakingEnvForEachEval(nExprs, 3*nEvals);   
  }
  static final void timeRunByReparsingForEachEval(int nExprs, int nEvals) {
    System.out.println("Parse and eval "+nExprs+"*"+nEvals+" expressions:");
    System.gc(); // remove gc effects of old garbage.
    long startTime = System.currentTimeMillis();
    int result = runByReparsingForEachEval(nExprs, nEvals);
    long duration = System.currentTimeMillis() - startTime;
    System.out.println("Result: "+result+", duration: "+duration/1000.0+"sec");
  }
  static final void timeRunByMakingEnvForEachEval(int nExprs, int nEvals) {
    System.out.println
      ("Parse "+nExprs+", eval each "+nEvals+" times in different envs:");
    System.gc(); // remove gc effects of old garbage.
    long startTime = System.currentTimeMillis();
    int result = runByMakingEnvForEachEval(nExprs, nEvals);
    long duration = System.currentTimeMillis() - startTime;
    System.out.println("Result: "+result+", duration: "+duration/1000.0+"sec");
  }
}
