gt:           && (theTok != T.end || slen < 2 || st[1].tok != T.process)) {
context: /* $RCSfile$
   * $Author: hansonr $
   * $Date: 2015-10-11 10:41:18 -0500 (Sun, 11 Oct 2015) $
   * $Revision: 20817 $
   *
   * Copyright (C) 2003-2006  Miguel, Jmol Development, www.jmol.org
   *
   * Contact: jmol-developers@lists.sf.net
   *
   *  This library is free software; you can redistribute it and/or
   *  modify it under the terms of the GNU Lesser General Public
   *  License as published by the Free Software Foundation; either
   *  version 2.1 of the License, or (at your option) any later version.
   *
   *  This library is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   *  Lesser General Public License for more details.
   *
   *  You should have received a copy of the GNU Lesser General Public
   *  License along with this library; if not, write to the Free Software
   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   */
  package org.jmol.script;
  
  import java.util.Arrays;
  import java.util.Hashtable;
  import java.util.Map;
  
  import javajs.awt.Font;
  import javajs.util.A4;
  import javajs.util.AU;
  import javajs.util.BArray;
  import javajs.util.Base64;
  import javajs.util.Lst;
  import javajs.util.M3;
  import javajs.util.M4;
  import javajs.util.Measure;
  import javajs.util.OC;
  import javajs.util.P3;
  import javajs.util.P4;
  import javajs.util.PT;
  import javajs.util.Quat;
  import javajs.util.SB;
  import javajs.util.T3;
  import javajs.util.V3;
  
  import org.jmol.api.Interface;
  import org.jmol.api.JmolParallelProcessor;
  import org.jmol.api.JmolScriptFunction;
  import org.jmol.api.SymmetryInterface;
  import org.jmol.atomdata.RadiusData;
  import org.jmol.atomdata.RadiusData.EnumType;
  import org.jmol.c.PAL;
  import org.jmol.c.STR;
  import org.jmol.c.VDW;
  import org.jmol.i18n.GT;
  import org.jmol.io.JmolBinary;
  import org.jmol.java.BS;
  import org.jmol.modelset.Atom;
  import org.jmol.modelset.BondSet;
  import org.jmol.modelset.Group;
  import org.jmol.modelset.TickInfo;
  import org.jmol.shape.MeshCollection;
  import org.jmol.shape.Shape;
  import org.jmol.thread.JmolThread;
  import org.jmol.util.BSUtil;
  import org.jmol.util.ColorEncoder;
  import org.jmol.util.Edge;
  import org.jmol.util.Elements;
  import org.jmol.util.Escape;
  import org.jmol.util.Logger;
  import org.jmol.util.Parser;
  import org.jmol.util.SimpleUnitCell;
  import org.jmol.util.Txt;
  import org.jmol.viewer.ActionManager;
  import org.jmol.viewer.FileManager;
  import org.jmol.viewer.JC;
  import org.jmol.viewer.ShapeManager;
  import org.jmol.viewer.StateManager;
  import org.jmol.viewer.TransformManager;
  import org.jmol.viewer.Viewer;
  import org.mosmar.ovrui.OculusWS;
  
  public class ScriptEval extends ScriptExpr {
  
    
    /*
     * To make this a bit more manageable, I separated ScriptEvaluator into four parts:
     * 
     * 
     * ScriptEval                -- entry point and script command code
     * 
     *   extends ScriptExpr      -- expression parsing
     * 
     *     extends ScriptParam   -- parameter parsing
     * 
     *       extends ScriptError -- error handling 
     * 
     *   scriptext.CmdExt        -- optionally loaded, less-used commands
     *   scriptext.MathExt       -- optionally loaded, less-used functions
     *   scriptext.SmilesExt     -- optionally loaded methods for cmds and math
     *   
     * 
     * 
     * This main class is subdivided into the following sections:
     * 
     *  global fields
     * 
     * 
     * 
     * 
     *  Bob Hanson, 2/27/2014 
     */
    
    /*
     *
     * The ScriptEvaluator class, the Viewer, the xxxxManagers, the Graphics3D
     * rendering engine, the ModelSet and Shape classes, and the Adapter file
     * reader classes form the core of the Jmol molecular visualization framework.
     * 
     * An extension of this file is org.jmol.scriptext.ScriptExt .
     * 
     * The ScriptEvaluator has just a few entry points, which you will find
     * immediately following this comment. They include:
     * 
     * public boolean compileScriptString(String script, boolean tQuiet)
     * 
     * public boolean compileScriptFile(String filename, boolean tQuiet)
     * 
     * public void evaluateCompiledScript(boolean isCmdLine_c_or_C_Option, boolean
     * isCmdLine_C_Option, boolean historyDisabled, boolean listCommands)
     * 
     * Essentially ANYTHING can be done using these three methods. A variety of
     * other methods are available via Viewer, which is the the true portal to
     * Jmol (via the JmolViewer interface) for application developers who want
     * faster, more direct processing.
     * 
     * A little Jmol history:
     * 
     * General history notes can be found at our ConfChem paper, which can be
     * found at
     * http://chemapps.stolaf.edu/jmol/presentations/confchem2006/jmol-confchem
     * .htm
     * 
     * This ScriptEvaluator class was initially written by Michael (Miguel) Howard
     * as Eval.java as an efficient means of reproducing the RasMol scripting
     * language for Jmol. Key additions there included:
     * 
     * - tokenization of commands via the Compiler class (now ScriptCompiler and
     * ScriptCompilationTokenParser) - ScriptException error handling - a flexible
     * yet structured command parameter syntax - implementations of RasMol
     * secondary structure visualizations - isosurfaces, dots, labels, polyhedra,
     * draw, stars, pmesh, more
     * 
     * Other Miguel contributions include:
     * 
     * - the structural bases of the Adapter, ModelSet, and ModelSetBio classes -
     * creation of Manager classes - absolutely amazing raw pixel bitmap rendering
     * code (org.jmol.g3d) - popup context menu - inline model loading
     * 
     * Bob Hanson (St. Olaf College) found out about Jmol during the spring of
     * 2004. After spending over a year working on developing online interactive
     * documentation, he started actively writing code early in 2006. During the
     * period 2006-2009 Bob completely reworked the script processor (and much of
     * the rest of Jmol) to handle a much broader range of functionality. Notable
     * improvements include:
     * 
     * - display/hide commands - dipole, ellipsoid, geosurface, lcaoCartoon
     * visualizations - quaternion and ramachandran commands - much expanded
     * isosurface / draw commands - configuration, disorder, and biomolecule
     * support - broadly 2D- and 3D-positionable echos - translateSelected and
     * rotateSelected commands - getProperty command, providing access to more
     * file information - data and write commands - writing of high-resolution
     * JPG, PNG, and movie-sequence JPG - generalized export to Maya and PovRay
     * formats
     * 
     * - multiple file loading, including trajectories - minimization using the
     * Universal Force Field (UFF) - atom/model deletion and addition - direct
     * loading of properties such as partial charge or coordinates - several new
     * file readers, including manifested zip file reading - default directory, CD
     * command, and pop-up file open/save dialogs
     * 
     * - "internal" molecular coordinate-based rotations - full support for
     * crystallographic formats, including space groups, symmetry, unit cells, and
     * fractional coordinates - support for point groups and molecular symmetry -
     * navigation mode - antialiasing of display and imaging - save/restore/write
     * exact Jmol state - JVXL file format for compressed rapid generation of
     * isosurfaces
     * 
     * - user-defined variables - addition of a Reverse Polish Notation (RPN)
     * expression processor - extension of the RPN processor to user variables -
     * user-defined functions - flow control commands if/else/endif, for, and
     * while - JavaScript/Java-like brace syntax - key stroke-by-key stroke
     * command syntax checking - integrated help command - user-definable popup
     * menu - language switching
     * 
     * - fully functional signed applet - applet-applet synchronization, including
     * two-applet geoWall stereo rendering - JSON format for property delivery to
     * JavaScript - jmolScriptWait, dual-threaded queued JavaScript scripting
     * interface - extensive callback development - script editor panel (work in
     * progress, June 2009)
     * 
     * Several other people have contributed. Perhaps they will not be too shy to
     * add their claim to victory here. Please add your contributions.
     * 
     * - Jmol application (Egon Willighagen) - smiles support (Nico Vervelle) -
     * readers (Rene Kanter, Egon, several others) - initial VRML export work
     * (Nico Vervelle) - WebExport (Jonathan Gutow) - internationalization (Nico,
     * Egon, Angel Herriez) - Jmol Wiki and user guide book (Angel Herriez)
     * 
     * While this isn't necessarily the best place for such discussion, open
     * source principles require proper credit given to those who have
     * contributed. This core class seems to me a place to acknowledge this core
     * work of the Jmol team.
     * 
     * Bob Hanson, 6/2009 hansonr@stolaf.edu
     */
  
    
    
    /////////////////// global fields ///////////////////
    
    private final static String saveList = 
        "bonds? context? coordinates? orientation? rotation? selection? state? structure?";
    
    private static int iProcess;
  
    public ShapeManager sm;
  
    public boolean isJS;
    
    private JmolThread scriptDelayThread, fileLoadThread;
  
    public boolean allowJSThreads = true;
  
    @Override
    public boolean getAllowJSThreads() {
      return allowJSThreads;
    }
    
    // execution options:
    
    public boolean historyDisabled; // set by ScriptExt.evaluateParallel
  
    private boolean debugScript;
    private boolean isCmdLine_C_Option;
    private boolean isCmdLine_c_or_C_Option;
    private boolean listCommands;
  
    public boolean tQuiet;
    
    public boolean doReport() {
      return (!tQuiet && scriptLevel <= scriptReportingLevel);
    }
  
    private boolean executionStopped;
    private boolean executionPaused;
    private boolean executionStepping;
    private boolean executing;
  
    private long timeBeginExecution;
    private long timeEndExecution;
  
    private boolean mustResumeEval; // see resumeEval
  
    //private static int evalID;
  
    private Thread currentThread;
    public ScriptCompiler compiler;
  
    public SB outputBuffer;
  
    private String contextPath = "";
    public String scriptFileName;
    public String functionName;
    
    public boolean isStateScript;
    
    @Override
    public boolean isStateScript() {
      return isStateScript;
    }
  
    public int scriptLevel;
  
    public static int commandHistoryLevelMax = 0;
    private static int contextDepthMax = 100; // mutable using set scriptLevelMax
    private static int scriptReportingLevel = 0;
  
    /**
     * set a static variable, with checking for range
     */
    @Override
    public int setStatic(int tok, int ival) {
      switch (tok) {
      case T.contextdepthmax:
        if (ival >= 10)
          contextDepthMax = ival;
        return contextDepthMax;
      case T.historylevel:
        if (ival >= 0)
          commandHistoryLevelMax = ival;
        return commandHistoryLevelMax;  
      case T.scriptreportinglevel:
        if (ival >= 0)
        scriptReportingLevel = ival;
        return scriptReportingLevel;
      }
      return 0;
    }
  
    // created by Compiler:
    
    public T[][] aatoken;
    private short[] lineNumbers;
    private int[][] lineIndices;
  
    private String script;
    private String scriptExtensions;
  
    @Override
    public String getScript() {
      return script;
    }
  
    // specific to current statement:
    
    protected int pc; // program counter
    public String thisCommand;
    public String fullCommand;
    private int lineEnd;
    private int pcEnd;
  
    // for specific commmands:
    
    private boolean forceNoAddHydrogens;
    
  
    /**
     * @j2sIgnoreSuperConstructor
     * 
     */
    public ScriptEval() {
      // by reflection as well as directly
      currentThread = Thread.currentThread();
      //evalID++;
    }
  
    @Override
    public ScriptEval setViewer(Viewer vwr) {
      this.vwr = vwr;
      this.compiler = (compiler == null ? (ScriptCompiler) vwr.compiler
          : compiler);
      isJS = vwr.isSingleThreaded;
      return this;
    }
  
    @Override
    public void setCompiler() {
      vwr.compiler = compiler = new ScriptCompiler(vwr);
    }
  
    // //////////////// primary interfacing methods //////////////////
  
    /*
     * see Viewer.evalStringWaitStatus for how these are implemented
     */
    @Override
    public boolean compileScriptString(String script, boolean tQuiet) {
      clearState(tQuiet);
      contextPath = "[script]";
      return compileScript(null, script, debugScript);
    }
  
    @Override
    public boolean compileScriptFile(String filename, boolean tQuiet) {
      clearState(tQuiet);
      contextPath = filename;
      return compileScriptFileInternal(filename, null, null, null);
    }
  
    @Override
    public void evaluateCompiledScript(boolean isCmdLine_c_or_C_Option,
                                       boolean isCmdLine_C_Option,
                                       boolean historyDisabled,
                                       boolean listCommands, SB outputBuffer,
                                       boolean allowThreads) {
      boolean tempOpen = this.isCmdLine_C_Option;
      this.isCmdLine_C_Option = isCmdLine_C_Option;
      chk = this.isCmdLine_c_or_C_Option = isCmdLine_c_or_C_Option;
      this.historyDisabled = historyDisabled;
      this.outputBuffer = outputBuffer;
      currentThread = Thread.currentThread();
      this.allowJSThreads = allowThreads;
      this.listCommands = listCommands;
      timeBeginExecution = System.currentTimeMillis();
      executionStopped = executionPaused = false;
      executionStepping = false;
      executing = true;
      vwr.pushHoldRepaintWhy("runEval");
      setScriptExtensions();
      executeCommands(false, true);
      this.isCmdLine_C_Option = tempOpen;
      if(isStateScript)
        ScriptManager.setStateScriptVersion(vwr, null); // set by compiler
    }
  
    public boolean useThreads() {
      return (!chk && !vwr.headless && !vwr.autoExit
          && vwr.haveDisplay && outputBuffer == null && allowJSThreads);
    }
  
    private void executeCommands(boolean isTry, boolean reportCompletion) {
      boolean haveError = false;
      try {
        if (!dispatchCommands(false, false, isTry))
          return;
      } catch (Error er) {
        vwr.handleError(er, false);
        setErrorMessage("" + er + " " + vwr.getShapeErrorState());
        errorMessageUntranslated = "" + er;
        report(errorMessage);
        haveError = true;
      } catch (ScriptException e) {
        if (e instanceof ScriptInterruption && (!isTry || !e.isError)) {
          
          // ScriptInterruption will be called in Java or JavaScript
          // by a THROW command, but in that case e.isError == true
          
          // ScriptInterruption will be called in JavaScript to 
          // stop this thread and initiate a setTimeout sequence 
          // that is responsible for getting us back to the
          // current point using resumeEval again.
          // it's not a real exception, but it has that 
          // property so that it can be caught here.
          
          return;
        }
        if (isTry) {
          vwr.setStringProperty("_errormessage", "" + e);
          return;
        }
        setErrorMessage(e.toString());
        errorMessageUntranslated = e.getErrorMessageUntranslated();
        report(errorMessage);
        vwr
            .notifyError(
                (errorMessage != null
                    && errorMessage.indexOf("java.lang.OutOfMemoryError") >= 0 ? "Error"
                    : "ScriptException"), errorMessage, errorMessageUntranslated);
        haveError = true;
      }
      if (haveError || !isJS || !allowJSThreads) {
        vwr.setTainted(true);
        vwr.popHoldRepaint("executeCommands" + " "
            + (scriptLevel > 0 ? JC.REPAINT_IGNORE : ""));
      }
      timeEndExecution = System.currentTimeMillis();
      if (errorMessage == null && executionStopped)
        setErrorMessage("execution interrupted");
      else if (!tQuiet && reportCompletion)
        vwr.scriptStatus(JC.SCRIPT_COMPLETED);
      executing = chk = this.isCmdLine_c_or_C_Option = this.historyDisabled = false;
      String msg = getErrorMessageUntranslated();
      vwr.setErrorMessage(errorMessage, msg);
      if (!tQuiet && reportCompletion)
        vwr.setScriptStatus("Jmol script terminated", errorMessage,
            1 + (int) (timeEndExecution - timeBeginExecution), msg);
    }
  
  
    /**
     * From dispatchCommands and JmolThread resumeEval.
     * 
     * After throwing a ScriptInterruption, all statements following the current
     * one are lost. When a JavaScript timeout returns from a DELAY, MOVE, MOVETO,
     * or other sleep-requiring command, it is the ScriptContext that contains all
     * have to worry about this, because the current thread is just put to sleep,
     * not stopped, but in JavaScript, where we only have one thread, we need to
     * manage this more carefully.
     * 
     * We re-enter the halted script here, using a saved script context. The
     * program counter is incremented to skip the initiating statement, and all
     * parent contexts up the line are set with mustResumeEval = true.
     * 
     * @param sc
     */
  
    @Override
    public void resumeEval(ScriptContext sc) {
  
      // 
      //
      //      |
      //      |
      //     INTERRUPT---
      //     (1)         |
      //      |          |
      //      |          |
      //      |      INTERRUPT----------------     
      //      |         (2)                   |     
      //      |          |                    |      
      //      |          |                    |
      //      |     resumeEval-->(1)     MOVETO_INIT
      //   (DONE)           
      //                                 (new thread) 
      //                                 MOVETO_FINISH
      //                                      |
      //                                 resumeEval-->(2)
      //                                   
      //  In Java, this is one overall thread that sleeps
      //  during the MOVETO. But in JavaScript, the setTimeout()
      //  starts a new thread and (1) and (2) are never executed.
      //  We must run resumeEval at the end of each dispatch loop.
      //
      //  Thus, it is very important that nothing is ever executed 
      //  after dispatchCommands.
      //
      // 
      //  Functions are tricky, though. How do we restart them?
      //
      //     main
      //      |
      //      ---------test()
      //                 |
      //                 |
      //                 |
      //                 -------------------zoomTo     
      //                (2)                   |     
      //                 |                    |      
      //      ---------return                 |
      //      |                          MOVETO_INIT
      //   (DONE)           
      //                                 (new thread) 
      //                                 MOVETO_FINISH
      //                                      |
      //                                 resumeEval-->(2)
      //
      // When the zoomTo is initiated, a ScriptInterrpt is thrown, which
      // stops processing in test() and main. 
      //
      // 
      setErrorMessage(null);
      if (executionStopped || sc == null || !sc.mustResumeEval) {
        resumeViewer("resumeEval");
        return;
      }
      if (!executionPaused)
        sc.pc++;
      thisContext = sc;
      
      if (sc.scriptLevel > 0)
        scriptLevel = sc.scriptLevel - 1;
      restoreScriptContext(sc, true, false, false);
      pcResume = sc.pc;
      executeCommands(sc.isTryCatch, scriptLevel <= 0);
      pcResume = -1;
    }
  
    private void resumeViewer(String why) {
      vwr.setTainted(true);
      vwr.popHoldRepaint(why + (chk ? JC.REPAINT_IGNORE : ""));
      vwr.queueOnHold = false;
    }
  
    @Override
    public void runScript(String script) throws ScriptException {
      if (!vwr.isPreviewOnly)
        runScriptBuffer(script, outputBuffer);
    }
  
    /**
     * runs a script immediately and sends selected output to a provided SB
     * 
     * @param script
     * @param outputBuffer
     * @throws ScriptException
     */
    @Override
    public void runScriptBuffer(String script, SB outputBuffer)
        throws ScriptException {
      pushContext(null, "runScriptBuffer");
      contextPath += " >> script() ";
      this.outputBuffer = outputBuffer;
      allowJSThreads = false;
      if (compileScript(null, script + JC.SCRIPT_EDITOR_IGNORE
          + JC.REPAINT_IGNORE, false))
        dispatchCommands(false, false, false);
      popContext(false, false);
    }
  
    /**
     * a method for just checking a script
     * 
     * @param script
     * @return a ScriptContext that indicates errors and provides a tokenized
     *         version of the script that has passed all syntax checking, both in
     *         the compiler and the evaluator
     * 
     */
    @Override
    public ScriptContext checkScriptSilent(String script) {
      ScriptContext sc = compiler.compile(null, script, false, true, false, true);
      if (sc.errorType != null)
        return sc;
      restoreScriptContext(sc, false, false, false);
      chk = true;
      isCmdLine_c_or_C_Option = isCmdLine_C_Option = false;
      pc = 0;
      try {
        dispatchCommands(false, false, false);
      } catch (ScriptException e) {
        setErrorMessage(e.toString());
        sc = getScriptContext("checkScriptSilent");
      }
      chk = false;
      return sc;
    }
  
    static SB getContextTrace(Viewer vwr, ScriptContext sc, SB sb,
                              boolean isTop) {
      if (sb == null)
        sb = new SB();
      int pc = Math.min(sc.pc, sc.lineNumbers[sc.lineNumbers.length - 1]);
      sb.append(getErrorLineMessage(sc.functionName, sc.scriptFileName,
          sc.lineNumbers[pc], pc, ScriptEval.statementAsString(vwr,
              sc.statement, (isTop ? sc.iToken : 9999), false)));
      if (sc.parentContext != null)
        getContextTrace(vwr, sc.parentContext, sb, false);
      return sb;
    }
  
    // //////////////////////// script execution /////////////////////
  
    @Override
    public void setDebugging() {
      debugScript = vwr.getBoolean(T.debugscript);
      debugHigh = (debugScript && Logger.debugging);
    }
  
  
    @Override
    public void haltExecution() {
      resumePausedExecution();
      executionStopped = true;
    }
  
    @Override
    public void pauseExecution(boolean withDelay) {
      if (chk || vwr.headless)
        return;
      if (withDelay && !isJS)
        delayScript(-100);
      vwr.popHoldRepaint("pauseExecution " + withDelay);
      executionStepping = false;
      executionPaused = true;
    }
  
    @Override
    public void stepPausedExecution() {
      executionStepping = true;
      executionPaused = false;
      // releases a paused thread but
      // sets it to pause for the next command.
    }
  
    @Override
    public void resumePausedExecution() {
      executionPaused = false;
      executionStepping = false;
    }
  
    @Override
    public boolean isExecuting() {
      return executing && !executionStopped;
    }
  
    @Override
    public boolean isPaused() {
      return executionPaused;
    }
  
    @Override
    public boolean isStepping() {
      return executionStepping;
    }
  
    @Override
    public boolean isStopped() {
      return executionStopped || !isJS && currentThread != Thread.currentThread();
    }
  
    /**
     * when paused, indicates what statement will be next
     * 
     * @return a string indicating the statement
     */
    @Override
    public String getNextStatement() {
      return (pc < aatoken.length ? getErrorLineMessage(functionName,
          scriptFileName, getLinenumber(null), pc,
          statementAsString(vwr, aatoken[pc], -9999, debugHigh)) : "");
    }
  
    /**
     * used for recall of commands in the application console
     * 
     * @param pc
     * @param allThisLine
     * @param addSemi
     * @return a string representation of the command
     */
    private String getCommand(int pc, boolean allThisLine, boolean addSemi) {
      if (pc >= lineIndices.length)
        return "";
      if (allThisLine) {
        int pt0 = -1;
        int pt1 = script.length();
        for (int i = 0; i < lineNumbers.length; i++)
          if (lineNumbers[i] == lineNumbers[pc]) {
            if (pt0 < 0)
              pt0 = lineIndices[i][0];
            pt1 = lineIndices[i][1];
          } else if (lineNumbers[i] == 0 || lineNumbers[i] > lineNumbers[pc]) {
            break;
          }
        String s = script;
        if (s.indexOf('\1') >= 0)
          s = s.substring(0, s.indexOf('\1'));
        if (pt1 == s.length() - 1 && s.endsWith("}"))
          pt1++;
        return (pt0 == s.length() || pt1 < pt0 ? "" : s.substring(
            Math.max(pt0, 0), Math.min(s.length(), pt1)));
      }
      int ichBegin = lineIndices[pc][0];
      int ichEnd = lineIndices[pc][1];
      // (pc + 1 == lineIndices.length || lineIndices[pc + 1][0] == 0 ? script
      // .length()
      // : lineIndices[pc + 1]);
      String s = "";
      if (ichBegin < 0 || ichEnd <= ichBegin || ichEnd > script.length())
        return "";
      try {
        s = script.substring(ichBegin, ichEnd);
        if (s.indexOf("\\\n") >= 0)
          s = PT.rep(s, "\\\n", "  ");
        if (s.indexOf("\\\r") >= 0)
          s = PT.rep(s, "\\\r", "  ");
        // int i;
        // for (i = s.length(); --i >= 0 && !ScriptCompiler.eol(s.charAt(i), 0);
        // ){
        // }
        // s = s.substring(0, i + 1);
        if (s.length() > 0 && !s.endsWith(";")/*
                                               * && !s.endsWith("{") &&
                                               * !s.endsWith("}")
                                               */)
          s += ";";
      } catch (Exception e) {
        Logger.error("darn problem in Eval getCommand: ichBegin=" + ichBegin
            + " ichEnd=" + ichEnd + " len = " + script.length() + "\n" + e);
      }
      return s;
    }
  
    private void logDebugScript(T[] st, int ifLevel) {
      iToken = -9999;
      if (debugHigh) {
        if (st.length > 0)
          Logger.debug(st[0].toString());
        for (int i = 1; i < st.length; ++i)
          if (st[i] != null)
            Logger.debug(st[i].toString());
        SB strbufLog = new SB();
        String s = (ifLevel > 0 ? "                          ".substring(0,
            ifLevel * 2) : "");
        strbufLog.append(s).append(
            statementAsString(vwr, st, iToken, debugHigh));
        vwr.scriptStatus(strbufLog.toString());
      } else {
        String cmd = getCommand(pc, false, false);
        if (cmd != "")
          vwr.scriptStatus(cmd);
      }
  
    }
  
    // /////////////// string-based evaluation support /////////////////////
  
    /**
     * a general-use method to evaluate a "SET" type expression.
     * @param asVariable
     * @param expr
     * 
     * @return an object of one of the following types: Boolean, Integer, Float,
     *         String, Point3f, BitSet
     */
  
    @Override
    public Object evaluateExpression(Object expr, boolean asVariable, boolean compileOnly) {
      // Text.formatText for MESSAGE and ECHO
      // prior to 12.[2/3].32 was not thread-safe for compilation.
      ScriptEval e = (new ScriptEval()).setViewer(vwr);
      try {
        // disallow end-of-script message and JavaScript script queuing
        e.thisContext = thisContext;
        e.contextVariables = contextVariables;
        e.pushContext(null, "evalExp");
        e.allowJSThreads = false;
      } catch (ScriptException e1) {
        //ignore
      }
      boolean exec0 = executing;
      Object o = (e.evaluate(expr, asVariable, compileOnly));
      executing = exec0;
      return o;
    }
  
    private Object evaluate(Object expr, boolean asVariable, boolean compileOnly) {
      try {
        if (expr instanceof String) {
          if (compileScript(null, "e_x_p_r_e_s_s_i_o_n = " + expr, false)) {
            if (compileOnly)
              return aatoken[0];
            setStatement(aatoken[0], 1);
            return (asVariable ? parameterExpressionList(2, -1, false).get(0)
                : parameterExpressionString(2, 0));
          }
        } else if (expr instanceof T[]) {
          BS bs = atomExpression((T[]) expr, 0, 0, true, false, null, false);
          return (asVariable ? SV.newV(T.bitset, bs) : bs);
  
        }
      } catch (Exception ex) {
        Logger.error("Error evaluating: " + expr + "\n" + ex);
      }
      return (asVariable ? SV.getVariable("ERROR") : "ERROR");
    }
  
    /**
     * Check a map for a WHERE phrase
     * 
     */
    @Override
    public boolean checkSelect(Map<String, SV> h, T[] where) {
      boolean ok = false;
      try {
        pushContext(null, "checkSelect");
        ok = parameterExpressionSelect(h, where);
      } catch (Exception ex) {
        Logger.error("checkSelect " + ex);
      }
      popContext(false, false);
      return ok;
    }
  
    /**
     * A general method to evaluate a string representing an atom set.
     * Excepts one atom expression or one per line as "OR".
     * Excepts "()" as "none".
     * 
     * 
     * @param atomExpression
     * @return is a bitset indicating the selected atoms
     * 
     */
    @Override
    public BS getAtomBitSet(Object atomExpression) {
  
      // called by ScriptExpr and ScriptManager
      
      if (atomExpression instanceof BS)
        return (BS) atomExpression;
      BS bs = new BS();
      try {
        pushContext(null, "getAtomBitSet");
        String scr = "select (" + atomExpression + ")";
        scr = PT.replaceAllCharacters(scr, "\n\r", "),(");
        scr = PT.rep(scr, "()", "(none)");
        if (compileScript(null, scr, false)) {
          st = aatoken[0];
          setStatement(st, 0);
          bs = atomExpression(st, 1, 0, false, false, null, true);
        }
        popContext(false, false);
      } catch (Exception ex) {
        Logger.error("getAtomBitSet " + atomExpression + "\n" + ex);
      }
      return bs;
    }
  
  
    // ////////////////////// supporting methods for compilation and loading
  
    public boolean compileScript(String filename, String strScript,
                                 boolean debugCompiler) {
      scriptFileName = filename;
      strScript = fixScriptPath(strScript, filename);
      restoreScriptContext(compiler.compile(filename, strScript, false, false,
          debugCompiler && Logger.debugging, false), false, false, false);
      isStateScript = compiler.isStateScript;
      forceNoAddHydrogens = (isStateScript && script.indexOf("pdbAddHydrogens") < 0);
      String s = script;
      pc = setScriptExtensions();
      if (!chk && vwr.scriptEditorVisible
          && strScript.indexOf(JC.SCRIPT_EDITOR_IGNORE) < 0)
        vwr.scriptStatus("");
      script = s;
      return !error;
    }
  
    private String fixScriptPath(String strScript, String filename) {
      if (filename != null && strScript.indexOf("$SCRIPT_PATH$") >= 0) {
        String path = filename;
        // we first check for paths into ZIP files and adjust accordingly
        int pt = Math.max(filename.lastIndexOf("|"), filename.lastIndexOf("/"));
        path = path.substring(0, pt + 1);
        strScript = PT.rep(strScript, "$SCRIPT_PATH$/", path);
        // now replace the variable itself
        strScript = PT.rep(strScript, "$SCRIPT_PATH$", path);
      }
      return strScript;
    }
  
    private int setScriptExtensions() {
      String extensions = scriptExtensions;
      if (extensions == null)
        return 0;
      int pt = extensions.indexOf("##SCRIPT_STEP");
      if (pt >= 0) {
        executionStepping = true;
      }
      pt = extensions.indexOf("##SCRIPT_START=");
      if (pt < 0)
        return 0;
      pt = PT.parseInt(extensions.substring(pt + 15));
      if (pt == Integer.MIN_VALUE)
        return 0;
      for (pc = 0; pc < lineIndices.length; pc++) {
        if (lineIndices[pc][0] > pt || lineIndices[pc][1] >= pt)
          break;
      }
      if (pc > 0 && pc < lineIndices.length && lineIndices[pc][0] > pt)
        --pc;
      return pc;
    }
  
    private boolean compileScriptFileInternal(String filename, String localPath,
                                              String remotePath, String scriptPath) {
      // from "script" command, with push/pop surrounding or vwr
      if (filename.toLowerCase().indexOf("javascript:") == 0)
        return compileScript(filename, vwr.jsEval(filename.substring(11)),
            debugScript);
      String[] data = new String[2];
      data[0] = filename;
      if (!vwr.fm.getFileDataAsString(data, -1, false, true, false)) { // first opening
        setErrorMessage("io error reading " + data[0] + ": " + data[1]);
        return false;
      }
      if (("\n" + data[1]).indexOf("\nJmolManifest.txt\n") >= 0) {
        String path;
        if (filename.endsWith(".all.pngj") || filename.endsWith(".all.png")) {
          path = "|state.spt";
          filename += "|";
        } else {
          data[0] = filename += "|JmolManifest.txt";
          if (!vwr.fm.getFileDataAsString(data, -1, false, true, false)) { // second entry
            setErrorMessage("io error reading " + data[0] + ": " + data[1]);
            return false;
          }
          path = JmolBinary.getManifestScriptPath(data[1]);
        }
        if (path != null && path.length() > 0) {
          data[0] = filename = filename.substring(0, filename.lastIndexOf("|"))
              + path;
          if (!vwr.fm.getFileDataAsString(data, -1, false, true, false)) { // third entry
            setErrorMessage("io error reading " + data[0] + ": " + data[1]);
            return false;
          }
        }
      }
      scriptFileName = filename;
      data[1] = JmolBinary.getEmbeddedScript(data[1]);
      String script = fixScriptPath(data[1], data[0]);
      if (scriptPath == null) {
        scriptPath = vwr.fm.getFilePath(filename, false, false);
        scriptPath = scriptPath.substring(0,
            Math.max(scriptPath.lastIndexOf("|"), scriptPath.lastIndexOf("/")));
      }
      script = FileManager.setScriptFileReferences(script, localPath, remotePath,
          scriptPath);
      return compileScript(filename, script, debugScript);
    }
  
    // ///////////// Jmol function support  // ///////////////
  
    private JmolParallelProcessor parallelProcessor;
  
    private int pcResume = -1;
  
    @Override
    @SuppressWarnings("unchecked")
    public float evalFunctionFloat(Object func, Object params, float[] values) {
      try {
        Lst<SV> p = (Lst<SV>) params;
        for (int i = 0; i < values.length; i++)
          p.get(i).value = Float.valueOf(values[i]);
        ScriptFunction f = (ScriptFunction) func;
        return SV.fValue(runFunctionAndRet(f, f.name, p, null, true, false, false));
      } catch (Exception e) {
        return Float.NaN;
      }
  
    }
  
    @Override
    public SV getUserFunctionResult(String name, Lst<SV> params, SV tokenAtom)
        throws ScriptException {
  
      // called by ScriptExpr(getBitsetProperty) and ScriptExt(evaluateUserFunction)
      //
      
      return runFunctionAndRet(null, name, params, tokenAtom, true, true, false);
    }
    
    private SV runFunctionAndRet(JmolScriptFunction function, String name,
                             Lst<SV> params, SV tokenAtom, boolean getReturn,
                             boolean setContextPath, boolean allowThreads)
        throws ScriptException {
      
      // called by cmdFlow(TRY command), cmdFunc(user-defined command),
      // evalFunctionFloat (isosurface looking for a value), getFunctionRet
      // (above)
      // 
      if (function == null) {
        // general function call
        name = name.toLowerCase();
        function = vwr.getFunction(name);
        if (function == null)
          return null;
        if (setContextPath)
          contextPath += " >> function " + name;
      } else if (setContextPath) {
        // "try"; not from evalFunctionFloat
        contextPath += " >> " + name;
      }
      pushContext(null, "runFunctionAndRet ");
      if (allowJSThreads)
        allowJSThreads = allowThreads;
      boolean isTry = (function.getTok() == T.trycmd);
      thisContext.isTryCatch = isTry;
      thisContext.isFunction = !isTry;
      functionName = name;
      if (isTry) {
        resetError();
        thisContext.displayLoadErrorsSave = vwr.displayLoadErrors;
        thisContext.tryPt = ++vwr.tryPt;
        vwr.displayLoadErrors = false;
        restoreFunction(function, params, tokenAtom);
        contextVariables.put("_breakval", SV.newI(Integer.MAX_VALUE));
        contextVariables.put("_errorval", SV.newS(""));
        Map<String, SV> cv = contextVariables;
        executeCommands(true, false);
        //JavaScript will not return here after DELAY
        while (thisContext.tryPt > vwr.tryPt)
          popContext(false, false);
        processTry(cv);
        return null;
      } else if (function instanceof JmolParallelProcessor) {
        synchronized (function) // can't do this -- too general
        {
          parallelProcessor = (JmolParallelProcessor) function;
          restoreFunction(function, params, tokenAtom);
          dispatchCommands(false, true, false); // to load the processes
          ((JmolParallelProcessor) function).runAllProcesses(vwr);
        }
      } else {
        restoreFunction(function, params, tokenAtom);
        dispatchCommands(false, true, false);
        //JavaScript will not return here after DELAY or after what???
      }
      SV v = (getReturn ? getContextVariableAsVariable("_retval", false) : null);
      popContext(false, false);
      return v;
    }
  
    private void processTry(Map<String, SV> cv) throws ScriptException {
      vwr.displayLoadErrors = thisContext.displayLoadErrorsSave;
      popContext(false, false);
      String err = (String) vwr.getP("_errormessage");
      if (err.length() > 0) {
        cv.put("_errorval", SV.newS(err));
        resetError();
      }
      cv.put("_tryret", cv.get("_retval"));
      SV ret = cv.get("_tryret");
      if (ret.value != null || ret.intValue != Integer.MAX_VALUE) {
        cmdReturn(ret);
        return;
      }
      String errMsg = (String) (cv.get("_errorval")).value;
      if (errMsg.length() == 0) {
        int iBreak = (cv.get("_breakval")).intValue;
        if (iBreak != Integer.MAX_VALUE) {
          breakAt(pc - iBreak);
          return;
        }
      }
      // normal return will skip the catch
      if (pc + 1 < aatoken.length && aatoken[pc + 1][0].tok == T.catchcmd) {
        // set the intValue positive to indicate "not done" for the IF evaluation
        ContextToken ct = (ContextToken) aatoken[pc + 1][0];
        if (ct.contextVariables != null && ct.name0 != null)
          ct.contextVariables.put(ct.name0, SV.newS(errMsg));
        ct.intValue = (errMsg.length() > 0 ? 1 : -1) * Math.abs(ct.intValue);
      }
    }
  
    private void breakAt(int pt) {
      if (pt < 0) {
        // if pt is a backward reference
        // this is a break within a try{...} block
        getContextVariableAsVariable("_breakval", false).intValue = -pt;
        pcEnd = pc;
        return;
      }
      // pt is to the FOR, WHILE, or SWITCH statement that is being exited
      int ptEnd = Math.abs(aatoken[pt][0].intValue);
      int tok = aatoken[pt][0].tok;
      if (tok == T.casecmd || tok == T.defaultcmd) {
        // breaking from SWITCH
        theToken = aatoken[ptEnd--][0];
        int ptNext = Math.abs(theToken.intValue);
        if (theToken.tok != T.end)
          theToken.intValue = -ptNext;
      } else {
        // breaking from FOR or WHILE (or PROCESS?)
        pc = -1;
        while (pc != pt && thisContext != null) {
          while (thisContext != null
              && !ScriptCompiler.isBreakableContext(thisContext.token.tok))
            popContext(true, false);
          pc = thisContext.pc;
          popContext(true, false);
        }
      }
      pc = ptEnd;
    }
  
    /**
     * note that functions requiring motion cannot be run in JavaScript
     * 
     * @param f
     * @param params
     * @param tokenAtom
     * @throws ScriptException
     */
    private void restoreFunction(JmolScriptFunction f, Lst<SV> params,
                                 SV tokenAtom) throws ScriptException {
      ScriptFunction function = (ScriptFunction) f;
      aatoken = function.aatoken;
      lineNumbers = function.lineNumbers;
      lineIndices = function.lineIndices;
      script = function.script;
      pc = 0;
      if (function.names != null) {
        contextVariables = new Hashtable<String, SV>();
        function.setVariables(contextVariables, params);
      }
      if (tokenAtom != null)
        contextVariables.put("_x", tokenAtom);
    }
  
    
    ////////////////////////// defined atom sets ////////////////////////
    
    @Override
    public void clearDefinedVariableAtomSets() {
      vwr.definedAtomSets.remove("# variable");
    }
  
    /**
     * support for @xxx or define xxx commands
     * 
     */
    private void defineSets() {
      if (!vwr.definedAtomSets.containsKey("# static")) {
        for (int i = 0; i < JC.predefinedStatic.length; i++)
          defineAtomSet(JC.predefinedStatic[i]);
        defineAtomSet("# static");
      }
      if (vwr.definedAtomSets.containsKey("# variable"))
        return;
      for (int i = 0; i < JC.predefinedVariable.length; i++)
        defineAtomSet(JC.predefinedVariable[i]);
      // Now, define all the elements as predefined sets
  
      // name ==> elemno=n for all standard elements, isotope-blind
      // _Xx ==> elemno=n for of all elements, isotope-blind
      for (int i = Elements.elementNumberMax; --i >= 0;) {
        String definition = " elemno=" + i;
        defineAtomSet("@" + Elements.elementNameFromNumber(i) + definition);
        defineAtomSet("@_" + Elements.elementSymbolFromNumber(i) + definition);
      }
      // name ==> _e=nn for each alternative element
      for (int i = Elements.firstIsotope; --i >= 0;) {
        String definition = "@" + Elements.altElementNameFromIndex(i) + " _e="
            + Elements.altElementNumberFromIndex(i);
        defineAtomSet(definition);
      }
      // these variables _e, _x can't be more than two characters
      // name ==> _isotope=iinn for each isotope
      // _T ==> _isotope=iinn for each isotope
      // _3H ==> _isotope=iinn for each isotope
      for (int i = Elements.altElementMax; --i >= Elements.firstIsotope;) {
        int ei = Elements.altElementNumberFromIndex(i);
        String def = " _e=" + ei;
        String definition = "@_" + Elements.altElementSymbolFromIndex(i);
        defineAtomSet(definition + def);
  
        definition = "@_" + Elements.altIsotopeSymbolFromIndex(i);
        defineAtomSet(definition + def);
        definition = "@_" + Elements.altIsotopeSymbolFromIndex2(i);
        defineAtomSet(definition + def);
  
        definition = "@" + Elements.altElementNameFromIndex(i);
        if (definition.length() > 1)
          defineAtomSet(definition + def);
  
        // @_12C _e=6
        // @_C12 _e=6
        int e = Elements.getElementNumber(ei);
        ei = Elements.getNaturalIsotope(e);
        if (ei > 0) {
          def = Elements.elementSymbolFromNumber(e);
          defineAtomSet("@_" + def + ei + " _e=" + e);
          defineAtomSet("@_" + ei + def + " _e=" + e);
        }
      }
      defineAtomSet("# variable");
    }
  
    private void defineAtomSet(String script) {
      if (script.indexOf("#") == 0) {
        vwr.definedAtomSets.put(script, Boolean.TRUE);
        return;
      }
      ScriptContext sc = compiler.compile("#predefine", script, true, false,
          false, false);
      if (sc.errorType != null) {
        vwr
            .scriptStatus("JmolConstants.java ERROR: predefined set compile error:"
                + script + "\ncompile error:" + sc.errorMessageUntranslated);
        return;
      }
  
      if (sc.getTokenCount() != 1) {
        vwr
            .scriptStatus("JmolConstants.java ERROR: predefinition does not have exactly 1 command:"
                + script);
        return;
      }
      T[] statement = sc.getToken(0);
      if (statement.length <= 2) {
        vwr.scriptStatus("JmolConstants.java ERROR: bad predefinition length:"
            + script);
        return;
      }
      int tok = statement[1].tok;
      if (!T.tokAttr(tok, T.identifier) && !T.tokAttr(tok, T.predefinedset)) {
        vwr.scriptStatus("JmolConstants.java ERROR: invalid variable name:"
            + script);
        return;
      }
      String name = ((String) statement[1].value).toLowerCase();
      if (name.startsWith("dynamic_"))
        name = "!" + name.substring(8);
      vwr.definedAtomSets.put(name, statement);
    }
  
    @Override
    public BS lookupIdentifierValue(String identifier) throws ScriptException {
      
      // called by ScriptExpr and ScriptExt
      
      // all variables and possible residue names for PDB
      // or atom names for non-pdb atoms are processed here.
  
      // priority is given to a defined variable.
  
      BS bs = lookupValue(identifier, false);
      if (bs != null)
        return BSUtil.copy(bs);
  
      // next we look for names of groups (PDB) or atoms (non-PDB)
      bs = getAtomBits(T.identifier, identifier);
      return (bs == null ? new BS() : bs);
    }
  
    private BS lookupValue(String setName, boolean plurals)
        throws ScriptException {
      if (chk) {
        return new BS();
      }
      defineSets();
      setName = setName.toLowerCase();
      Object value = vwr.definedAtomSets.get(setName);
      boolean isDynamic = false;
      if (value == null) {
        value = vwr.definedAtomSets.get("!" + setName);
        isDynamic = (value != null);
      }
      if (value instanceof BS)
        return (BS) value;
      if (value instanceof T[]) { // j2s OK -- any Array here
        pushContext(null, "lookupValue");
        BS bs = atomExpression((T[]) value, -2, 0, true, false, null, true);
        popContext(false, false);
        if (!isDynamic)
          vwr.definedAtomSets.put(setName, bs);
        return bs;
      }
      if (setName.equals("water")) {
        BS bs = vwr.ms.getAtoms(T.solvent, null);
        if (!isDynamic)
          vwr.definedAtomSets.put(setName, bs);
        return bs;
      }
      if (plurals)
        return null;
      int len = setName.length();
      if (len < 5) // iron is the shortest
        return null;
      if (setName.charAt(len - 1) != 's')
        return null;
      if (setName.endsWith("ies"))
        setName = setName.substring(0, len - 3) + 'y';
      else
        setName = setName.substring(0, len - 1);
      return lookupValue(setName, true);
    }
  
    @Override
    public void deleteAtomsInVariables(BS bsDeleted) {
      for (Map.Entry<String, Object> entry : vwr.definedAtomSets.entrySet()) {
        Object value = entry.getValue();
        if (value instanceof BS) {
          BSUtil.deleteBits((BS) value, bsDeleted);
          if (!entry.getKey().startsWith("!"))
            vwr
                .g.setUserVariable("@" + entry.getKey(), SV.newV(T.bitset, value));
        }
      }
    }
    
    
    // ///////////////// Script context support //////////////////////
  
   @Override
    public ScriptContext getThisContext() {
      return thisContext;
    }
  
    private void clearState(boolean tQuiet) {
      thisContext = null;
      scriptLevel = 0;
      setErrorMessage(null);
      contextPath = "";
      this.tQuiet = tQuiet;
    }
  
    @Override
    public void pushContextDown(String why) {
      scriptLevel--;
      pushContext2(null, why);
    }
  
    private void pushContext(ContextToken token, String why)
        throws ScriptException {
      if (scriptLevel == contextDepthMax)
        error(ERROR_tooManyScriptLevels);
      pushContext2(token, why);
    }
  
    private void pushContext2(ContextToken token, String why) {
      thisContext = getScriptContext(why);
      thisContext.token = token;
      if (token == null) {
        scriptLevel = ++thisContext.scriptLevel;
      } else {
        thisContext.scriptLevel = -1;
        contextVariables = new Hashtable<String, SV>();
        if (token.contextVariables != null)
          for (String key : token.contextVariables.keySet())
            ScriptCompiler.addContextVariable(contextVariables, key);
      }
      if (debugHigh || isCmdLine_c_or_C_Option)
        Logger.info("-->>----------------------".substring(0,
            Math.min(15, scriptLevel + 5))
            + scriptLevel
            + " "
            + scriptFileName
            + " "
            + token
            + " "
            + thisContext.id + " " + why + " path=" + thisContext.contextPath);
    }
  
    @Override
    public ScriptContext getScriptContext(String why) {
      ScriptContext context = new ScriptContext();
      if (debugHigh)
        Logger.info("creating context " + context.id + " for " + why + " path=" + contextPath);
      context.scriptLevel = scriptLevel;
      context.parentContext = thisContext;
      context.contextPath = contextPath;
      context.scriptFileName = scriptFileName;
      context.parallelProcessor = parallelProcessor;
      context.functionName = functionName;
      context.script = script;
      context.lineNumbers = lineNumbers;
      context.lineIndices = lineIndices;    
      context.saveTokens(aatoken);
      context.statement = st;
      context.statementLength = slen;
      context.pc = context.pc0 = pc;
      context.lineEnd = lineEnd;
      context.pcEnd = pcEnd;
      context.iToken = iToken;
      context.theToken = theToken;
      context.theTok = theTok;
      context.outputBuffer = outputBuffer;
      context.vars = contextVariables;
      context.isStateScript = isStateScript;
  
      context.errorMessage = errorMessage;
      context.errorType = errorType;
      context.iCommandError = iCommandError;
      context.chk = chk;
      context.executionStepping = executionStepping;
      context.executionPaused = executionPaused;
      context.scriptExtensions = scriptExtensions;
  
      context.mustResumeEval = mustResumeEval;
      context.allowJSThreads = allowJSThreads;
      return context;
    }
  
    void popContext(boolean isFlowCommand, boolean statementOnly) {
      if (thisContext == null)
        return;
      if (thisContext.scriptLevel > 0)
        scriptLevel = thisContext.scriptLevel - 1;
      // we must save (and thus NOT restore) the current statement
      // business when doing push/pop for commands like FOR and WHILE
      ScriptContext scTemp = (isFlowCommand ? getScriptContext("popFlow") : null);
      restoreScriptContext(thisContext, true, isFlowCommand, statementOnly);
      if (scTemp != null)
        restoreScriptContext(scTemp, true, false, true);
      if (debugHigh || isCmdLine_c_or_C_Option)
        Logger.info("--<<------------".substring(0,
            Math.min(15, scriptLevel + 5))
            + (scriptLevel + 1)
            + " "
            + scriptFileName
            + " isFlow "
            + isFlowCommand
            + " thisContext="
            + (thisContext == null ? "" : "" + thisContext.id)
            + " pc=" + pc + "-->" + pc + " path=" + (thisContext == null ? "" : thisContext.contextPath));
    }
  
    public void restoreScriptContext(ScriptContext context, boolean isPopContext,
                                     boolean isFlowCommand, boolean statementOnly) {
  
      executing = !chk;
      if (context == null)
        return;
      if (!isFlowCommand) {
        st = context.statement;
        slen = context.statementLength;
        pc = context.pc;
        lineEnd = context.lineEnd;
        pcEnd = context.pcEnd;
        if (statementOnly)
          return;
      }
      mustResumeEval = context.mustResumeEval;
      script = context.script;
      lineNumbers = context.lineNumbers;
      lineIndices = context.lineIndices;
      aatoken = context.restoreTokens();
      contextVariables = context.vars;
      scriptExtensions = context.scriptExtensions;
  
      if (isPopContext) {
        contextPath = context.contextPath;
        int pt = (contextPath == null ? -1 : contextPath.indexOf(" >> "));
        if (pt >= 0)
          contextPath = contextPath.substring(0, pt);      
        scriptFileName = context.scriptFileName;
        parallelProcessor = context.parallelProcessor;
        functionName = context.functionName;
        iToken = context.iToken;
        theToken = context.theToken;
        theTok = context.theTok;
  
        outputBuffer = context.outputBuffer;
        isStateScript = context.isStateScript;
        thisContext = context.parentContext;
        allowJSThreads = context.allowJSThreads;
        if (debugHigh || isCmdLine_c_or_C_Option)
          Logger.info("--r------------".substring(0,
              Math.min(15, scriptLevel + 5))
              + scriptLevel
              + " "
              + scriptFileName
              + " isPop "
              + isPopContext
              + " isFlow "
              + isFlowCommand
              + " context.id="
              + context.id + " pc=" + pc + "-->" + context.pc + " " + contextPath);
      } else {
        error = (context.errorType != null);
        //isComplete = context.isComplete;
        errorMessage = context.errorMessage;
        errorMessageUntranslated = context.errorMessageUntranslated;
        iCommandError = context.iCommandError;
        errorType = context.errorType;
      }
    }
  
    // /////////////// error message support /////////////////
  
    public void setException(ScriptException sx, String msg, String untranslated) {
      // from ScriptException, while initializing
      sx.untranslated = (untranslated == null ? msg : untranslated);
      boolean isThrown = "!".equals(untranslated);
      errorType = msg;
      iCommandError = pc;
      if (sx.message == null) {
        sx.message = "";
        return;
      }
      String s = ScriptEval.getContextTrace(vwr,
          getScriptContext("setException"), null, true).toString();
      while (thisContext != null && !thisContext.isTryCatch)
        popContext(false, false);
      if (sx.message.indexOf(s) < 0) {
        sx.message += s;
        sx.untranslated += s;
      }
      resumeViewer(isThrown ? "throw context" : "scriptException");
      if (isThrown || thisContext != null || chk
          || msg.indexOf(JC.NOTE_SCRIPT_FILE) >= 0)
        return;
      Logger.error("eval ERROR: " + s + toString());
      if (vwr.autoExit)
        vwr.exitJmol();
    }
  
    @SuppressWarnings("unchecked")
    public static String statementAsString(Viewer vwr, T[] statement,
                                           int iTok, boolean doLogMessages) {
      if (statement.length == 0)
        return "";
      SB sb = new SB();
      int tok = statement[0].tok;
      switch (tok) {
      case T.nada:
        return (String) statement[0].value;
      case T.end:
        if (statement.length == 2
            && (statement[1].tok == T.function || statement[1].tok == T.parallel))
          return ((ScriptFunction) (statement[1].value)).toString();
      }
      boolean useBraces = true;// (!Token.tokAttr(tok,
      // Token.atomExpressionCommand));
      boolean inBrace = false;
      boolean inClauseDefine = false;
      boolean setEquals = (statement.length > 1 && tok == T.set
          && statement[0].value.equals("")
          && (statement[0].intValue == '=' || statement[0].intValue == '#') && statement[1].tok != T.expressionBegin);
      int len = statement.length;
      for (int i = 0; i < len; ++i) {
        T token = statement[i];
        if (token == null) {
          len = i;
          break;
        }
        if (iTok == i - 1)
          sb.append(" <<");
        if (i != 0)
          sb.appendC(' ');
        if (i == 2 && setEquals) {
          if ((setEquals = (token.tok != T.opEQ)) || statement[0].intValue == '#') {
            sb.append(setEquals ? "= " : "== ");
            if (!setEquals)
              continue;
          }
        }
        if (iTok == i && token.tok != T.expressionEnd)
          sb.append("<<<<");
        switch (token.tok) {
        case T.expressionBegin:
          if (useBraces)
            sb.append("{");
          continue;
        case T.expressionEnd:
          if (inClauseDefine && i == statement.length - 1)
            useBraces = false;
          if (useBraces)
            sb.append("}");
          continue;
        case T.leftsquare:
        case T.rightsquare:
          break;
        case T.leftbrace:
        case T.rightbrace:
          inBrace = (token.tok == T.leftbrace);
          break;
        case T.define:
          if (i > 0 && ((String) token.value).equals("define")) {
            sb.append("@");
            if (i + 1 < statement.length
                && statement[i + 1].tok == T.expressionBegin) {
              if (!useBraces)
                inClauseDefine = true;
              useBraces = true;
            }
            continue;
          }
          break;
        case T.on:
          sb.append("true");
          continue;
        case T.off:
          sb.append("false");
          continue;
        case T.select:
          break;
        case T.integer:
          sb.appendI(token.intValue);
          continue;
        case T.point3f:
        case T.point4f:
        case T.bitset:
          sb.append(SV.sValue(token)); // list
          continue;
        case T.hash:
           if (Boolean.TRUE == ((Map<String, Object>)token.value).get("$_BINARY_$")) {
             sb.append("<BINARY DATA>");
             continue;
           }          
          //$FALL-THROUGH$
        case T.varray:
          sb.append(((SV) token).escape()); // list
          continue;
        case T.inscode:
          sb.appendC('^');
          continue;
        case T.spec_seqcode_range:
          if (token.intValue != Integer.MAX_VALUE)
            sb.appendI(token.intValue);
          else
            sb.append(Group.getSeqcodeStringFor(getSeqCode(token)));
          token = statement[++i];
          sb.appendC(' ');
          // if (token.intValue == Integer.MAX_VALUE)
          sb.append(inBrace ? "-" : "- ");
          //$FALL-THROUGH$
        case T.spec_seqcode:
          if (token.intValue != Integer.MAX_VALUE)
            sb.appendI(token.intValue);
          else
            sb.append(Group.getSeqcodeStringFor(getSeqCode(token)));
          continue;
        case T.spec_chain:
          sb.append("*:");
          sb.append(vwr.getChainIDStr(token.intValue));
          continue;
        case T.spec_alternate:
          sb.append("*%");
          if (token.value != null)
            sb.append(token.value.toString());
          continue;
        case T.spec_model:
          sb.append("*/");
          //$FALL-THROUGH$
        case T.spec_model2:
        case T.decimal:
          if (token.intValue < Integer.MAX_VALUE) {
            sb.append(Escape.escapeModelFileNumber(token.intValue));
          } else {
            sb.append("" + token.value);
          }
          continue;
        case T.spec_resid:
          sb.appendC('[');
          int ptr = token.intValue * 6 + 1;
          sb.append(Group.standardGroupList.substring(ptr, ptr + 3).trim());
          sb.appendC(']');
          continue;
        case T.spec_name_pattern:
          sb.appendC('[');
          sb.appendO(token.value);
          sb.appendC(']');
          continue;
        case T.spec_atom:
          sb.append("*.");
          break;
        case T.cell:
          if (token.value instanceof P3) {
            P3 pt = (P3) token.value;
            sb.append("cell=").append(Escape.eP(pt));
            continue;
          }
          break;
        case T.string:
          sb.append("\"").appendO(token.value).append("\"");
          continue;
        case T.opEQ:
        case T.opLE:
        case T.opGE:
        case T.opGT:
        case T.opLT:
        case T.opNE:
          // not quite right -- for "inmath"
          if (token.intValue == T.property) {
            sb.append((String) statement[++i].value).append(" ");
          } else if (token.intValue != Integer.MAX_VALUE)
            sb.append(T.nameOf(token.intValue)).append(" ");
          break;
        case T.trycmd:
          continue;
        case T.end:
          sb.append("end");
          continue;
        default:
          if (T.tokAttr(token.tok, T.identifier) || !doLogMessages)
            break;
          sb.appendC('\n').append(token.toString()).appendC('\n');
          continue;
        }
        if (token.value != null)
          sb.append(token.value.toString());
      }
  //    if (iTok >= len - 1 && iTok != 9999)
  //      sb.append(" <<");
      return sb.toString();
    }
  
    ///////////// shape get/set properties ////////////////
  
    /**
     * called by Viewer in setting up a PyMOL scene.
     */
    @Override
    public String setObjectPropSafe(String id, int tokCommand) {
      try {
        return setObjectProp(id, tokCommand, -1);
      } catch (ScriptException e) {
        return null;
      }
    }
  
    @Override
    public void restrictSelected(boolean isBond, boolean doInvert) {
      
      // called by ScriptParam
      
      if (!chk)
        sm.restrictSelected(isBond, doInvert);
    }
  
    //////////////////// showing strings /////////////////
  
    @Override
    public void showString(String str) {
      // called by ScriptExt and ScriptError
      showStringPrint(str, false);
    }
  
    public void showStringPrint(String str, boolean isPrint) {
      if (chk || str == null)
        return;
      if (outputBuffer != null && Logger.isActiveLevel(Logger.LEVEL_WARN))
        outputBuffer.append(str).appendC('\n');
      else
        vwr.showString(str, isPrint);
    }
  
    public void report(String s) {
      if (chk)
        return;
      if (outputBuffer != null) {
        outputBuffer.append(s).appendC('\n');
        return;
      }
      vwr.scriptStatus(s);
    }
  
    /*
     * ****************************************************************
     * =============== command processing checks ===============================
     */
  
    private void addProcess(Lst<T[]> vProcess, int pc, int pt) {
      if (parallelProcessor == null)
        return;
      T[][] statements = new T[pt][];
      for (int i = 0; i < vProcess.size(); i++)
        statements[i + 1 - pc] = vProcess.get(i);
      ScriptContext context = getScriptContext("addProcess");
      context.saveTokens(statements);
      context.pc = 1 - pc;
      context.pcEnd = pt;
      parallelProcessor.addProcess("p" + (++iProcess), context);
    }
  
    /**
     * checks to see if there is a pause condition, during which commands can
     * still be issued, but with the ! first.
     * 
     * @return false if there was a problem
     * @throws ScriptException
     */
    private boolean checkContinue() throws ScriptException {
      if (executionStopped)
        return false;
      if (executionStepping && isCommandDisplayable(pc)) {
        vwr.setScriptStatus("Next: " + getNextStatement(),
            "stepping -- type RESUME to continue", 0, null);
        executionPaused = true;
      } else if (!executionPaused) {
        return true;
      }
      if (Logger.debugging) {
        Logger.debug("script execution paused at command " + (pc + 1) + " level "
            + scriptLevel + ": " + thisCommand);
      }
      refresh(false);
      while (executionPaused) {
        vwr.popHoldRepaint("pause " + JC.REPAINT_IGNORE);
        // does not actually do a repaint
        // but clears the way for interaction
        String script = vwr.getInsertedCommand();
        if (script.length() > 0) {
          resumePausedExecution();
          setErrorMessage(null);
          ScriptContext scSave = getScriptContext("script insertion");
          pc--; // in case there is an error, we point to the PAUSE command
          try {
            runScript(script);
          } catch (Exception e) {
            setErrorMessage("" + e);
          } catch (Error er) {
            setErrorMessage("" + er);
          }
          if (error) {
            report(errorMessage);
            setErrorMessage(null);
          }
          restoreScriptContext(scSave, true, false, false);
          pauseExecution(false);
        }
        doDelay(ScriptDelayThread.PAUSE_DELAY);
        // JavaScript will not reach this point, 
        // but no need to pop anyway, because
        // we will be out of this thread.
        vwr.pushHoldRepaintWhy("pause");
      }
      notifyResumeStatus();
      // once more to trap quit during pause
      return !error && !executionStopped;
    }
  
    public void delayScript(int millis) {
      if (vwr.autoExit)
        return;
      stopScriptThreads();
      scriptDelayThread = new ScriptDelayThread(this, vwr, millis);
      scriptDelayThread.run();
    }
  
    /**
     * 
     * @param millis
     *        negative here bypasses max check
     * @throws ScriptException
     */
    private void doDelay(int millis) throws ScriptException {
      if (!useThreads())
        return;
      if (isJS)
        throw new ScriptInterruption(this, "delay", millis);
      delayScript(millis);
    }
  
    @Override
    public boolean evalParallel(ScriptContext context,
                                    ShapeManager shapeManager) {
      
      // now in ScriptExt
      
      return getCmdExt().evalParallel(context, shapeManager);
    }
  
    /**
     * provides support for the script editor
     * 
     * @param i
     * @return true if displayable
     */
    private boolean isCommandDisplayable(int i) {
      if (i >= aatoken.length || i >= pcEnd || aatoken[i] == null)
        return false;
      return (lineIndices[i][1] > lineIndices[i][0]);
    }
  
    /**
     * load a static file asynchronously
     */
    @Override
    public void loadFileResourceAsync(String fileName) throws ScriptException {
      loadFileAsync(null, fileName, -Math.abs(fileName.hashCode()), false);    
    }
    
    /**
     * Allows asynchronous file loading from the LOAD or SCRIPT command. Saves the
     * context, initiates a FileLoadThread instance. When the file loading
     * completes, the file data (sans filename) is saved in the FileManager cache
     * under cache://localLoad_xxxxx. Context is resumed at this command in the
     * script, and the file is then retrieved from the cache. Only run from
     * JSmol/HTML5 when vwr.isJS;
     * 
     * Incompatibilities:
     * 
     * LOAD and SCRIPT commands, load() function only;
     * 
     * only one "?" per LOAD command
     * 
     * @param prefix
     * @param filename
     *        or null if end of LOAD command and now just clearing out cache
     * @param i
     * @param doClear
     *        ensures only one file is in the cache for a given type
     * @return cached file name if it exists
     * @throws ScriptException
     */
    public String loadFileAsync(String prefix, String filename, int i,
                                boolean doClear) throws ScriptException {
      // note that we will never know the actual file name
      // so we construct one and point to it in the scriptContext
      // with a key to this point in the script. 
      
      if (vwr.fm.cacheGet(filename, false) != null)
        return filename;
      if (prefix != null)
        prefix = "cache://local" + prefix;
      String key = pc + "_" + i + "_" + filename;
      String cacheName;
      if (thisContext == null || thisContext.htFileCache == null) {
        pushContext(null, "loadFileAsync");
        thisContext.htFileCache = new Hashtable<String, String>();
      }
      cacheName = thisContext.htFileCache.get(key);
      if (cacheName != null && cacheName.length() > 0) {
        // file has been loaded
        fileLoadThread = null;
        popContext(false, false);
        vwr.queueOnHold = false;
        if ("#CANCELED#".equals(cacheName) || "#CANCELED#".equals(vwr.fm.cacheGet(cacheName, false)))
          evalError("#CANCELED#", null);
        return cacheName;
      }
      thisContext.htFileCache.put(key,
          cacheName = prefix + System.currentTimeMillis());
  //    if (fileLoadThread != null && i >= 0)
  //      evalError("#CANCELED#", null);
      if (doClear)
        vwr.cacheFileByName(prefix + "*", false);
      fileLoadThread = new FileLoadThread(this, vwr, filename, key, cacheName);
      if (vwr.testAsync)
        fileLoadThread.start();
      else
        fileLoadThread.run();
      if (i < 0) // no need to hang on to this - never "canceled"
        fileLoadThread = null;
      throw new ScriptInterruption(this, "load", 1);
    }
  
    @SuppressWarnings("unchecked")
    private void logLoadInfo(String msg) {
      if (msg.length() > 0)
        Logger.info(msg);
      SB sb = new SB();
      int modelCount = vwr.ms.mc;
      if (modelCount > 1)
        sb.append((vwr.am.isMovie ? vwr.am.getFrameCount() + " frames"
            : modelCount + " models") + "\n");
      for (int i = 0; i < modelCount; i++) {
        Map<String, Object> moData = (Map<String, Object>) vwr
            .ms.getInfo(i, "moData");
        if (moData == null || !moData.containsKey("mos"))
          continue;
        sb.appendI(((Lst<Map<String, Object>>) moData.get("mos")).size())
            .append(" molecular orbitals in model ")
            .append(vwr.getModelNumberDotted(i)).append("\n");
      }
      if (sb.length() > 0)
       vwr.showString(sb.toString(), false);
    }
  
    @Override
    public void notifyResumeStatus() {
      if (!chk && !executionStopped && !executionStepping) {
        vwr.scriptStatus("script execution "
            + (error || executionStopped ? "interrupted" : "resumed"));
      }
      if (Logger.debugging)
        Logger.debug("script execution resumed");
    }
  
    /**
     * Refresh the display NOW
     * 
     */
    @Override
    public void refresh(boolean doDelay) throws ScriptException {
      if (chk)
        return;
      vwr.setTainted(true);
      vwr.requestRepaintAndWait("refresh cmd");
      if (isJS && doDelay)
        doDelay(10); // need this to update JavaScript display
    }
  
    @Override
    public void stopScriptThreads() {
      if (scriptDelayThread != null) {
        scriptDelayThread.interrupt();
        scriptDelayThread = null;
      }
      if (fileLoadThread != null) {
        fileLoadThread.interrupt();
        fileLoadThread.resumeEval();
        if (thisContext != null)
          this.popContext(false, false);
        fileLoadThread = null;
      }
    }
    
    // from ScriptExt 
  
    public String getErrorLineMessage2() {
      return getErrorLineMessage(functionName, scriptFileName,
          getLinenumber(null), pc,
          statementAsString(vwr, st, -9999, debugHigh));
    }
    
    public int getLinenumber(ScriptContext c) {
      return (c == null ? lineNumbers[pc] : c.lineNumbers[c.pc]);
    }
  
    
    ///////////////////////////////////////////////////////////////////////
    ///////////////////////// Jmol script commands ////////////////////////
    ///////////////////////////////////////////////////////////////////////
    
    /**
     * 
     * @param isSpt
     * @param fromFunc
     * @param isTry 
     * @return false only when still working through resumeEval
     * @throws ScriptException
     */
    public boolean dispatchCommands(boolean isSpt, boolean fromFunc, boolean isTry)
        throws ScriptException {
      if (sm == null)
        sm = vwr.shm;
      debugScript = debugHigh = false;
      if (!chk)
        setDebugging();
      if (pcEnd == 0)
        pcEnd = Integer.MAX_VALUE;
      if (lineEnd == 0)
        lineEnd = Integer.MAX_VALUE;
      if (aatoken == null)
        return true;
      if (!tQuiet) {
        tQuiet = (vwr.getInt(T.showscript) < 0);
      }
      boolean allowJSInterrupt = (
          isJS 
          && !fromFunc 
          && useThreads() 
          && vwr.getInt(T.showscript) >= 0);
      commandLoop(allowJSInterrupt);
      if (chk)
        return true;
      String script = vwr.getInsertedCommand();
      if (!"".equals(script))
        runScriptBuffer(script, null);
      else if (isSpt && debugScript && vwr.getBoolean(T.messagestylechime))
        vwr.getChimeMessenger().update(null);
      if (!mustResumeEval && !allowJSInterrupt || fromFunc)
        return true;
      if (!isTry && mustResumeEval || thisContext == null) {
        boolean done = (thisContext == null);
        resumeEval(thisContext);
        mustResumeEval = false;
        return done;
      }
      return true;
    }
  
    private void commandLoop(boolean allowJSInterrupt) throws ScriptException {
      String lastCommand = "";
      boolean isForCheck = false; // indicates the stage of the for command loop
      Lst<T[]> vProcess = null;
      long lastTime = System.currentTimeMillis();
  
      if (debugScript && debugHigh && !chk) {
        for (int i = pc; i < aatoken.length && i < pcEnd; i++) {
          Logger.info("Command " + i);
          if (debugScript)
            logDebugScript(aatoken[i], 0);
        }
        Logger.info("-----");
      }
  
      for (; pc < aatoken.length && pc < pcEnd; pc++) {
        if (allowJSInterrupt) {
          // every 1 s check for interruptions
          if (!executionPaused && System.currentTimeMillis() - lastTime > 1000) {
            pc--;
            doDelay(-1);
          }
          lastTime = System.currentTimeMillis();
        }
        if (!chk && !checkContinue())
          break;
        if (pc >= lineNumbers.length || lineNumbers[pc] > lineEnd)
          break;
        if (debugHigh) {
          long timeBegin = 0;
          timeBegin = System.currentTimeMillis();
          vwr.scriptStatus("Eval.dispatchCommands():" + timeBegin);
          vwr.scriptStatus(script);
        }
  
        if (debugScript && !chk)
          Logger.info("Command " + pc
              + (thisContext == null ? "" : " path=" + thisContext.contextPath));
        theToken = (aatoken[pc].length == 0 ? null : aatoken[pc][0]);
        // when checking scripts, we can't check statments
        // containing @{...}
        if (!historyDisabled && !chk && scriptLevel <= commandHistoryLevelMax
            && !tQuiet) {
          String cmdLine = getCommand(pc, true, true);
          if (theToken != null
              && cmdLine.length() > 0
              && !cmdLine.equals(lastCommand)
              && (theToken.tok == T.function || theToken.tok == T.parallel || !T
                  .tokAttr(theToken.tok, T.flowCommand)))
            vwr.addCommand(lastCommand = cmdLine);
        }
        if (!chk && allowJSInterrupt) {
          String script = vwr.getInsertedCommand();
          if (!"".equals(script))
            runScript(script);
        }
        if (!setStatement(aatoken[pc], 1)) {
          Logger.info(getCommand(pc, true, false)
              + " -- STATEMENT CONTAINING @{} SKIPPED");
          continue;
        }
        thisCommand = getCommand(pc, false, true);
        if (debugHigh || debugScript)
          Logger.info(thisCommand);
        String nextCommand = getCommand(pc + 1, false, true);
        fullCommand = thisCommand
            + (nextCommand.startsWith("#") ? nextCommand : "");
        getToken(0);
        iToken = 0;
        if ((listCommands || !chk && scriptLevel > 0) && !isJS) {
          int milliSecDelay = vwr.getInt(T.showscript);
          if (listCommands || milliSecDelay > 0) {
            if (milliSecDelay > 0)
              delayScript(-milliSecDelay);
            vwr.scriptEcho("$[" + scriptLevel + "." + lineNumbers[pc] + "."
                + (pc + 1) + "] " + thisCommand);
          }
        }
        if (vProcess != null
  