Package org.jmol.script

Source Code of org.jmol.script.ScriptEvaluator

/* $RCSfile$
* $Author: hansonr $
* $Date: 2011-06-22 16:45:54 +0200 (mer., 22 juin 2011) $
* $Revision: 15635 $
*
* 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.awt.Image;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.vecmath.AxisAngle4f;
import javax.vecmath.Matrix3f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3f;
import javax.vecmath.Point4f;
import javax.vecmath.Tuple3f;
import javax.vecmath.Vector3f;

import org.jmol.api.JmolEdge;
import org.jmol.api.MinimizerInterface;
import org.jmol.api.SymmetryInterface;
import org.jmol.atomdata.RadiusData;
import org.jmol.g3d.Font3D;
import org.jmol.g3d.Graphics3D;
import org.jmol.i18n.GT;
import org.jmol.modelset.Atom;
import org.jmol.modelset.AtomCollection;
import org.jmol.modelset.Bond;
import org.jmol.modelset.BoxInfo;
import org.jmol.modelset.Group;
import org.jmol.modelset.LabelToken;
import org.jmol.modelset.MeasurementData;
import org.jmol.modelset.ModelCollection;
import org.jmol.modelset.ModelSet;
import org.jmol.modelset.Bond.BondSet;
import org.jmol.modelset.ModelCollection.StateScript;
import org.jmol.shape.Object2d;
import org.jmol.shape.Shape;
import org.jmol.util.BitSetUtil;
import org.jmol.util.ColorEncoder;
import org.jmol.util.Escape;

import org.jmol.util.Elements;
import org.jmol.util.Logger;
import org.jmol.util.Measure;
import org.jmol.util.Parser;
import org.jmol.util.Point3fi;
import org.jmol.util.Quaternion;
import org.jmol.util.SurfaceFileTyper;
import org.jmol.util.TextFormat;
import org.jmol.modelset.TickInfo;
import org.jmol.viewer.ActionManager;
import org.jmol.viewer.FileManager;
import org.jmol.viewer.JmolConstants;
import org.jmol.viewer.PropertyManager;
import org.jmol.viewer.ShapeManager;
import org.jmol.viewer.StateManager;
import org.jmol.viewer.Viewer;

public class ScriptEvaluator {

  /*
   * 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.
   *
   * 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
   */

  public static final String SCRIPT_COMPLETED = "Script completed";

  public ScriptEvaluator(Viewer viewer) {
    this.viewer = viewer;
    compiler = viewer.compiler;
    definedAtomSets = viewer.definedAtomSets;
  }

  // //////////////// primary interfacing methods //////////////////

  /*
   * see Viewer.evalStringWaitStatus for how these are implemented
   */
  public boolean compileScriptString(String script, boolean tQuiet) {
    clearState(tQuiet);
    contextPath = "[script]";
    return compileScript(null, script, debugScript);
  }

  public boolean compileScriptFile(String filename, boolean tQuiet) {
    clearState(tQuiet);
    contextPath = filename;
    return compileScriptFileInternal(filename, null, null, null);
  }

  public void evaluateCompiledScript(boolean isCmdLine_c_or_C_Option,
                                     boolean isCmdLine_C_Option,
                                     boolean historyDisabled,
                                     boolean listCommands, StringBuffer outputBuffer) {
    boolean tempOpen = this.isCmdLine_C_Option;
    this.isCmdLine_C_Option = isCmdLine_C_Option;
    viewer.pushHoldRepaint("runEval");
    interruptExecution = executionPaused = false;
    executionStepping = false;
    isExecuting = true;
    currentThread = Thread.currentThread();
    isSyntaxCheck = this.isCmdLine_c_or_C_Option = isCmdLine_c_or_C_Option;
    timeBeginExecution = System.currentTimeMillis();
    this.historyDisabled = historyDisabled;
    this.outputBuffer = outputBuffer;
    setErrorMessage(null);
    try {
      try {
        setScriptExtensions();
        instructionDispatchLoop(listCommands);
        String script = viewer.getInterruptScript();
        if (script != "")
          runScript(script, null);
      } catch (Error er) {
        viewer.handleError(er, false);
        setErrorMessage("" + er + " " + viewer.getShapeErrorState());
        errorMessageUntranslated = "" + er;
        scriptStatusOrBuffer(errorMessage);
      }
    } catch (ScriptException e) {
      setErrorMessage(e.toString());
      errorMessageUntranslated = e.getErrorMessageUntranslated();
      scriptStatusOrBuffer(errorMessage);
      viewer.notifyError((errorMessage != null
          && errorMessage.indexOf("java.lang.OutOfMemoryError") >= 0 ? "Error"
          : "ScriptException"), errorMessage, errorMessageUntranslated);
    }
    timeEndExecution = System.currentTimeMillis();
    this.isCmdLine_C_Option = tempOpen;
    if (errorMessage == null && interruptExecution)
      setErrorMessage("execution interrupted");
    else if (!tQuiet && !isSyntaxCheck)
      viewer.scriptStatus(SCRIPT_COMPLETED);
    isExecuting = isSyntaxCheck = isCmdLine_c_or_C_Option = historyDisabled = false;
    viewer.setTainted(true);
    viewer.popHoldRepaint("runEval");
  }

  /**
   * runs a script and sends selected output to a provided StringBuffer
   *
   * @param script
   * @param outputBuffer
   * @throws ScriptException
   */
  public void runScript(String script, StringBuffer outputBuffer)
      throws ScriptException {
    // a = script("xxxx")
    pushContext(null);
    contextPath += " >> script() ";

    this.outputBuffer = outputBuffer;
    if (compileScript(null, script + JmolConstants.SCRIPT_EDITOR_IGNORE, false))
      instructionDispatchLoop(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
   *
   */
  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);
    isSyntaxCheck = true;
    isCmdLine_c_or_C_Option = isCmdLine_C_Option = false;
    pc = 0;
    try {
      instructionDispatchLoop(false);
    } catch (ScriptException e) {
      setErrorMessage(e.toString());
      sc = getScriptContext();
    }
    isSyntaxCheck = false;
    return sc;
  }

  // //////////////////////// script execution /////////////////////

  private boolean tQuiet;
  protected boolean isSyntaxCheck;
  private boolean isCmdLine_C_Option;
  protected boolean isCmdLine_c_or_C_Option;
  private boolean historyDisabled;
  protected boolean logMessages;
  private boolean debugScript;

  public void setDebugging() {
    debugScript = viewer.getDebugScript();
    logMessages = (debugScript && Logger.debugging);
  }

  private boolean interruptExecution;
  private boolean executionPaused;
  private boolean executionStepping;
  private boolean isExecuting;

  private long timeBeginExecution;
  private long timeEndExecution;

  public int getExecutionWalltime() {
    return (int) (timeEndExecution - timeBeginExecution);
  }

  public void haltExecution() {
    resumePausedExecution();
    interruptExecution = true;
  }

  public void pauseExecution(boolean withDelay) {
    if (isSyntaxCheck)
      return;
    if (withDelay)
      delay(-100);
    viewer.popHoldRepaint("pauseExecution");
    executionStepping = false;
    executionPaused = true;
  }

  public void stepPausedExecution() {
    executionStepping = true;
    executionPaused = false;
    // releases a paused thread but
    // sets it to pause for the next command.
  }

  public void resumePausedExecution() {
    executionPaused = false;
    executionStepping = false;
  }

  public boolean isScriptExecuting() {
    return isExecuting && !interruptExecution;
  }

  public boolean isExecutionPaused() {
    return executionPaused;
  }

  public boolean isExecutionStepping() {
    return executionStepping;
  }

  /**
   * when paused, indicates what statement will be next
   *
   * @return a string indicating the statement
   */
  public String getNextStatement() {
    return (pc < aatoken.length ? setErrorLineMessage(functionName, filename,
        getLinenumber(null), pc, statementAsString(aatoken[pc], -9999,
            logMessages)) : "");
  }

  /**
   * 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;
        }
      if (pt1 == script.length() - 1 && script.endsWith("}"))
        pt1++;
      return (pt0 == script.length() || pt1 < pt0 ? "" : script.substring(Math
          .max(pt0, 0), Math.min(script.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 = TextFormat.simpleReplace(s, "\\\n", "  ");
      if (s.indexOf("\\\r") >= 0)
        s = TextFormat.simpleReplace(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(int ifLevel) {
    if (logMessages) {
      if (statement.length > 0)
        Logger.debug(statement[0].toString());
      for (int i = 1; i < statementLength; ++i)
        Logger.debug(statement[i].toString());
    }
    iToken = -9999;
    if (logMessages) {
      StringBuffer strbufLog = new StringBuffer(80);
      String s = (ifLevel > 0 ? "                          ".substring(0,
          ifLevel * 2) : "");
      strbufLog.append(s).append(
          statementAsString(statement, iToken, logMessages));
      viewer.scriptStatus(strbufLog.toString());
    } else {
      String cmd = getCommand(pc, false, false);
      if (cmd != "")
        viewer.scriptStatus(cmd);
    }

  }

  // /////////////// string-based evaluation support /////////////////////

  private final static String EXPRESSION_KEY = "e_x_p_r_e_s_s_i_o_n";

  /**
   * a general-use method to evaluate a "SET" type expression.
   *
   * @param viewer
   * @param expr
   * @return an object of one of the following types: Boolean, Integer, Float,
   *         String, Point3f, BitSet
   */

  public static Object evaluateExpression(Viewer viewer, Object expr) {
    // Text.formatText for MESSAGE and ECHO
    ScriptEvaluator e = new ScriptEvaluator(viewer);
    try {
      if (expr instanceof String) {
        if (e.compileScript(null, EXPRESSION_KEY + " = " + expr, false)) {
          e.contextVariables = viewer.getContextVariables();
          e.setStatement(0);
          return e.parameterExpressionString(2, 0);
        }
      } else if (expr instanceof Token[]) {
        e.contextVariables = viewer.getContextVariables();
        return e.atomExpression((Token[]) expr, 0, 0, true, false, true, false);
      }
    } catch (Exception ex) {
      Logger.error("Error evaluating: " + expr + "\n" + ex);
    }
    return "ERROR";
  }

  ShapeManager shapeManager;

  public static boolean evaluateContext(Viewer viewer, ScriptContext context,
                                        ShapeManager shapeManager) {
    ScriptEvaluator e = new ScriptEvaluator(viewer);
    e.compiler = new ScriptCompiler(e.compiler);
    e.shapeManager = shapeManager;
    try {
      e.restoreScriptContext(context, true, false, false);
      e.instructionDispatchLoop(false);
    } catch (Exception ex) {
      viewer.setStringProperty("_errormessage", "" + ex);
      Logger.error("Error evaluating context");
      //ex.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   * a general method to evaluate a string representing an atom set.
   *
   * @param e
   * @param atomExpression
   * @return is a bitset indicating the selected atoms
   *
   */
  public static BitSet getAtomBitSet(ScriptEvaluator e, Object atomExpression) {
    if (atomExpression instanceof BitSet)
      return (BitSet) atomExpression;
    BitSet bs = new BitSet();
    try {
      e.pushContext(null);
      String scr = "select (" + atomExpression + ")";
      scr = TextFormat.replaceAllCharacters(scr, "\n\r", "),(");
      scr = TextFormat.simpleReplace(scr, "()", "(none)");
      if (e.compileScript(null, scr, false)) {
        e.statement = e.aatoken[0];
        bs = e.atomExpression(e.statement, 1, 0, false, false, true, true);
      }
      e.popContext(false, false);
    } catch (Exception ex) {
      Logger.error("getAtomBitSet " + atomExpression + "\n" + ex);
    }
    return bs;
  }

  /**
   * just provides a vector list of atoms in a string-based expression
   *
   * @param e
   * @param atomCount
   * @param atomExpression
   * @return vector list of selected atoms
   */
  public static List getAtomBitSetVector(ScriptEvaluator e, int atomCount,
                                           Object atomExpression) {
    List v = new ArrayList();
    BitSet bs = getAtomBitSet(e, atomExpression);
    for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
      v.add(new Integer(i));
    return v;
  }

  private List parameterExpressionList(int pt, boolean isArrayItem)
      throws ScriptException {
    return (List) parameterExpression(pt, -1, null, true, true, -1,
        isArrayItem, null, null);
  }

  private String parameterExpressionString(int pt, int ptMax) throws ScriptException {
    return (String) parameterExpression(pt, ptMax, "", true, false, -1, false, null, null);
  }

  private boolean parameterExpressionBoolean(int pt, int ptMax)
      throws ScriptException {
    return ((Boolean) parameterExpression(pt, ptMax, null, true, false, -1,
        false, null, null)).booleanValue();
  }
 
  private ScriptVariable parameterExpressionToken(int pt) throws ScriptException {
    return (ScriptVariable) parameterExpressionList(pt, false).get(0);
  }

  /**
   * This is the primary driver of the RPN (reverse Polish notation) expression
   * processor. It handles all math outside of a "traditional" Jmol
   * SELECT/RESTRICT context. [Object atomExpression() takes care of that, and also
   * uses the RPN class.]
   *
   * @param pt
   *          token index in statement start of expression
   * @param ptMax
   *          token index in statement end of expression
   * @param key
   *          variable name for debugging reference only -- null indicates
   *          return Boolean -- "" indicates return String
   * @param ignoreComma TODO
   * @param asVector
   *          a flag passed on to RPN;
   * @param ptAtom
   *          this is a for() or select() function with a specific atom selected
   * @param isArrayItem
   *          we are storing A[x] = ... so we need to deliver "x" as well
   * @param localVars
   *          see below -- lists all nested for(x, {exp}, select(y, {ex},...))
   *          variables
   * @param localVar
   *          x or y in above for(), select() examples
   * @return either a vector or a value, caller's choice.
   * @throws ScriptException
   *           errors are thrown directly to the Eval error system.
   */
  private Object parameterExpression(int pt, int ptMax, String key,
                                     boolean ignoreComma, boolean asVector,
                                     int ptAtom, boolean isArrayItem,
                                     Hashtable localVars, String localVar) throws ScriptException {

    /*
     * localVar is a variable designated at the beginning of the select(x,...)
     * or for(x,...) construct that will be implicitly used for properties. So,
     * for example, "atomno" will become "x.atomno". That's all it is for.
     * localVars provides a localized context variable set for a given nested
     * set of for/select.
     *
     * Note that localVars has nothing to do standard if/for/while flow
     * contexts, just these specialized functions. Any variable defined in for
     * or while is simply added to the context for a given script or function.
     * These assignments are made by the compiler when seeing a VAR keyword.
     */
    Object v, res;
    boolean isImplicitAtomProperty = (localVar != null);
    boolean isOneExpressionOnly = (pt < 0);
    boolean returnBoolean = (!asVector && key == null);
    boolean returnString = (!asVector && key != null && key.length() == 0);
    int nSquare = 0;
    if (isOneExpressionOnly)
      pt = -pt;
    int nParen = 0;
    ScriptMathProcessor rpn = new ScriptMathProcessor(this, isArrayItem,
        asVector, false);
    if (pt == 0 && ptMax == 0) // set command with v[...] = ....
      pt = 2;
    if (ptMax < pt)
      ptMax = statementLength;
    out: for (int i = pt; i < ptMax; i++) {
      v = null;
      int tok = getToken(i).tok;
      if (isImplicitAtomProperty && tokAt(i + 1) != Token.per) {
        ScriptVariable token = (localVars != null
            && localVars.containsKey(theToken.value) ? null
            : getBitsetPropertySelector(i, false));
        if (token != null) {
          rpn.addX((ScriptVariable) localVars.get(localVar));
          if (!rpn.addOp(token, (tokAt(i + 1) == Token.leftparen)))
            error(ERROR_invalidArgument);
          if ((token.intValue == Token.function || token.intValue == Token.parallel)
              && tokAt(iToken + 1) != Token.leftparen) {
            rpn.addOp(Token.tokenLeftParen);
            rpn.addOp(Token.tokenRightParen);
          }
          i = iToken;
          continue;
        }
      }
      switch (tok) {
      case Token.define:
        // @{@x} or @{@{x}}
        if (tokAt(++i) == Token.expressionBegin) {
          v = parameterExpressionToken(++i);
        } else {
          v = getParameter(ScriptVariable.sValue(statement[i]), Token.variable);
        }
        v = getParameter(ScriptVariable.sValue((ScriptVariable) v), Token.variable);
        i = iToken;
        break;
      case Token.ifcmd:
        if (getToken(++i).tok != Token.leftparen)
          error(ERROR_invalidArgument);
        if (localVars == null)
          localVars = new Hashtable();
        res = parameterExpression(++i, -1, null, ignoreComma, false, -1, false,
            localVars, localVar);
        boolean TF = ((Boolean) res).booleanValue();
        int iT = iToken;
        if (getToken(iT++).tok != Token.semicolon)
          error(ERROR_invalidArgument);
        parameterExpressionBoolean(iT, -1);
        int iF = iToken;
        if (tokAt(iF++) != Token.semicolon)
          error(ERROR_invalidArgument);
        parameterExpression(-iF, -1, null, ignoreComma, false, 1, false, localVars, localVar);
        int iEnd = iToken;
        if (tokAt(iEnd) != Token.rightparen)
          error(ERROR_invalidArgument);
        v = parameterExpression(TF ? iT : iF, TF ? iF : iEnd, "XXX", ignoreComma, false,
            1, false, localVars, localVar);
        i = iEnd;
        break;
      case Token.forcmd:
      case Token.select:
        boolean isFunctionOfX = (pt > 0);
        boolean isFor = (isFunctionOfX && tok == Token.forcmd);
        // it is important to distinguish between the select command:
        // select {atomExpression} (mathExpression)
        // and the select(dummy;{atomExpression};mathExpression) function:
        // select {*.ca} (phi < select(y; {*.ca}; y.resno = _x.resno + 1).phi)
        String dummy;
        // for(dummy;...
        // select(dummy;...
        if (isFunctionOfX) {
          if (getToken(++i).tok != Token.leftparen
              || !Token.tokAttr(getToken(++i).tok, Token.identifier))
            error(ERROR_invalidArgument);
          dummy = parameterAsString(i);
          if (getToken(++i).tok != Token.semicolon)
            error(ERROR_invalidArgument);
        } else {
          dummy = "_x";
        }
        // for(dummy;{atom expr};...
        // select(dummy;{atom expr};...
        v = parameterExpressionToken(-(++i)).value;
        if (!(v instanceof BitSet))
          error(ERROR_invalidArgument);
        BitSet bsAtoms = (BitSet) v;
        i = iToken;
        if (isFunctionOfX && getToken(i++).tok != Token.semicolon)
          error(ERROR_invalidArgument);
        // for(dummy;{atom expr};math expr)
        // select(dummy;{atom expr};math expr)
        // bsX is necessary because there are a few operations that still
        // are there for now that require it; could go, though.
        BitSet bsSelect = new BitSet();
        BitSet bsX = new BitSet();
        String[] sout = (isFor ? new String[BitSetUtil.cardinalityOf(bsAtoms)]
            : null);
        if (localVars == null)
          localVars = new Hashtable();
        bsX.set(0);
        ScriptVariable t = new ScriptVariable(bsX, 0);
        localVars.put(dummy, t.setName(dummy));
        // one test just to check for errors and get iToken
        int pt2 = -1;
        if (isFunctionOfX) {
          pt2 = i - 1;
          int np = 0;
          int tok2;
          while (np >= 0 && ++pt2 < ptMax) {
            if ((tok2 = tokAt(pt2)) == Token.rightparen)
              np--;
            else if (tok2 == Token.leftparen)
              np++;
          }
        }
        int p = 0;
        int jlast = 0;
        int j = bsAtoms.nextSetBit(0);
        if (j < 0) {
          iToken = pt2 - 1;
        } else if (!isSyntaxCheck) {
          for (; j >= 0; j = bsAtoms.nextSetBit(j + 1)) {
            if (jlast >= 0)
              bsX.clear(jlast);
            jlast = j;
            bsX.set(j);
            t.index = j;
            res = parameterExpression(i, pt2, (isFor ? "XXX" : null), ignoreComma, isFor,
                j, false, localVars, isFunctionOfX ? null : dummy);
            if (isFor) {
              if (res == null || ((List) res).size() == 0)
                error(ERROR_invalidArgument);
              sout[p++] = ScriptVariable.sValue((ScriptVariable) ((List) res)
                  .get(0));
            } else if (((Boolean) res).booleanValue()) {
              bsSelect.set(j);
            }
          }
        }
        if (isFor) {
          v = sout;
        } else if (isFunctionOfX) {
          v = bsSelect;
        } else {
          return bitsetVariableVector(bsSelect);
        }
        i = iToken + 1;
        break;
      case Token.semicolon: // for (i = 1; i < 3; i=i+1)
        break out;
      case Token.decimal:
        rpn.addXNum(ScriptVariable.getVariable(theToken.value));
        break;
      case Token.spec_seqcode:
      case Token.integer:
        rpn.addXNum(ScriptVariable.intVariable(theToken.intValue));
        break;
      // these next are for the within() command
      case Token.plane:
        if (tokAt(iToken + 1) == Token.leftparen) {
          if (!rpn.addOp(theToken, true))
            error(ERROR_invalidArgument);
          break;
        }
        rpn.addX(new ScriptVariable(theToken));
        break;
      // for within:
      case Token.atomname:
      case Token.atomtype:
      case Token.branch:
      case Token.boundbox:
      case Token.chain:
      case Token.coord:
      case Token.element:
      case Token.group:
      case Token.model:
      case Token.molecule:
      case Token.sequence:
      case Token.site:
      case Token.search:
      case Token.smiles:
      case Token.structure:
        //
      case Token.on:
      case Token.off:
      case Token.string:
      case Token.point3f:
      case Token.point4f:
      case Token.matrix3f:
      case Token.matrix4f:
      case Token.bitset:
      case Token.hash:
        rpn.addX(new ScriptVariable(theToken));
        break;
      case Token.dollarsign:
        rpn.addX(new ScriptVariable(Token.point3f, centerParameter(i)));
        i = iToken;
        break;
      case Token.leftbrace:
        if (tokAt(i + 1) == Token.string)
          v = getHash(i);
        else
          v = getPointOrPlane(i, false, true, true, false, 3, 4);
        i = iToken;
        break;
      case Token.expressionBegin:
        if (tokAt(i + 1) == Token.expressionEnd) {
          v = new Hashtable();
          i++;
          break;
        } else if (tokAt(i + 1) == Token.all
            && tokAt(i + 2) == Token.expressionEnd) {
          tok = Token.all;
          iToken += 2;
        }
        // fall through
      case Token.all:
        if (tok == Token.all)
          v = viewer.getModelUndeletedAtomsBitSet(-1);
        else
          v = atomExpression(statement, i, 0, true, true, true, true);
        i = iToken;
        if (nParen == 0 && isOneExpressionOnly) {
          iToken++;
          return bitsetVariableVector(v);
        }
        break;
      case Token.spacebeforesquare:
        rpn.addOp(theToken);
        continue;
      case Token.expressionEnd:
        i++;
        break out;
      case Token.rightbrace:
        if (!ignoreComma && nParen == 0 && nSquare == 0)
          break out;
        error(ERROR_invalidArgument);
        break;
      case Token.comma: // ignore commas
        if (!ignoreComma && nParen == 0 && nSquare == 0) {
          break out;
        }
        if (!rpn.addOp(theToken))
          error(ERROR_invalidArgument);
        break;
      case Token.per:
        ScriptVariable token = getBitsetPropertySelector(i + 1, false);
        if (token == null)
          error(ERROR_invalidArgument);
        // check for added min/max modifier
        boolean isUserFunction = (token.intValue == Token.function);
        boolean allowMathFunc = true;
        int tok2 = tokAt(iToken + 2);
        if (tokAt(iToken + 1) == Token.per) {
          switch (tok2) {
          case Token.all:
            tok2 = Token.minmaxmask;
            if (tokAt(iToken + 3) == Token.per
                && tokAt(iToken + 4) == Token.bin)
              tok2 = Token.selectedfloat;
            // fall through
          case Token.min:
          case Token.max:
          case Token.stddev:
          case Token.sum:
          case Token.sum2:
          case Token.average:
            allowMathFunc = (isUserFunction || tok2 == Token.minmaxmask || tok2 == Token.selectedfloat);
            token.intValue |= tok2;
            getToken(iToken + 2);
          }
        }
        allowMathFunc &= (tokAt(iToken + 1) == Token.leftparen || isUserFunction);
        if (!rpn.addOp(token, allowMathFunc))
          error(ERROR_invalidArgument);
        i = iToken;
        if (token.intValue == Token.function && tokAt(i + 1) != Token.leftparen) {
          rpn.addOp(Token.tokenLeftParen);
          rpn.addOp(Token.tokenRightParen);
        }
        break;
      default:
        if (Token.tokAttr(theTok, Token.mathop)
            || Token.tokAttr(theTok, Token.mathfunc) && tokAt(iToken + 1) == Token.leftparen) {
          if (!rpn.addOp(theToken)) {
            if (ptAtom >= 0) {
              // this is expected -- the right parenthesis
              break out;
            }
            error(ERROR_invalidArgument);
          }
          switch (theTok) {
          case Token.leftparen:
            nParen++;
            break;
          case Token.rightparen:
            if (--nParen == 0 && nSquare == 0 && isOneExpressionOnly) {
              iToken++;
              break out;
            }
            break;
          case Token.leftsquare:
            nSquare++;
            break;
          case Token.rightsquare:
            if (--nSquare == 0 && nParen == 0 && isOneExpressionOnly) {
              iToken++;
              break out;
            }
            break;
          }
        } else {
          String name = parameterAsString(i).toLowerCase();
          boolean haveParens = (tokAt(i + 1) == Token.leftparen);
          if (isSyntaxCheck) {
            v = name;
          } else if (!haveParens
              && (localVars == null || (v = localVars.get(name)) == null)) {
            v = getContextVariableAsVariable(name);
          }
          if (v == null) {
            if (Token.tokAttr(theTok, Token.identifier)
                && viewer.isFunction(name)) {
              if (!rpn
                  .addOp(new ScriptVariable(Token.function, theToken.value)))
                error(ERROR_invalidArgument);
              if (!haveParens) {
                rpn.addOp(Token.tokenLeftParen);
                rpn.addOp(Token.tokenRightParen);
              }
            } else {
              rpn.addX(viewer.getOrSetNewVariable(name, false));
            }
          }
        }
      }
      if (v != null) {
        if (v instanceof BitSet)
          rpn.addX((BitSet) v);
        else
          rpn.addX(v);
      }
    }
    ScriptVariable result = rpn.getResult(false);
    if (result == null) {
      if (!isSyntaxCheck)
        rpn.dumpStacks("null result");
      error(ERROR_endOfStatementUnexpected);
    }
    if (result.tok == Token.vector)
      return result.value;
    if (returnBoolean)
      return Boolean.valueOf(ScriptVariable.bValue(result));
    if (returnString) {
      if (result.tok == Token.string)
        result.intValue = Integer.MAX_VALUE;
      return ScriptVariable.sValue(result);
    }
    switch (result.tok) {
    case Token.on:
    case Token.off:
      return Boolean.valueOf(result.intValue == 1);
    case Token.integer:
      return new Integer(result.intValue);
    case Token.bitset:
    case Token.decimal:
    case Token.string:
    case Token.point3f:
    default:
      return result.value;
    }
  }

  private Hashtable getHash(int i) throws ScriptException {
    Hashtable ht = new Hashtable();
    for (i = i + 1; i < statementLength; i++) {
      if (tokAt(i) == Token.rightbrace)
        break;
      String key = stringParameter(i++);
      if (tokAt(i++) != Token.colon)
        error(ERROR_invalidArgument);
      List v = (List)
          parameterExpression(i, 0, null, false, true, -1, false, null, null);
      ht.put(key, v.get(0));
      i = iToken;
      if (tokAt(i) != Token.comma)
        break;
    }
    iToken = i;
    if (tokAt(i) != Token.rightbrace)
      error(ERROR_invalidArgument);
    return ht;
  }

  Object bitsetVariableVector(Object v) {
    List resx = new ArrayList();
    if (v instanceof BitSet)
      resx.add(new ScriptVariable(Token.bitset, v));
    return resx;
  }

  Object getBitsetIdent(BitSet bs, String label, Object tokenValue,
                        boolean useAtomMap, int index, boolean isExplicitlyAll) {
    boolean isAtoms = !(tokenValue instanceof BondSet);
    if (isAtoms) {
      if (label == null)
        label = viewer.getStandardLabelFormat();
      else if (label.length() == 0)
        label = "%[label]";
    }
    int pt = (label == null ? -1 : label.indexOf("%"));
    boolean haveIndex = (index != Integer.MAX_VALUE);
    if (bs == null || isSyntaxCheck || isAtoms && pt < 0) {
      if (label == null)
        label = "";
      return isExplicitlyAll ? new String[] { label } : (Object) label;
    }
    ModelSet modelSet = viewer.getModelSet();
    int n = 0;
    int[] indices = (isAtoms || !useAtomMap ? null : ((BondSet) tokenValue)
        .getAssociatedAtoms());
    if (indices == null && label != null && label.indexOf("%D") > 0)
      indices = viewer.getAtomIndices(bs);
    boolean asIdentity = (label == null || label.length() == 0);
    Hashtable htValues = (isAtoms || asIdentity ? null : LabelToken
        .getBondLabelValues());
    LabelToken[] tokens = (asIdentity ? null : isAtoms ? LabelToken.compile(
        viewer, label, '\0', null) : LabelToken.compile(viewer, label, '\1',
        htValues));
    int nmax = (haveIndex ? 1 : BitSetUtil.cardinalityOf(bs));
    String[] sout = new String[nmax];
    for (int j = (haveIndex ? index : bs.nextSetBit(0)); j >= 0; j = bs
        .nextSetBit(j + 1)) {
      String str;
      if (isAtoms) {
        if (asIdentity)
          str = modelSet.atoms[j].getInfo();
        else
          str = LabelToken.formatLabel(viewer, modelSet.atoms[j], null, tokens,
              '\0', indices);
      } else {
        Bond bond = modelSet.getBondAt(j);
        if (asIdentity)
          str = bond.getIdentity();
        else
          str = LabelToken.formatLabel(viewer, bond, tokens, htValues, indices);
      }
      str = TextFormat.formatString(str, "#", (n + 1));
      sout[n++] = str;
      if (haveIndex)
        break;
    }
    return nmax == 1 && !isExplicitlyAll ? sout[0] : (Object) sout;
  }

  private ScriptVariable getBitsetPropertySelector(int i, boolean mustBeSettable)
      throws ScriptException {
    int tok = getToken(i).tok;
    String s = null;
    switch (tok) {
    case Token.min:
    case Token.max:
    case Token.average:
    case Token.stddev:
    case Token.sum:
    case Token.sum2:
    case Token.property:
      break;
    default:
      if (Token.tokAttrOr(tok, Token.atomproperty, Token.mathproperty))
        break;
      if (!Token.tokAttr(tok, Token.identifier))
        return null;
      String name = parameterAsString(i);
      if (!mustBeSettable && viewer.isFunction(name)) {
        tok = Token.function;
        break;
      }
      return null;
    }
    if (mustBeSettable && !Token.tokAttr(tok, Token.settable))
      return null;
    if (s == null)
      s = parameterAsString(i).toLowerCase();
    return new ScriptVariable(Token.propselector, tok, s);
  }

  private float[] getBitsetPropertyFloat(BitSet bs, int tok, float min,
                                         float max) throws ScriptException {
    float[] data = (float[]) getBitsetProperty(bs, tok, null,
        null, null, null, false, Integer.MAX_VALUE, false);
    if (!Float.isNaN(min))
      for (int i = 0; i < data.length; i++)
        if (data[i] < min)
          data[i] = Float.NaN;
    if (!Float.isNaN(max))
      for (int i = 0; i < data.length; i++)
        if (data[i] > max)
          data[i] = Float.NaN;
    return data;
  }

  protected Object getBitsetProperty(BitSet bs, int tok, Point3f ptRef,
                                     Point4f planeRef, Object tokenValue,
                                     Object opValue, boolean useAtomMap,
                                     int index, boolean asVector)
      throws ScriptException {

    // index is a special argument set in parameterExpression that
    // indicates we are looking at only one atom within a for(...) loop
    // the bitset cannot be a BondSet in that case

    boolean haveIndex = (index != Integer.MAX_VALUE);

    boolean isAtoms = haveIndex || !(tokenValue instanceof BondSet);
    // check minmax flags:

    int minmaxtype = tok & Token.minmaxmask;
    boolean selectedFloat = (minmaxtype == Token.selectedfloat);
    int atomCount = viewer.getAtomCount();
    float[] fout = (minmaxtype == Token.allfloat ? new float[atomCount] : null);
    boolean isExplicitlyAll = (minmaxtype == Token.minmaxmask || selectedFloat);
    tok &= ~Token.minmaxmask;
    if (tok == Token.nada)
      tok = (isAtoms ? Token.atoms : Token.bonds);

    // determine property type:

    boolean isPt = false;
    boolean isInt = false;
    boolean isString = false;
    switch (tok) {
    case Token.xyz:
    case Token.vibxyz:
    case Token.fracxyz:
    case Token.fuxyz:
    case Token.unitxyz:
    case Token.color:
      isPt = true;
      break;
    case Token.function:
    case Token.distance:
      break;
    default:
      isInt = Token.tokAttr(tok, Token.intproperty)
          && !Token.tokAttr(tok, Token.floatproperty);
      // occupancy and radius considered floats here
      isString = !isInt && Token.tokAttr(tok, Token.strproperty);
      // structure considered int; for the name, use .label("%[structure]")
    }

    // preliminarty checks we only want to do once:

    Point3f pt = (isPt || !isAtoms ? new Point3f() : null);
    if (isExplicitlyAll || isString && minmaxtype != Token.allfloat)
      minmaxtype = Token.all;
    List vout = (minmaxtype == Token.all ? new ArrayList() : null);
    BitSet bsNew = null;
    String userFunction = null;
    List params = null;
    BitSet bsAtom = null;
    ScriptVariable tokenAtom = null;
    Point3f ptT = null;
    float[] data = null;

    switch (tok) {
    case Token.atoms:
    case Token.bonds:
      if (isSyntaxCheck)
        return bs;
      bsNew = (tok == Token.atoms ? (isAtoms ? bs : viewer.getAtomBits(
          Token.bonds, bs)) : (isAtoms ? (BitSet) new BondSet(viewer
          .getBondsForSelectedAtoms(bs)) : bs));
      int i;
      switch (minmaxtype) {
      case Token.min:
        i = bsNew.nextSetBit(0);
        break;
      case Token.max:
        i = bsNew.length() - 1;
        break;
      case Token.stddev:
      case Token.sum:
      case Token.sum2:
        return new Float(Float.NaN);
      default:
        return bsNew;
      }
      bsNew.clear();
      if (i >= 0)
        bsNew.set(i);
      return bsNew;
    case Token.identify:
      switch (minmaxtype) {
      case 0:
      case Token.all:
        return getBitsetIdent(bs, null, tokenValue, useAtomMap, index,
            isExplicitlyAll);
      }
      return "";
    case Token.function:
      userFunction = (String) ((Object[]) opValue)[0];
      params = (List) ((Object[]) opValue)[1];
      bsAtom = new BitSet(atomCount);
      tokenAtom = new ScriptVariable(Token.bitset, bsAtom);
      break;
    case Token.straightness:
    case Token.surfacedistance:
      viewer.autoCalculate(tok);
      break;
    case Token.distance:
      if (ptRef == null && planeRef == null)
        return new Point3f();
      break;
    case Token.color:
      ptT = new Point3f();
      break;
    case Token.property:
      data = viewer.getDataFloat((String) opValue);
      break;
    }

    int n = 0;
    int ivvMinMax = 0;
    int ivMinMax = 0;
    float fvMinMax = 0;
    double sum = 0;
    double sum2 = 0;
    switch (minmaxtype) {
    case Token.min:
      ivMinMax = Integer.MAX_VALUE;
      fvMinMax = Float.MAX_VALUE;
      break;
    case Token.max:
      ivMinMax = Integer.MIN_VALUE;
      fvMinMax = -Float.MAX_VALUE;
      break;
    }
    ModelSet modelSet = viewer.getModelSet();
    int mode = (isPt ? 3 : isString ? 2 : isInt ? 1 : 0);
    if (isAtoms) {
      boolean haveBitSet = (bs != null);
      int iModel = -1;
      int i0, i1;
      if (haveIndex) {
        i0 = index;
        i1 = index + 1;
      } else if (haveBitSet) {
        i0 = bs.nextSetBit(0);
        i1 = Math.min(atomCount, bs.length());
      } else {
        i0 = 0;
        i1 = atomCount;
      }
      if (isSyntaxCheck)
        i1 = 0;
      for (int i = i0; i >= 0 && i < i1; i = (haveBitSet ? bs.nextSetBit(i + 1)
          : i + 1)) {
        n++;
        Atom atom = modelSet.atoms[i];
        switch (mode) {
        case 0: // float
          float fv = Float.MAX_VALUE;
          switch (tok) {
          case Token.function:
            bsAtom.set(i);
            fv = ScriptVariable.fValue(runFunction(null, userFunction, params,
                tokenAtom, true));
            bsAtom.clear(i);
            break;
          case Token.property:
            fv = (data == null ? 0 : data[i]);
            break;
          case Token.distance:
            if (planeRef != null)
              fv = Measure.distanceToPlane(planeRef, atom);
            else
              fv = atom.distance(ptRef);
            break;
          default:
            fv = Atom.atomPropertyFloat(viewer, atom, tok);
          }
          if (fv == Float.MAX_VALUE || Float.isNaN(fv)
              && minmaxtype != Token.all) {
            n--; // don't count this one
            continue;
          }
          switch (minmaxtype) {
          case Token.min:
            if (fv < fvMinMax)
              fvMinMax = fv;
            break;
          case Token.max:
            if (fv > fvMinMax)
              fvMinMax = fv;
            break;
          case Token.allfloat:
            fout[i] = fv;
            break;
          case Token.all:
            vout.add(new Float(fv));
            break;
          case Token.sum2:
          case Token.stddev:
            sum2 += ((double) fv) * fv;
            // fall through
          case Token.sum:
          default:
            sum += fv;
          }
          break;
        case 1: // isInt
          int iv = 0;
          switch (tok) {
          case Token.symop:
            // a little weird:
            // First we determine how many operations we have in this model.
            // Then we get the symmetry bitset, which shows the assignments
            // of symmetry for this atom.
            if (atom.getModelIndex() != iModel)
              iModel = atom.getModelIndex();
            BitSet bsSym = atom.getAtomSymmetry();
            int p = 0;
            switch (minmaxtype) {
            case Token.min:
              ivvMinMax = Integer.MAX_VALUE;
              break;
            case Token.max:
              ivvMinMax = Integer.MIN_VALUE;
              break;
            }
            for (int k = bsSym.nextSetBit(0); k >= 0; k = bsSym
                .nextSetBit(k + 1)) {
              iv += k + 1;
              switch (minmaxtype) {
              case Token.min:
                ivvMinMax = Math.min(ivvMinMax, k + 1);
                break;
              case Token.max:
                ivvMinMax = Math.max(ivvMinMax, k + 1);
                break;
              }
              p++;
            }
            switch (minmaxtype) {
            case Token.min:
            case Token.max:
              iv = ivvMinMax;
            }
            n += p - 1;
            break;
          case Token.configuration:
          case Token.cell:
            error(ERROR_unrecognizedAtomProperty, Token.nameOf(tok));
          default:
            iv = Atom.atomPropertyInt(atom, tok);
          }
          switch (minmaxtype) {
          case Token.min:
            if (iv < ivMinMax)
              ivMinMax = iv;
            break;
          case Token.max:
            if (iv > ivMinMax)
              ivMinMax = iv;
            break;
          case Token.allfloat:
            fout[i] = iv;
            break;
          case Token.all:
            vout.add(new Integer(iv));
            break;
          case Token.sum2:
          case Token.stddev:
            sum2 += ((double) iv) * iv;
            // fall through
          case Token.sum:
          default:
            sum += iv;
          }
          break;
        case 2: // isString
          String s = Atom.atomPropertyString(atom, tok);
          switch (minmaxtype) {
          case Token.allfloat:
            fout[i] = Parser.parseFloat(s);
            break;
          default:
            vout.add(s);
          }
          break;
        case 3: // isPt
          Tuple3f t = Atom.atomPropertyTuple(atom, tok);
          if (t == null)
            error(ERROR_unrecognizedAtomProperty, Token.nameOf(tok));
          switch (minmaxtype) {
          case Token.allfloat:
            fout[i] = (float) Math.sqrt(t.x * t.x + t.y * t.y + t.z * t.z);
            break;
          case Token.all:
            vout.add(new Point3f(t));
            break;
          default:
            pt.add(t);
          }
          break;
        }
        if (haveIndex)
          break;
      }
    } else { // bonds
      boolean isAll = (bs == null);
      int i0 = (isAll ? 0 : bs.nextSetBit(0));
      int i1 = viewer.getBondCount();
      for (int i = i0; i >= 0 && i < i1; i = (isAll ? i + 1 : bs
          .nextSetBit(i + 1))) {
        n++;
        Bond bond = modelSet.getBondAt(i);
        switch (tok) {
        case Token.length:
          float fv = bond.getAtom1().distance(bond.getAtom2());
          switch (minmaxtype) {
          case Token.min:
            if (fv < fvMinMax)
              fvMinMax = fv;
            break;
          case Token.max:
            if (fv > fvMinMax)
              fvMinMax = fv;
            break;
          case Token.all:
            vout.add(new Float(fv));
            break;
          case Token.sum2:
          case Token.stddev:
            sum2 += (double) fv * fv;
            // fall through
          case Token.sum:
          default:
            sum += fv;
          }
          break;
        case Token.xyz:
          switch (minmaxtype) {
          case Token.all:
            pt.set(bond.getAtom1());
            pt.add(bond.getAtom2());
            pt.scale(0.5f);
            vout.add(new Point3f(pt));
            break;
          default:
            pt.add(bond.getAtom1());
            pt.add(bond.getAtom2());
            n++;
          }
          break;
        case Token.color:
          Graphics3D.colorPointFromInt(viewer.getColorArgbOrGray(bond
              .getColix()), ptT);
          switch (minmaxtype) {
          case Token.all:
            vout.add(new Point3f(ptT));
            break;
          default:
            pt.add(ptT);
          }
          break;
        default:
          error(ERROR_unrecognizedBondProperty, Token.nameOf(tok));
        }
      }
    }
    if (minmaxtype == Token.allfloat)
      return fout;
    if (minmaxtype == Token.all) {
      if (asVector)
        return vout;
      int len = vout.size();
      if (isString && !isExplicitlyAll && len == 1)
        return vout.get(0);
      if (tok == Token.sequence) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < len; i++)
          sb.append((String) vout.get(i));
        return sb.toString();
      }
      if (selectedFloat) {
        fout = new float[len];
        Point3f zero = (len > 0 && isPt ? new Point3f() : null);
        for (int i = len; --i >= 0;) {
          Object v = vout.get(i);
          switch (mode) {
          case 0:
            fout[i] = ((Float) v).floatValue();
            break;
          case 1:
            fout[i] = ((Integer) v).floatValue();
            break;
          case 2:
            fout[i] = Parser.parseFloat((String) v);
            break;
          case 3:
            fout[i] = ((Point3f) v).distance(zero);
            break;
          }
        }
        return fout;
      }
      String[] sout = new String[len];
      for (int i = len; --i >= 0;) {
        Object v = vout.get(i);
        if (v instanceof Point3f)
          sout[i] = Escape.escape((Point3f) v);
        else
          sout[i] = "" + vout.get(i);
      }
      return sout;
    }
    if (isPt)
      return (n == 0 ? pt : new Point3f(pt.x / n, pt.y / n, pt.z / n));
    if (n == 0 || n == 1 && minmaxtype == Token.stddev)
      return new Float(Float.NaN);
    if (isInt) {
      switch (minmaxtype) {
      case Token.min:
      case Token.max:
        return new Integer(ivMinMax);
      case Token.sum2:
      case Token.stddev:
        break;
      case Token.sum:
        return new Integer((int) sum);
      default:
        if (sum / n == (int) (sum / n))
          return new Integer((int) (sum / n));
        return new Float(sum / n);
      }
    }
    switch (minmaxtype) {
    case Token.min:
    case Token.max:
      sum = fvMinMax;
      break;
    case Token.sum:
      break;
    case Token.sum2:
      sum = sum2;
      break;
    case Token.stddev:
      // because SUM (x_i - X_av)^2 = SUM(x_i^2) - 2X_av SUM(x_i) + SUM(X_av^2)
      // = SUM(x_i^2) - 2nX_av^2 + nX_av^2
      // = SUM(x_i^2) - nX_av^2
      // = SUM(x_i^2) - [SUM(x_i)]^2 / n
      sum = Math.sqrt((sum2 - sum * sum / n) / (n - 1));
      break;
    default:
      sum /= n;
      break;
    }
    return new Float(sum);
  }

  private void setBitsetProperty(BitSet bs, int tok, int iValue, float fValue,
                                 Token tokenValue) throws ScriptException {
    if (isSyntaxCheck || BitSetUtil.cardinalityOf(bs) == 0)
      return;
    String[] list = null;
    String sValue = null;
    float[] fvalues = null;
    Point3f pt;
    List sv = null;
    int nValues = 0;
    boolean isStrProperty = Token.tokAttr(tok, Token.strproperty);
    if (tokenValue.tok == Token.varray) {
      sv = ((ScriptVariable) tokenValue).getList();
      if ((nValues = sv.size()) == 0)
        return;
    }
    switch (tok) {
    case Token.xyz:
    case Token.fracxyz:
    case Token.fuxyz:
    case Token.vibxyz:
      switch (tokenValue.tok) {
      case Token.point3f:
        viewer.setAtomCoord(bs, tok, tokenValue.value);
        break;
      case Token.varray:
        theToken = tokenValue;
        viewer.setAtomCoord(bs, tok, getPointArray(-1, nValues));
        break;
      }
      return;
    case Token.color:
      Object value = null;
      String prop = "color";
      switch (tokenValue.tok) {
      case Token.varray:
        int[] values = new int[nValues];
        for (int i = nValues; --i >= 0;) {
          ScriptVariable svi = (ScriptVariable) sv.get(i);
          pt = ScriptVariable.ptValue(svi);
          if (pt != null) {
            values[i] = Graphics3D.colorPtToInt(pt);
          } else if (svi.tok == Token.integer) {
            values[i] = svi.intValue;
          } else {
            values[i] = Graphics3D
                .getArgbFromString(ScriptVariable.sValue(svi));
            if (values[i] == 0)
              values[i] = ScriptVariable.iValue(svi);
          }
          if (values[i] == 0)
            error(ERROR_unrecognizedParameter, "ARRAY", ScriptVariable
                .sValue(svi));
        }
        value = values;
        prop = "colorValues";
        break;
      case Token.point3f:
        value = Integer.valueOf(Graphics3D.colorPtToInt((Point3f) tokenValue.value));
        break;
      case Token.string:
        value = tokenValue.value;
        break;
      default:
        value = Integer.valueOf(ScriptVariable.iValue(tokenValue));
        break;
      }
      setShapeProperty(JmolConstants.SHAPE_BALLS, prop, value, bs);
      return;
    case Token.label:
    case Token.format:
      if (tokenValue.tok != Token.varray)
        sValue = ScriptVariable.sValue(tokenValue);
      break;
    case Token.element:
    case Token.elemno:
      clearDefinedVariableAtomSets();
      isStrProperty = false;
      break;
    }
    switch (tokenValue.tok) {
    case Token.varray:
      if (isStrProperty)
        list = ScriptVariable.listValue(tokenValue);
      else
        fvalues = ScriptVariable.flistValue(tokenValue, nValues);
      break;
    case Token.string:
      if (sValue == null)
        list = Parser.getTokens(ScriptVariable.sValue(tokenValue));
      break;
    }
    if (list != null) {
      nValues = list.length;
      if (!isStrProperty) {
        fvalues = new float[nValues];
        for (int i = nValues; --i >= 0;)
          fvalues[i] = (tok == Token.element ? Elements
              .elementNumberFromSymbol(list[i], false) : Parser
              .parseFloat(list[i]));
      }
      if (tokenValue.tok != Token.varray && nValues == 1) {
        if (isStrProperty)
          sValue = list[0];
        else
          fValue = fvalues[0];
        iValue = (int) fValue;
        list = null;
        fvalues = null;
      }
    }
    viewer.setAtomProperty(bs, tok, iValue, fValue, sValue, fvalues, list);
  }

  // ///////////////////// general fields //////////////////////

  private final static int scriptLevelMax = 100;

  private Thread currentThread;
  protected Viewer viewer;
  protected ScriptCompiler compiler;
  private Hashtable definedAtomSets;
  private StringBuffer outputBuffer;

  private String contextPath = "";
  private String filename;
  private String functionName;
  private boolean isStateScript;
  int scriptLevel;
  private int scriptReportingLevel = 0;
  private int commandHistoryLevelMax = 0;

  // created by Compiler:
  private Token[][] aatoken;
  private short[] lineNumbers;
  private int[][] lineIndices;
  private Hashtable contextVariables;

  public Hashtable getContextVariables() {
    return contextVariables;
  }

  private String script;

  public String getScript() {
    return script;
  }

  // specific to current statement
  protected int pc; // program counter
  private String thisCommand;
  private String fullCommand;
  private Token[] statement;
  private int statementLength;
  private int iToken;
  private int lineEnd;
  private int pcEnd;
  private String scriptExtensions;

  // ////////////////////// supporting methods for compilation and  loading
  // //////////

  private boolean compileScript(String filename, String strScript,
                                boolean debugCompiler) {
    this.filename = filename;
    strScript = fixScriptPath(strScript, filename);
    restoreScriptContext(compiler.compile(filename, strScript, false, false,
        debugCompiler, false), false, false, false);
    isStateScript = (script.indexOf(Viewer.STATE_VERSION_STAMP) >= 0);
    String s = script;
    pc = setScriptExtensions();
    if (!isSyntaxCheck && viewer.isScriptEditorVisible()
        && strScript.indexOf(JmolConstants.SCRIPT_EDITOR_IGNORE) < 0)
      viewer.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 = TextFormat.simpleReplace(strScript, "$SCRIPT_PATH$/", path);
      // now replace the variable itself
      strScript = TextFormat.simpleReplace(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 = Parser.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 void runScript(String script) throws ScriptException {
    if (!viewer.isPreviewOnly())
      runScript(script, outputBuffer);
  }

  private boolean compileScriptFileInternal(String filename, String localPath,
                                            String remotePath, String scriptPath) {
    // from "script" command, with push/pop surrounding or viewer
    if (filename.toLowerCase().indexOf("javascript:") == 0)
      return compileScript(filename, viewer.jsEval(filename.substring(11)),
          debugScript);
    String[] data = new String[2];
    data[0] = filename;
    if (!viewer.getFileAsString(data, Integer.MAX_VALUE, false)) {
      setErrorMessage("io error reading " + data[0] + ": " + data[1]);
      return false;
    }
    this.filename = filename;
    data[1] = ScriptCompiler.getEmbeddedScript(data[1]);
    String script = fixScriptPath(data[1], data[0]);
    if (scriptPath == null) {
      scriptPath = viewer.getFilePath(filename, false);
      scriptPath = scriptPath.substring(0, scriptPath.lastIndexOf("/"));
    }
    script = FileManager.setScriptFileReferences(script, localPath, remotePath,
        scriptPath);
    return compileScript(filename, script, debugScript);
  }

  // ///////////// Jmol parameter / user variable / function support
  // ///////////////

  private Object getParameter(String key, int tokType) {
    Object v = getContextVariableAsVariable(key);
    if (v == null)
      v = viewer.getParameter(key);
    switch (tokType) {
    case Token.variable:
      return ScriptVariable.getVariable(v);
    case Token.string:
      if (!(v instanceof List))
        break;
      List sv = (List) v;
      StringBuffer sb = new StringBuffer();
      for (int i = 0; i < sv.size(); i++)
        sb.append(ScriptVariable.sValue((ScriptVariable) sv.get(i))).append('\n');
      return sb.toString();
    }
    return (v instanceof ScriptVariable ? ScriptVariable
        .oValue((ScriptVariable) v) : v);
  }

  private String getParameterEscaped(String var) {
    ScriptVariable v = getContextVariableAsVariable(var);
    return (v == null ? "" + viewer.getParameterEscaped(var) : Escape
        .escape(v.value));
  }

  private String getStringParameter(String var, boolean orReturnName) {
    ScriptVariable v = getContextVariableAsVariable(var);
    if (v != null)
      return ScriptVariable.sValue(v);
    String val = "" + viewer.getParameter(var);
    return (val.length() == 0 && orReturnName ? var : val);
  }

  private Object getNumericParameter(String var) {
    if (var.equalsIgnoreCase("_modelNumber")) {
      int modelIndex = viewer.getCurrentModelIndex();
      return new Integer(modelIndex < 0 ? 0 : viewer
          .getModelFileNumber(modelIndex));
    }
    ScriptVariable v = getContextVariableAsVariable(var);
    if (v == null) {
      Object val = viewer.getParameter(var);
      if (!(val instanceof String))
        return val;
      v = new ScriptVariable(Token.string, val);
    }
    return ScriptVariable.nValue(v);
  }

  private ScriptVariable getContextVariableAsVariable(String var) {
    if (var.equals("expressionBegin"))
      return null;
    var = var.toLowerCase();
    if (contextVariables != null && contextVariables.containsKey(var))
      return (ScriptVariable) contextVariables.get(var);
    ScriptContext context = thisContext;
    while (context != null) {
      if (context.isFunction == true)
        return null;
      if (context.contextVariables != null
          && context.contextVariables.containsKey(var))
        return (ScriptVariable) context.contextVariables.get(var);
      context = context.parentContext;
    }
    return null;
  }

  private Object getStringObjectAsVariable(String s, String key) {
    if (s == null || s.length() == 0)
      return s;
    Object v = ScriptVariable.unescapePointOrBitsetAsVariable(s);
    if (v instanceof String && key != null)
      v = viewer.setUserVariable(key, new ScriptVariable(Token.string,
          (String) v));
    return v;
  }

  private ParallelProcessor parallelProcessor;

  ScriptVariable runFunction(ScriptFunction function, String name, List params, Token tokenAtom,
                             boolean getReturn) throws ScriptException {
    pushContext(null);
    thisContext.isFunction = true;
    //System.out.println(contextPath);
    if (function == null) {
      function = viewer.getFunction(name);
      contextPath += " >> function " + name;
    } else {
      contextPath += " >> " + name;
    }
    if (function == null)
      return null;
    functionName = name;

    if (function instanceof ParallelProcessor) {
      synchronized (function) // can't do this -- too general
      {
        parallelProcessor = (ParallelProcessor) function;
        vProcess = null;
        runFunction(function, params, tokenAtom);

        boolean isTry = (function.tok == Token.trycmd);
        ScriptContext sc = getScriptContext();
        if (isTry) {
          contextVariables.put("_breakval", ScriptVariable.intVariable(Integer.MAX_VALUE));
          contextVariables.put("_errorval", ScriptVariable.getVariable(""));
          viewer.resetError();
          parallelProcessor.addProcess("try", sc);
        }
        ((ParallelProcessor) function).runAllProcesses(viewer, !isTry);
        if (isTry){
          String err = (String) viewer.getParameter("_errormessage");
          if (err.length() > 0) {
            contextVariables.put("_errorval", ScriptVariable.getVariable(err));
            viewer.resetError();
          }
          contextVariables.put("_tryret", contextVariables.get("_retval"));
          contextVariables.put("_retval", new ScriptVariable(0, contextVariables));
        }
      }
    } else {
      runFunction(function, params, tokenAtom);
    }
    ScriptVariable v = (getReturn ? getContextVariableAsVariable("_retval")
        : null);
    popContext(false, false);
    return v;
  }

  private void runFunction(ScriptFunction function, List params,
                           Token tokenAtom) throws ScriptException {
    aatoken = function.aatoken;
    lineNumbers = function.lineNumbers;
    lineIndices = function.lineIndices;
    script = function.script;
    pc = 0;
    if (function.names != null) {
      contextVariables = new Hashtable();
      function.setVariables(contextVariables, params);
    }
    if (tokenAtom != null)
      contextVariables.put("_x", tokenAtom);
    if (function.tok != Token.trycmd)
      instructionDispatchLoop(false);
  }

  private void clearDefinedVariableAtomSets() {
    definedAtomSets.remove("# variable");
  }

  /**
   * support for @xxx or define xxx commands
   *
   */
  private void defineSets() {
    if (!definedAtomSets.containsKey("# static")) {
      for (int i = 0; i < JmolConstants.predefinedStatic.length; i++)
        defineAtomSet(JmolConstants.predefinedStatic[i]);
      defineAtomSet("# static");
    }
    if (definedAtomSets.containsKey("# variable"))
      return;
    for (int i = 0; i < JmolConstants.predefinedVariable.length; i++)
      defineAtomSet(JmolConstants.predefinedVariable[i]);
    // Now, define all the elements as predefined sets
    // hydrogen is handled specially, so don't define it

    int firstIsotope = JmolConstants.firstIsotope;
    // name ==> e_=n for all standard elements
    for (int i = Elements.elementNumberMax; --i >= 0;) {
      String definition = "@" + Elements.elementNameFromNumber(i) + " _e=" + i;
      defineAtomSet(definition);
    }
    // _Xx ==> name for of all elements, isotope-blind
    for (int i = Elements.elementNumberMax; --i >= 0;) {
      String definition = "@_" + Elements.elementSymbolFromNumber(i) + " "
          + Elements.elementNameFromNumber(i);
      defineAtomSet(definition);
    }
    // name ==> _e=nn for each alternative element
    for (int i = 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 >= firstIsotope;) {
      String def = " element=" + Elements.altElementNumberFromIndex(i);
      String definition = "@_" + Elements.altElementSymbolFromIndex(i);
      defineAtomSet(definition + def);
      definition = "@_" + Elements.altIsotopeSymbolFromIndex(i);
      defineAtomSet(definition + def);
      definition = "@" + Elements.altElementNameFromIndex(i);
      if (definition.length() > 1)
        defineAtomSet(definition + def);
    }
    defineAtomSet("# variable");
  }

  private void defineAtomSet(String script) {
    if (script.indexOf("#") == 0) {
      definedAtomSets.put(script, Boolean.TRUE);
      return;
    }
    ScriptContext sc = compiler.compile("#predefine", script, true, false,
        false, false);
    if (sc.errorType != null) {
      viewer
          .scriptStatus("JmolConstants.java ERROR: predefined set compile error:"
              + script + "\ncompile error:" + sc.errorMessageUntranslated);
      return;
    }

    if (sc.aatoken.length != 1) {
      viewer
          .scriptStatus("JmolConstants.java ERROR: predefinition does not have exactly 1 command:"
              + script);
      return;
    }
    Token[] statement = sc.aatoken[0];
    if (statement.length <= 2) {
      viewer.scriptStatus("JmolConstants.java ERROR: bad predefinition length:"
          + script);
      return;
    }
    int tok = statement[1].tok;
    if (!Token.tokAttr(tok, Token.identifier)
        && !Token.tokAttr(tok, Token.predefinedset)) {
      viewer.scriptStatus("JmolConstants.java ERROR: invalid variable name:"
          + script);
      return;
    }
    String name = ((String) statement[1].value).toLowerCase();
    if (name.startsWith("dynamic_"))
      name = "!" + name.substring(8);
    definedAtomSets.put(name, statement);
  }

  private BitSet lookupIdentifierValue(String identifier)
      throws ScriptException {
    // 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.

    BitSet bs = lookupValue(identifier, false);
    if (bs != null)
      return BitSetUtil.copy(bs);

    // next we look for names of groups (PDB) or atoms (non-PDB)
    bs = getAtomBits(Token.identifier, identifier);
    return (bs == null ? new BitSet() : bs);
  }

  private BitSet lookupValue(String setName, boolean plurals)
      throws ScriptException {
    if (isSyntaxCheck) {
      return new BitSet();
    }
    defineSets();
    setName = setName.toLowerCase();
    Object value = definedAtomSets.get(setName);
    boolean isDynamic = false;
    if (value == null) {
      value = definedAtomSets.get("!" + setName);
      isDynamic = (value != null);
    }
    if (value instanceof BitSet)
      return (BitSet) value;
    if (value instanceof Token[]) {
      pushContext(null);
      BitSet bs = atomExpression((Token[]) value, -2, 0, true, false, true,
          true);
      popContext(false, false);
      if (!isDynamic)
        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);
  }

  public void deleteAtomsInVariables(BitSet bsDeleted) {
    Enumeration e = definedAtomSets.keys();
    while (e.hasMoreElements()) {
      String key = (String) e.nextElement();
      Object value = definedAtomSets.get(key);
      if (value instanceof BitSet) {
        BitSetUtil.deleteBits((BitSet) value, bsDeleted);
        if (!key.startsWith("!"))
          viewer.setStringProperty("@" + key, Escape.escape(value));
      }
    }
  }

  /**
   * provides support for @x and @{....} in statements. The compiler passes on
   * these, because they must be integrated with the statement dynamically.
   *
   * @param pc
   * @return a fixed token set -- with possible overrun of unused null tokens
   *
   * @throws ScriptException
   */
  private boolean setStatement(int pc) throws ScriptException {
    statement = aatoken[pc];
    statementLength = statement.length;
    if (statementLength == 0)
      return true;
    Token[] fixed;
    int i;
    int tok;
    for (i = 1; i < statementLength; i++) {
      if (statement[i] == null) {
        statementLength = i;
        return true;
      }
      if (statement[i].tok == Token.define)
        break;
    }
    if (i == statementLength)// || isScriptCheck)
      return i == statementLength;
    fixed = new Token[statementLength];
    fixed[0] = statement[0];
    boolean isExpression = false;
    int j = 1;
    for (i = 1; i < statementLength; i++) {
      if (statement[i] == null)
        continue;
      switch (tok = getToken(i).tok) {
      default:
        fixed[j] = statement[i];
        break;
      case Token.expressionBegin:
      case Token.expressionEnd:
        // @ in expression will be taken as SELECT
        isExpression = (tok == Token.expressionBegin);
        fixed[j] = statement[i];
        break;
      case Token.define:
        if (++i == statementLength)
          error(ERROR_invalidArgument);
        Object v;
        // compiler can indicate that a definition MUST
        // be interpreted as a String
        boolean forceString = (theToken.intValue == Token.string);
        // Object var_set;
        String s;
        String var = parameterAsString(i);
        boolean isClauseDefine = (tokAt(i) == Token.expressionBegin);
        boolean isSetAt = (j == 1 && statement[0] == Token.tokenSetCmd);
        if (isClauseDefine) {
          ScriptVariable vt = parameterExpressionToken(++i);
          i = iToken;
          v = (vt.tok == Token.varray ? vt : ScriptVariable.oValue(vt));
        } else {
          v = getParameter(var, 0);
          if (!isExpression && !isSetAt)
            isClauseDefine = true;
        }
        tok = tokAt(0);
        forceString |= (Token.tokAttr(tok, Token.implicitStringCommand) || tok == Token.script); // for the file names
        if (v instanceof ScriptVariable) {
          // select @{...}
          fixed[j] = (Token) v;
          if (isExpression && fixed[j].tok == Token.varray) {
            BitSet bs = ScriptVariable.getBitSet((ScriptVariable) v, true);
            // I can't remember why we have to be checking list variables
            // for atom names.
            fixed[j] = new ScriptVariable(Token.bitset,
                bs == null ? getAtomBitSet(this, ScriptVariable
                    .sValue(fixed[j])) : bs);
          }
        } else if (v instanceof Boolean) {
          fixed[j] = (((Boolean) v).booleanValue() ? Token.tokenOn
              : Token.tokenOff);
        } else if (v instanceof Integer) {
          // if (isExpression && !isClauseDefine
          // && (var_set = getParameter(var + "_set", false)) != null)
          // fixed[j] = new Token(Token.define, "" + var_set);
          // else
          fixed[j] = new Token(Token.integer, ((Integer) v).intValue(), v);

        } else if (v instanceof Float) {
          fixed[j] = new Token(Token.decimal, JmolConstants.modelValue("" + v),
              v);
        } else if (v instanceof String) {
          if (!forceString) {
            if ((tok != Token.set || j > 1) && Token.tokAttr(tok, Token.mathExpressionCommand)) {
              v = getParameter((String) v, Token.variable);
            }
            if (v instanceof String) {
              v = getStringObjectAsVariable((String) v, null);
            }
          }
          if (v instanceof ScriptVariable) {
            // was a variable name
            fixed[j] = (Token) v;
          } else {
            // wasn't a known variable name
            s = (String) v;
            if (isExpression && !forceString) {
              // select @x  where x is "arg", for example
              fixed[j] = new Token(Token.bitset, getAtomBitSet(this, s));
            } else {
              // bit of a hack here....
              // identifiers cannot have periods; file names can, though
              // TODO: this is still a hack
              // what we really need to know is what the compiler
              // expects here -- a string or an identifier, because
              // they will be processed differently.
              // a filename with only letters and numbers will be
              // read incorrectly here as an identifier.

              // note that command keywords cannot be implemented as variables
              // because they are not Token.identifiers in the first place.
              // but the identifier tok is important here because just below
              // there is a check for SET parameter name assignments.
              // even those may not work...

              tok = (isClauseDefine || forceString || s.indexOf(".") >= 0
                  || s.indexOf(" ") >= 0 || s.indexOf("=") >= 0
                  || s.indexOf(";") >= 0 || s.indexOf("[") >= 0
                  || s.indexOf("{") >= 0 ? Token.string : Token.identifier);
              fixed[j] = new Token(tok, v);
            }
          }
        } else if (v instanceof BitSet) {
          fixed[j] = new ScriptVariable(Token.bitset, v);
        } else if (v instanceof Point3f) {
          fixed[j] = new ScriptVariable(Token.point3f, v);
        } else if (v instanceof Point4f) {
          fixed[j] = new ScriptVariable(Token.point4f, v);
        } else if (v instanceof Matrix3f) {
          fixed[j] = new ScriptVariable(Token.matrix3f, v);
        } else if (v instanceof Matrix4f) {
          fixed[j] = new ScriptVariable(Token.matrix4f, v);
        } else if (v instanceof Map) {
          fixed[j] = new ScriptVariable(Token.hash, v);
        } else if (v instanceof List) {
          List sv = (List) v;
          BitSet bs = null;
          for (int k = 0; k < sv.size(); k++) {
            ScriptVariable svk = (ScriptVariable)sv.get(k);
            if (svk.tok != Token.bitset) {
              bs = null;
              break;
            }
            if (bs == null)
              bs = new BitSet();
            bs.or((BitSet) svk.value);
          }
          fixed[j] = (bs == null ? ScriptVariable.getVariable(v)
              : new Token(Token.bitset, bs));
        } else {
          Point3f center = getObjectCenter(var, Integer.MIN_VALUE,
              Integer.MIN_VALUE);
          if (center == null)
            error(ERROR_invalidArgument);
          fixed[j] = new Token(Token.point3f, center);
        }
        if (isSetAt && !Token.tokAttr(fixed[j].tok, Token.setparam))
          error(ERROR_invalidArgument);
        break;
      }

      j++;
    }
    statement = fixed;
    for (i = j; i < statement.length; i++)
      statement[i] = null;
    statementLength = j;

    return true;
  }

  // ///////////////// Script context support //////////////////////

  private void clearState(boolean tQuiet) {
    thisContext = null;
    scriptLevel = 0;
    setErrorMessage(null);
    contextPath = "";
    this.tQuiet = tQuiet;
  }

  protected ScriptContext thisContext = null;

  private void pushContext(ContextToken token) throws ScriptException {
    if (Logger.debugging || isCmdLine_c_or_C_Option)
      Logger.info("-->>-------------".substring(0, scriptLevel + 5)
          + scriptLevel + " " + filename + " " + token + " " + thisContext);
    if (scriptLevel == scriptLevelMax)
      error(ERROR_tooManyScriptLevels);
    thisContext = getScriptContext();
    thisContext.token = token;
    if (token == null) {
      scriptLevel = ++thisContext.scriptLevel;
    } else {
      thisContext.scriptLevel = -1;
      contextVariables = new Hashtable();
      if (token.contextVariables != null) {
        Iterator e = token.contextVariables.keySet().iterator();
        while (e.hasNext())
          ScriptCompiler
              .addContextVariable(contextVariables, (String) e.next());
      }
    }
    if (Logger.debugging || isCmdLine_c_or_C_Option)
      Logger.info("-->>-------------".substring(0, Math
          .max(17, scriptLevel + 5))
          + scriptLevel + " " + filename + " " + token + " " + thisContext);
  }

  public ScriptContext getScriptContext() {
    ScriptContext context = new ScriptContext();
    context.scriptLevel = scriptLevel;
    context.parentContext = thisContext;
    context.contextPath = contextPath;
    context.filename = filename;
    context.parallelProcessor = parallelProcessor;
    context.functionName = functionName;
    context.script = script;
    context.lineNumbers = lineNumbers;
    context.lineIndices = lineIndices;
    context.aatoken = aatoken;

    context.statement = statement;
    context.statementLength = statementLength;
    context.pc = pc;
    context.lineEnd = lineEnd;
    context.pcEnd = pcEnd;
    context.iToken = iToken;
    context.outputBuffer = outputBuffer;
    context.contextVariables = contextVariables;
    context.isStateScript = isStateScript;

    context.errorMessage = errorMessage;
    context.errorType = errorType;
    context.iCommandError = iCommandError;
    context.isSyntaxCheck = isSyntaxCheck;
    context.executionStepping = executionStepping;
    context.executionPaused = executionPaused;
    context.scriptExtensions = scriptExtensions;
    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() : null);
    restoreScriptContext(thisContext, true, isFlowCommand, statementOnly);
    restoreScriptContext(scTemp, true, false, true);
    if (Logger.debugging || isCmdLine_c_or_C_Option)
      Logger.info("--<<-------------".substring(0, Math.max(17, scriptLevel + 5))
          + scriptLevel + " " + filename + " " + (thisContext == null ? "" : "" + thisContext.token) + " " + thisContext);
  }

  private void restoreScriptContext(ScriptContext context,
                                    boolean isPopContext,
                                    boolean isFlowCommand, boolean statementOnly) {

    if (context == null)
      return;
    if (!isFlowCommand) {
      statement = context.statement;
      statementLength = context.statementLength;
      pc = context.pc;
      lineEnd = context.lineEnd;
      pcEnd = context.pcEnd;
      if (statementOnly)
        return;
    }
    script = context.script;
    lineNumbers = context.lineNumbers;
    lineIndices = context.lineIndices;
    aatoken = context.aatoken;
    contextVariables = context.contextVariables;
    scriptExtensions = context.scriptExtensions;
    if (isPopContext) {
      contextPath = context.contextPath;
      filename = context.filename;
      parallelProcessor = context.parallelProcessor;
      functionName = context.functionName;
      iToken = context.iToken;
      outputBuffer = context.outputBuffer;
      isStateScript = context.isStateScript;
      thisContext = context.parentContext;
    } else {
      error = (context.errorType != null);
      errorMessage = context.errorMessage;
      errorMessageUntranslated = context.errorMessageUntranslated;
      iCommandError = context.iCommandError;
      errorType = context.errorType;
    }
  }

  private String getContext(boolean withVariables) {
    StringBuffer sb = new StringBuffer();
    ScriptContext context = thisContext;
    while (context != null) {
      if (withVariables) {
        if (context.contextVariables != null) {
          sb.append(getScriptID(context));
          sb.append(StateManager.getVariableList(context.contextVariables, 80,
              true));
        }
      } else {
        sb.append(setErrorLineMessage(context.functionName, context.filename,
            getLinenumber(context), context.pc, statementAsString(
                context.statement, -9999, logMessages)));
      }
      context = context.parentContext;
    }
    if (withVariables) {
      if (contextVariables != null) {
        sb.append(getScriptID(null));
        sb.append(StateManager.getVariableList(contextVariables, 80, true));
      }
    } else {
      sb.append(setErrorLineMessage(functionName, filename,
          getLinenumber(null), pc, statementAsString(statement, -9999,
              logMessages)));
    }

    return sb.toString();
  }

  private int getLinenumber(ScriptContext c) {
    return (c == null ? lineNumbers[pc] : c.lineNumbers[c.pc]);
  }

  private String getScriptID(ScriptContext context) {
    String fuName = (context == null ? functionName : "function "
        + context.functionName);
    String fiName = (context == null ? filename : context.filename);
    return "\n# " + fuName + " (file " + fiName + ")\n";
  }

  // /////////////// error message support /////////////////

  private boolean error;
  private String errorMessage;
  protected String errorMessageUntranslated;
  protected String errorType;
  protected int iCommandError;

  public String getErrorMessage() {
    return errorMessage;
  }

  public String getErrorMessageUntranslated() {
    return errorMessageUntranslated == null ? errorMessage
        : errorMessageUntranslated;
  }

  private void setErrorMessage(String err) {
    errorMessageUntranslated = null;
    if (err == null) {
      error = false;
      errorType = null;
      errorMessage = null;
      iCommandError = -1;
      return;
    }
    error = true;
    if (errorMessage == null) // there could be a compiler error from a script
      // command
      errorMessage = GT._("script ERROR: ");
    errorMessage += err;
  }

  private boolean ignoreError;

  private void planeExpected() throws ScriptException {
    error(ERROR_planeExpected, "{a b c d}",
        "\"xy\" \"xz\" \"yz\" \"x=...\" \"y=...\" \"z=...\"", "$xxxxx");
  }

  private void integerOutOfRange(int min, int max) throws ScriptException {
    error(ERROR_integerOutOfRange, "" + min, "" + max);
  }

  private void numberOutOfRange(float min, float max) throws ScriptException {
    error(ERROR_numberOutOfRange, "" + min, "" + max);
  }

  void error(int iError, int i) throws ScriptException {
    iToken = i;
    error(iError, null, null, null, false);
  }

  void error(int iError) throws ScriptException {
    error(iError, null, null, null, false);
  }

  void error(int iError, String value) throws ScriptException {
    error(iError, value, null, null, false);
  }

  void error(int iError, String value, String more) throws ScriptException {
    error(iError, value, more, null, false);
  }

  void error(int iError, String value, String more, String more2)
      throws ScriptException {
    error(iError, value, more, more2, false);
  }

  private void warning(int iError, String value, String more)
      throws ScriptException {
    error(iError, value, more, null, true);
  }

  void error(int iError, String value, String more, String more2,
             boolean warningOnly) throws ScriptException {
    String strError = ignoreError ? null : errorString(iError, value, more,
        more2, true);
    String strUntranslated = (!ignoreError && GT.getDoTranslate() ? errorString(
        iError, value, more, more2, false)
        : null);
    if (!warningOnly)
      evalError(strError, strUntranslated);
    showString(strError);
  }

  void evalError(String message, String strUntranslated) throws ScriptException {
    if (ignoreError)
      throw new NullPointerException();
    if (!isSyntaxCheck) {
      // String s = viewer.getSetHistory(1);
      // viewer.addCommand(s + CommandHistory.ERROR_FLAG);
      setCursorWait(false);
      viewer.setBooleanProperty("refreshing", true);
      viewer.setStringProperty("_errormessage", strUntranslated);
    }
    throw new ScriptException(message, strUntranslated);
  }

  final static int ERROR_axisExpected = 0;
  final static int ERROR_backgroundModelError = 1;
  final static int ERROR_badArgumentCount = 2;
  final static int ERROR_badMillerIndices = 3;
  final static int ERROR_badRGBColor = 4;
  final static int ERROR_booleanExpected = 5;
  final static int ERROR_booleanOrNumberExpected = 6;
  final static int ERROR_booleanOrWhateverExpected = 7;
  final static int ERROR_colorExpected = 8;
  final static int ERROR_colorOrPaletteRequired = 9;
  final static int ERROR_commandExpected = 10;
  final static int ERROR_coordinateOrNameOrExpressionRequired = 11;
  final static int ERROR_drawObjectNotDefined = 12;
  final static int ERROR_endOfStatementUnexpected = 13;
  final static int ERROR_expressionExpected = 14;
  final static int ERROR_expressionOrIntegerExpected = 15;
  final static int ERROR_filenameExpected = 16;
  final static int ERROR_fileNotFoundException = 17;
  final static int ERROR_incompatibleArguments = 18;
  final static int ERROR_insufficientArguments = 19;
  final static int ERROR_integerExpected = 20;
  final static int ERROR_integerOutOfRange = 21;
  final static int ERROR_invalidArgument = 22;
  final static int ERROR_invalidParameterOrder = 23;
  final static int ERROR_keywordExpected = 24;
  final static int ERROR_moCoefficients = 25;
  final static int ERROR_moIndex = 26;
  final static int ERROR_moModelError = 27;
  final static int ERROR_moOccupancy = 28;
  final static int ERROR_moOnlyOne = 29;
  final static int ERROR_multipleModelsDisplayedNotOK = 30;
  final static int ERROR_noData = 31;
  final static int ERROR_noPartialCharges = 32;
  final static int ERROR_noUnitCell = 33;
  final static int ERROR_numberExpected = 34;
  final static int ERROR_numberMustBe = 35;
  final static int ERROR_numberOutOfRange = 36;
  final static int ERROR_objectNameExpected = 37;
  final static int ERROR_planeExpected = 38;
  final static int ERROR_propertyNameExpected = 39;
  final static int ERROR_spaceGroupNotFound = 40;
  final static int ERROR_stringExpected = 41;
  final static int ERROR_stringOrIdentifierExpected = 42;
  final static int ERROR_tooManyPoints = 43;
  final static int ERROR_tooManyScriptLevels = 44;
  final static int ERROR_unrecognizedAtomProperty = 45;
  final static int ERROR_unrecognizedBondProperty = 46;
  final static int ERROR_unrecognizedCommand = 47;
  final static int ERROR_unrecognizedExpression = 48;
  final static int ERROR_unrecognizedObject = 49;
  final static int ERROR_unrecognizedParameter = 50;
  final static int ERROR_unrecognizedParameterWarning = 51;
  final static int ERROR_unrecognizedShowParameter = 52;
  final static int ERROR_what = 53;
  final static int ERROR_writeWhat = 54;
  final static int ERROR_multipleModelsNotOK = 55;

  static String errorString(int iError, String value, String more,
                            String more2, boolean translated) {
    boolean doTranslate = false;
    if (!translated && (doTranslate = GT.getDoTranslate()) == true)
      GT.setDoTranslate(false);
    String msg;
    switch (iError) {
    default:
      msg = "Unknown error message number: " + iError;
      break;
    case ERROR_axisExpected:
      msg = GT._("x y z axis expected");
      break;
    case ERROR_backgroundModelError:
      msg = GT._("{0} not allowed with background model displayed");
      break;
    case ERROR_badArgumentCount:
      msg = GT._("bad argument count");
      break;
    case ERROR_badMillerIndices:
      msg = GT._("Miller indices cannot all be zero.");
      break;
    case ERROR_badRGBColor:
      msg = GT._("bad [R,G,B] color");
      break;
    case ERROR_booleanExpected:
      msg = GT._("boolean expected");
      break;
    case ERROR_booleanOrNumberExpected:
      msg = GT._("boolean or number expected");
      break;
    case ERROR_booleanOrWhateverExpected:
      msg = GT._("boolean, number, or {0} expected");
      break;
    case ERROR_colorExpected:
      msg = GT._("color expected");
      break;
    case ERROR_colorOrPaletteRequired:
      msg = GT._("a color or palette name (Jmol, Rasmol) is required");
      break;
    case ERROR_commandExpected:
      msg = GT._("command expected");
      break;
    case ERROR_coordinateOrNameOrExpressionRequired:
      msg = GT._("{x y z} or $name or (atom expression) required");
      break;
    case ERROR_drawObjectNotDefined:
      msg = GT._("draw object not defined");
      break;
    case ERROR_endOfStatementUnexpected:
      msg = GT._("unexpected end of script command");
      break;
    case ERROR_expressionExpected:
      msg = GT._("valid (atom expression) expected");
      break;
    case ERROR_expressionOrIntegerExpected:
      msg = GT._("(atom expression) or integer expected");
      break;
    case ERROR_filenameExpected:
      msg = GT._("filename expected");
      break;
    case ERROR_fileNotFoundException:
      msg = GT._("file not found");
      break;
    case ERROR_incompatibleArguments:
      msg = GT._("incompatible arguments");
      break;
    case ERROR_insufficientArguments:
      msg = GT._("insufficient arguments");
      break;
    case ERROR_integerExpected:
      msg = GT._("integer expected");
      break;
    case ERROR_integerOutOfRange:
      msg = GT._("integer out of range ({0} - {1})");
      break;
    case ERROR_invalidArgument:
      msg = GT._("invalid argument");
      break;
    case ERROR_invalidParameterOrder:
      msg = GT._("invalid parameter order");
      break;
    case ERROR_keywordExpected:
      msg = GT._("keyword expected");
      break;
    case ERROR_moCoefficients:
      msg = GT._("no MO coefficient data available");
      break;
    case ERROR_moIndex:
      msg = GT._("An MO index from 1 to {0} is required");
      break;
    case ERROR_moModelError:
      msg = GT._("no MO basis/coefficient data available for this frame");
      break;
    case ERROR_moOccupancy:
      msg = GT._("no MO occupancy data available");
      break;
    case ERROR_moOnlyOne:
      msg = GT._("Only one molecular orbital is available in this file");
      break;
    case ERROR_multipleModelsDisplayedNotOK:
      msg = GT._("{0} require that only one model be displayed");
      break;
    case ERROR_multipleModelsNotOK:
      msg = GT._("{0} requires that only one model be loaded");
      break;
    case ERROR_noData:
      msg = GT._("No data available");
      break;
    case ERROR_noPartialCharges:
      msg = GT
          ._("No partial charges were read from the file; Jmol needs these to render the MEP data.");
      break;
    case ERROR_noUnitCell:
      msg = GT._("No unit cell");
      break;
    case ERROR_numberExpected:
      msg = GT._("number expected");
      break;
    case ERROR_numberMustBe:
      msg = GT._("number must be ({0} or {1})");
      break;
    case ERROR_numberOutOfRange:
      msg = GT._("decimal number out of range ({0} - {1})");
      break;
    case ERROR_objectNameExpected:
      msg = GT._("object name expected after '$'");
      break;
    case ERROR_planeExpected:
      msg = GT
          ._("plane expected -- either three points or atom expressions or {0} or {1} or {2}");
      break;
    case ERROR_propertyNameExpected:
      msg = GT._("property name expected");
      break;
    case ERROR_spaceGroupNotFound:
      msg = GT._("space group {0} was not found.");
      break;
    case ERROR_stringExpected:
      msg = GT._("quoted string expected");
      break;
    case ERROR_stringOrIdentifierExpected:
      msg = GT._("quoted string or identifier expected");
      break;
    case ERROR_tooManyPoints:
      msg = GT._("too many rotation points were specified");
      break;
    case ERROR_tooManyScriptLevels:
      msg = GT._("too many script levels");
      break;
    case ERROR_unrecognizedAtomProperty:
      msg = GT._("unrecognized atom property");
      break;
    case ERROR_unrecognizedBondProperty:
      msg = GT._("unrecognized bond property");
      break;
    case ERROR_unrecognizedCommand:
      msg = GT._("unrecognized command");
      break;
    case ERROR_unrecognizedExpression:
      msg = GT._("runtime unrecognized expression");
      break;
    case ERROR_unrecognizedObject:
      msg = GT._("unrecognized object");
      break;
    case ERROR_unrecognizedParameter:
      msg = GT._("unrecognized {0} parameter");
      break;
    case ERROR_unrecognizedParameterWarning:
      msg = GT
          ._("unrecognized {0} parameter in Jmol state script (set anyway)");
      break;
    case ERROR_unrecognizedShowParameter:
      msg = GT._("unrecognized SHOW parameter --  use {0}");
      break;
    case ERROR_what:
      msg = "{0}";
      break;
    case ERROR_writeWhat:
      msg = GT._("write what? {0} or {1} \"filename\"");
      break;
    }
    if (msg.indexOf("{0}") < 0) {
      if (value != null)
        msg += ": " + value;
    } else {
      msg = TextFormat.simpleReplace(msg, "{0}", value);
      if (msg.indexOf("{1}") >= 0)
        msg = TextFormat.simpleReplace(msg, "{1}", more);
      else if (more != null)
        msg += ": " + more;
      if (msg.indexOf("{2}") >= 0)
        msg = TextFormat.simpleReplace(msg, "{2}", more);
    }
    if (doTranslate)
      GT.setDoTranslate(true);
    return msg;
  }

  static String setErrorLineMessage(String functionName, String filename,
                                    int lineCurrent, int pcCurrent,
                                    String lineInfo) {
    String err = "\n----";
    if (filename != null || functionName != null)
      err += "line " + lineCurrent + " command " + (pcCurrent + 1) + " of "
          + (functionName == null ? filename : functionName.equals("try") ? "try" : "function " + functionName)
          + ":";
    err += "\n         " + lineInfo;
    return err;
  }

  class ScriptException extends Exception {

    private String message;
    private String untranslated;

    ScriptException(String msg, String untranslated) {
      errorType = message = msg;
      iCommandError = pc;
      this.untranslated = (untranslated == null ? msg : untranslated);
      if (message == null) {
        message = "";
        return;
      }
      String s = getScriptContext().getContextTrace(null, true).toString();
      while (thisContext != null)
        popContext(false, false);
      message += s;
      this.untranslated += s;
      if (isSyntaxCheck
          || msg.indexOf("file recognized as a script file:") >= 0)
        return;
      Logger.error("eval ERROR: " + toString());
      if (viewer.autoExit)
        viewer.exitJmol();
    }

    protected String getErrorMessageUntranslated() {
      return untranslated;
    }

    public String toString() {
      return message;
    }
  }

  public String toString() {
    StringBuffer str = new StringBuffer();
    str.append("Eval\n pc:");
    str.append(pc);
    str.append("\n");
    str.append(aatoken.length);
    str.append(" statements\n");
    for (int i = 0; i < aatoken.length; ++i) {
      str.append("----\n");
      Token[] atoken = aatoken[i];
      for (int j = 0; j < atoken.length; ++j) {
        str.append(atoken[j]);
        str.append('\n');
      }
      str.append('\n');
    }
    str.append("END\n");
    return str.toString();
  }

  static String statementAsString(Token[] statement, int iTok,
                                  boolean doLogMessages) {
    if (statement.length == 0)
      return "";
    StringBuffer sb = new StringBuffer();
    int tok = statement[0].tok;
    switch (tok) {
    case Token.nada:
      return (String) statement[0].value;
    case Token.end:
      if (statement.length == 2
          && (statement[1].tok == Token.function || statement[1].tok == Token.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 == Token.set
        && statement[0].value.equals("") && statement[0].intValue == '=' && statement[1].tok != Token.expressionBegin);
    int len = statement.length;
    for (int i = 0; i < len; ++i) {
      Token token = statement[i];
      if (token == null) {
        len = i;
        break;
      }
      if (iTok == i - 1)
        sb.append(" <<");
      if (i != 0)
        sb.append(' ');
      if (i == 2 && setEquals) {
        setEquals = false;
        if (token.tok != Token.opEQ)
          sb.append("= ");
      }
      if (iTok == i && token.tok != Token.expressionEnd)
        sb.append(">> ");
      switch (token.tok) {
      case Token.expressionBegin:
        if (useBraces)
          sb.append("{");
        continue;
      case Token.expressionEnd:
        if (inClauseDefine && i == statement.length - 1)
          useBraces = false;
        if (useBraces)
          sb.append("}");
        continue;
      case Token.leftsquare:
      case Token.rightsquare:
        break;
      case Token.leftbrace:
      case Token.rightbrace:
        inBrace = (token.tok == Token.leftbrace);
        break;
      case Token.define:
        if (i > 0 && ((String) token.value).equals("define")) {
          sb.append("@");
          if (i + 1 < statement.length
              && statement[i + 1].tok == Token.expressionBegin) {
            if (!useBraces)
              inClauseDefine = true;
            useBraces = true;
          }
          continue;
        }
        break;
      case Token.on:
        sb.append("true");
        continue;
      case Token.off:
        sb.append("false");
        continue;
      case Token.select:
        break;
      case Token.integer:
        sb.append(token.intValue);
        continue;
      case Token.point3f:
      case Token.point4f:
      case Token.bitset:
        sb.append(ScriptVariable.sValue(token));
        continue;
      case Token.seqcode:
        sb.append('^');
        continue;
      case Token.spec_seqcode_range:
        if (token.intValue != Integer.MAX_VALUE)
          sb.append(token.intValue);
        else
          sb.append(Group.getSeqcodeString(getSeqCode(token)));
        token = statement[++i];
        sb.append(' ');
        // if (token.intValue == Integer.MAX_VALUE)
        sb.append(inBrace ? "-" : "- ");
        // fall through
      case Token.spec_seqcode:
        if (token.intValue != Integer.MAX_VALUE)
          sb.append(token.intValue);
        else
          sb.append(Group.getSeqcodeString(getSeqCode(token)));
        continue;
      case Token.spec_chain:
        sb.append("*:");
        sb.append((char) token.intValue);
        continue;
      case Token.spec_alternate:
        sb.append("*%");
        if (token.value != null)
          sb.append(token.value.toString());
        continue;
      case Token.spec_model:
        sb.append("*/");
        // fall through
      case Token.spec_model2:
      case Token.decimal:
        if (token.intValue < Integer.MAX_VALUE) {
          sb.append(Escape.escapeModelFileNumber(token.intValue));
        } else {
          sb.append("" + token.value);
        }
        continue;
      case Token.spec_resid:
        sb.append('[');
        sb.append(Group.getGroup3((short) token.intValue));
        sb.append(']');
        continue;
      case Token.spec_name_pattern:
        sb.append('[');
        sb.append(token.value);
        sb.append(']');
        continue;
      case Token.spec_atom:
        sb.append("*.");
        break;
      case Token.cell:
        if (token.value instanceof Point3f) {
          Point3f pt = (Point3f) token.value;
          sb.append("cell=").append(Escape.escape(pt));
          continue;
        }
        break;
      case Token.string:
        sb.append("\"").append(token.value).append("\"");
        continue;
      case Token.opEQ:
      case Token.opLE:
      case Token.opGE:
      case Token.opGT:
      case Token.opLT:
      case Token.opNE:
        // not quite right -- for "inmath"
        if (token.intValue == Token.property) {
          sb.append((String) statement[++i].value).append(" ");
        } else if (token.intValue != Integer.MAX_VALUE)
          sb.append(Token.nameOf(token.intValue)).append(" ");
        break;
      case Token.trycmd:
        continue;
      case Token.end:
        sb.append("end");
        continue;
      default:
        if (Token.tokAttr(token.tok, Token.identifier) || !doLogMessages)
          break;
        sb.append('\n').append(token.toString()).append('\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 ////////////////

  private Object getShapeProperty(int shapeType, String propertyName) {
    return shapeManager.getShapeProperty(shapeType, propertyName,
        Integer.MIN_VALUE);
  }

  private boolean getShapeProperty(int shapeType, String propertyName,
                                   Object[] data) {
    return shapeManager.getShapeProperty(shapeType, propertyName, data);
  }

  private Object getShapeProperty(int shapeType, String propertyName, int index) {
    return shapeManager.getShapeProperty(shapeType, propertyName, index);
  }

  private void addShapeProperty(List propertyList, String key, Object value) {
    if (isSyntaxCheck)
      return;
    propertyList.add(new Object[] { key, value });
  }

  private void loadShape(int iShape) {
    shapeManager.loadShape(iShape);
  }

  private void setObjectMad(int iShape, String name, int mad) {
    if (isSyntaxCheck)
      return;
    viewer.setObjectMad(iShape, name, mad);
  }

  private void setObjectArgb(String str, int argb) {
    if (isSyntaxCheck)
      return;
    viewer.setObjectArgb(str, argb);
  }

  private void setShapeProperty(int shapeType, String propertyName,
                                Object propertyValue) {
    if (isSyntaxCheck)
      return;
    shapeManager.setShapeProperty(shapeType, propertyName, propertyValue, null);
  }

  private void setShapeProperty(int iShape, String propertyName,
                                Object propertyValue, BitSet bs) {
    if (isSyntaxCheck)
      return;
    shapeManager.setShapeProperty(iShape, propertyName, propertyValue, bs);
  }

  private void setShapeSize(int shapeType, int size, BitSet bs) {
    // stars, halos, balls only
    if (isSyntaxCheck)
      return;
    shapeManager.setShapeSize(shapeType, size, null, bs);
  }

  private void setShapeSize(int shapeType, RadiusData rd) {
    if (isSyntaxCheck)
      return;
    shapeManager.setShapeSize(shapeType, 0, rd, null);
  }

  ////////////////////  setting properties ////////////////////////

  private void setBooleanProperty(String key, boolean value) {
    if (!isSyntaxCheck)
      viewer.setBooleanProperty(key, value);
  }

  private boolean setIntProperty(String key, int value) {
    if (!isSyntaxCheck)
      viewer.setIntProperty(key, value);
    return true;
  }

  private boolean setFloatProperty(String key, float value) {
    if (!isSyntaxCheck)
      viewer.setFloatProperty(key, value);
    return true;
  }

  private void setStringProperty(String key, String value) {
    if (!isSyntaxCheck)
      viewer.setStringProperty(key, value);
  }

  //////////////////// showing strings /////////////////

  private void showString(String str) {
    showString(str, false);
  }

  private void showString(String str, boolean isPrint) {
    if (isSyntaxCheck)
      return;
    if (outputBuffer != null)
      outputBuffer.append(str).append('\n');
    else
      viewer.showString(str, isPrint);
  }

  private void scriptStatusOrBuffer(String s) {
    if (isSyntaxCheck)
      return;
    if (outputBuffer != null) {
      outputBuffer.append(s).append('\n');
      return;
    }
    viewer.scriptStatus(s);
  }

  ///////////////// expression processing ///////////////////

  private Token[] tempStatement;
  private boolean isBondSet;
  private Object expressionResult;

  private BitSet atomExpression(int index) throws ScriptException {
    if (!checkToken(index))
      error(ERROR_badArgumentCount, index);
    return atomExpression(statement, index, 0, true, false, true, true);
  }

  private BitSet atomExpression(Token[] code, int pcStart, int pcStop,
                                boolean allowRefresh, boolean allowUnderflow,
                                boolean mustBeBitSet, boolean andNotDeleted)
      throws ScriptException {
    // note that this is general -- NOT just statement[]
    // errors reported would improperly access statement/line context
    // there should be no errors anyway, because this is for
    // predefined variables, but it is conceivable that one could
    // have a problem.

    isBondSet = false;
    if (code != statement) {
      tempStatement = statement;
      statement = code;
    }
    ScriptMathProcessor rpn = new ScriptMathProcessor(this, false, false,
        mustBeBitSet);
    Object val;
    int comparisonValue = Integer.MAX_VALUE;
    boolean refreshed = false;
    iToken = 1000;
    boolean ignoreSubset = (pcStart < 0);
    boolean isInMath = false;
    int nExpress = 0;
    int atomCount = viewer.getAtomCount();
    if (ignoreSubset)
      pcStart = -pcStart;
    ignoreSubset |= isSyntaxCheck;
    if (pcStop == 0 && code.length > pcStart)
      pcStop = pcStart + 1;
    // if (logMessages)
    // viewer.scriptStatus("start to evaluate expression");
    expression_loop: for (int pc = pcStart; pc < pcStop; ++pc) {
      iToken = pc;
      Token instruction = code[pc];
      if (instruction == null)
        break;
      Object value = instruction.value;
      // if (logMessages)
      // viewer.scriptStatus("instruction=" + instruction);
      switch (instruction.tok) {
      case Token.expressionBegin:
        pcStart = pc;
        pcStop = code.length;
        nExpress++;
        break;
      case Token.expressionEnd:
        nExpress--;
        if (nExpress > 0)
          continue;
        break expression_loop;
      case Token.leftbrace:
        if (isPoint3f(pc)) {
          Point3f pt = getPoint3f(pc, true);
          if (pt != null) {
            rpn.addX(pt);
            pc = iToken;
            break;
          }
        }
        break; // ignore otherwise
      case Token.rightbrace:
        if (pc > 0 && code[pc - 1].tok == Token.leftbrace)
          rpn.addX(new BitSet());
        break;
      case Token.leftsquare:
        isInMath = true;
        rpn.addOp(instruction);
        break;
      case Token.rightsquare:
        isInMath = false;
        rpn.addOp(instruction);
        break;
      case Token.define:
        rpn.addX(getAtomBitSet(this, (String) value));
        break;
      case Token.hkl:
        rpn.addX(new ScriptVariable(instruction));
        rpn.addX(new ScriptVariable(Token.point4f, hklParameter(pc + 2)));
        pc = iToken;
        break;
      case Token.plane:
        rpn.addX(new ScriptVariable(instruction));
        rpn.addX(new ScriptVariable(Token.point4f, planeParameter(pc + 2)));
        pc = iToken;
        break;
      case Token.coord:
        rpn.addX(new ScriptVariable(instruction));
        rpn.addX(getPoint3f(pc + 2, true));
        pc = iToken;
        break;
      case Token.string:
        String s = (String) value;
        if (s.indexOf("({") == 0) {
          BitSet bs = Escape.unescapeBitset(s);
          if (bs != null) {
            rpn.addX(bs);
            break;
          }
        }
        rpn.addX(new ScriptVariable(instruction));
        // note that the compiler has changed all within() types to strings.
        if (s.equals("hkl")) {
          rpn.addX(new ScriptVariable(Token.point4f, hklParameter(pc + 2)));
          pc = iToken;
        }
        break;
      case Token.smiles:
      case Token.search:
      case Token.substructure:
      case Token.within:
      case Token.connected:
      case Token.comma:
        rpn.addOp(instruction);
        break;
      case Token.all:
        rpn.addX(viewer.getModelUndeletedAtomsBitSet(-1));
        break;
      case Token.none:
        rpn.addX(new BitSet());
        break;
      case Token.on:
      case Token.off:
        rpn.addX(new ScriptVariable(instruction));
        break;
      case Token.selected:
        rpn.addX(BitSetUtil.copy(viewer.getSelectionSet(false)));
        break;
      case Token.subset:
        BitSet bsSubset = viewer.getSelectionSubset();
        rpn.addX(bsSubset == null ? viewer.getModelUndeletedAtomsBitSet(-1)
            : BitSetUtil.copy(bsSubset));
        break;
      case Token.hidden:
        rpn.addX(BitSetUtil.copy(viewer.getHiddenSet()));
        break;
      case Token.fixed:
        rpn.addX(BitSetUtil.copy(viewer.getMotionFixedAtoms()));
        break;
      case Token.displayed:
        rpn.addX(BitSetUtil.copyInvert(viewer.getHiddenSet(), atomCount));
        break;
      case Token.visible:
        if (!isSyntaxCheck && !refreshed)
          viewer.setModelVisibility();
        refreshed = true;
        rpn.addX(viewer.getVisibleSet());
        break;
      case Token.clickable:
        // a bit different, because it requires knowing what got slabbed
        if (!isSyntaxCheck && allowRefresh)
          refresh();
        rpn.addX(viewer.getClickableSet());
        break;
      case Token.spec_atom:
        int atomID = instruction.intValue;
        if (atomID > 0)
          rpn.addX(compareInt(Token.atomid, Token.opEQ, atomID));
        else
          rpn.addX(getAtomBits(instruction.tok, (String) value));
        break;
      case Token.carbohydrate:
      case Token.dna:
      case Token.hetero:
      case Token.isaromatic:
      case Token.nucleic:
      case Token.protein:
      case Token.purine:
      case Token.pyrimidine:
      case Token.rna:
      case Token.spec_name_pattern:
      case Token.spec_alternate:
      case Token.specialposition:
      case Token.symmetry:
      case Token.unitcell:
        rpn.addX(getAtomBits(instruction.tok, (String) value));
        break;
      case Token.spec_model:
        // from select */1002 or */1000002 or */1.2
        // */1002 is equivalent to 1.2 when more than one file is present
      case Token.spec_model2:
        // from just using the number 1.2
        int iModel = instruction.intValue;
        if (iModel == Integer.MAX_VALUE && value instanceof Integer) {
          // from select */n
          iModel = ((Integer) value).intValue();
          if (!viewer.haveFileSet()) {
            rpn.addX(getAtomBits(Token.spec_model, new Integer(iModel)));
            break;
          }
          if (iModel < 1000)
            iModel = iModel * 1000000;
          else
            iModel = (iModel / 1000) * 1000000 + iModel % 1000;
        }
        rpn.addX(bitSetForModelFileNumber(iModel));
        break;
      case Token.spec_resid:
      case Token.spec_chain:
        rpn
            .addX(getAtomBits(instruction.tok,
                new Integer(instruction.intValue)));
        break;
      case Token.spec_seqcode:
        if (isInMath)
          rpn.addXNum(ScriptVariable.intVariable(instruction.intValue));
        else
          rpn.addX(getAtomBits(Token.spec_seqcode, new Integer(
              getSeqCode(instruction))));
        break;
      case Token.spec_seqcode_range:
        if (isInMath) {
          rpn.addXNum(ScriptVariable.intVariable(instruction.intValue));
          rpn.addX(Token.tokenMinus);
          rpn.addXNum(ScriptVariable.intVariable(code[++pc].intValue));
          break;
        }
        int chainID = (pc + 3 < code.length && code[pc + 2].tok == Token.opAND
            && code[pc + 3].tok == Token.spec_chain ? code[pc + 3].intValue
            : '\t');
        rpn.addX(getAtomBits(Token.spec_seqcode_range, new int[] {
            getSeqCode(instruction), getSeqCode(code[++pc]), chainID }));
        if (chainID != '\t')
          pc += 2;
        break;
      case Token.cell:
        Point3f pt = (Point3f) value;
        rpn.addX(getAtomBits(Token.cell, new int[] { (int) (pt.x * 1000),
            (int) (pt.y * 1000), (int) (pt.z * 1000) }));
        break;
      case Token.thismodel:
        rpn.addX(viewer.getModelUndeletedAtomsBitSet(viewer
            .getCurrentModelIndex()));
        break;
      case Token.hydrogen:
      case Token.amino:
      case Token.backbone:
      case Token.solvent:
      case Token.helix:
      case Token.helixalpha:
      case Token.helix310:
      case Token.helixpi:
      case Token.sidechain:
      case Token.surface:
        rpn.addX(lookupIdentifierValue((String) value));
        break;
      case Token.opLT:
      case Token.opLE:
      case Token.opGE:
      case Token.opGT:
      case Token.opEQ:
      case Token.opNE:
        if (pc + 1 == code.length)
          error(ERROR_invalidArgument);
        val = code[++pc].value;
        int tokOperator = instruction.tok;
        int tokWhat = instruction.intValue;
        String property = (tokWhat == Token.property ? (String) val : null);
        if (property != null) {
          if (pc + 1 == code.length)
            error(ERROR_invalidArgument);
          val = code[++pc].value;
        }
        if (tokWhat == Token.configuration && tokOperator != Token.opEQ)
          error(ERROR_invalidArgument);
        if (isSyntaxCheck) {
          rpn.addX(new BitSet());
          break;
        }
        boolean isModel = (tokWhat == Token.model);
        boolean isIntProperty = Token.tokAttr(tokWhat, Token.intproperty);
        boolean isFloatProperty = Token.tokAttr(tokWhat, Token.floatproperty);
        boolean isIntOrFloat = isIntProperty && isFloatProperty;
        boolean isStringProperty = !isIntProperty
            && Token.tokAttr(tokWhat, Token.strproperty);
        if (tokWhat == Token.element)
          isIntProperty = !(isStringProperty = false);
        int tokValue = code[pc].tok;
        comparisonValue = code[pc].intValue;
        float comparisonFloat = Float.NaN;
        if (val instanceof Point3f) {
          if (tokWhat == Token.color) {
            comparisonValue = Graphics3D.colorPtToInt((Point3f) val);
            tokValue = Token.integer;
            isIntProperty = true;
          }
        } else if (val instanceof String) {
          if (tokWhat == Token.color) {
            comparisonValue = Graphics3D.getArgbFromString((String) val);
            if (comparisonValue == 0
                && Token.tokAttr(tokValue, Token.identifier)) {
              val = getStringParameter((String) val, true);
              if (((String) val).startsWith("{")) {
                val = Escape.unescapePoint((String) val);
                if (val instanceof Point3f)
                  comparisonValue = Graphics3D.colorPtToInt((Point3f) val);
                else
                  comparisonValue = 0;
              } else {
                comparisonValue = Graphics3D.getArgbFromString((String) val);
              }
            }
            tokValue = Token.integer;
            isIntProperty = true;
          } else if (isStringProperty) {
            if (Token.tokAttr(tokValue, Token.identifier))
              val = getStringParameter((String) val, true);
          } else {
            if (Token.tokAttr(tokValue, Token.identifier))
              val = getNumericParameter((String) val);
            if (val instanceof String) {
              if (tokWhat == Token.structure || tokWhat == Token.substructure || tokWhat == Token.element)
                isStringProperty = !(isIntProperty = (comparisonValue != Integer.MAX_VALUE));
              else
                val = ScriptVariable.nValue(code[pc]);
            }
            if (val instanceof Integer)
              comparisonFloat = comparisonValue = ((Integer) val).intValue();
            else if (val instanceof Float && isModel)
              comparisonValue = ModelCollection
                  .modelFileNumberFromFloat(((Float) val).floatValue());
          }
        }
        if (isStringProperty && !(val instanceof String)) {
          val = "" + val;
        }
        if (val instanceof Integer || tokValue == Token.integer) {
          if (isModel) {
            if (comparisonValue >= 1000000)
              tokWhat = -Token.model;
          } else if (isIntOrFloat) {
            isFloatProperty = false;
          } else if (isFloatProperty) {
            comparisonFloat = comparisonValue;
          }
        } else if (val instanceof Float) {
          if (isModel) {
            tokWhat = -Token.model;
          } else {
            comparisonFloat = ((Float) val).floatValue();
            if (isIntOrFloat) {
              isIntProperty = false;
            } else if (isIntProperty) {
              comparisonValue = (int) comparisonFloat;
            }
          }
        } else if (!isStringProperty) {
          iToken++;
          error(ERROR_invalidArgument);
        }
        if (isModel && comparisonValue >= 1000000
            && comparisonValue % 1000000 == 0) {
          comparisonValue /= 1000000;
          tokWhat = Token.file;
          isModel = false;
        }
        if (tokWhat == -Token.model && tokOperator == Token.opEQ) {
          rpn.addX(bitSetForModelFileNumber(comparisonValue));
          break;
        }
        if (value != null && ((String) value).indexOf("-") >= 0) {
          if (isIntProperty)
            comparisonValue = -comparisonValue;
          else if (!Float.isNaN(comparisonFloat))
            comparisonFloat = -comparisonFloat;
        }
        float[] data = (tokWhat == Token.property ? viewer
            .getDataFloat(property) : null);
        rpn.addX(isIntProperty ? compareInt(tokWhat, tokOperator,
            comparisonValue) : isStringProperty ? compareString(tokWhat,
            tokOperator, (String) val) : compareFloat(tokWhat, data,
            tokOperator, comparisonFloat));
        break;
      case Token.decimal:
      case Token.integer:
        rpn.addXNum(new ScriptVariable(instruction));
        break;
      case Token.bitset:
      case Token.point3f:
        rpn.addX(value);
        break;
      default:
        if (Token.tokAttr(instruction.tok, Token.mathop)) {
          rpn.addOp(instruction);
          break;
        }
        if (!(value instanceof String)) {
          // catch-all: point4f, hash, list, etc.
          rpn.addX(value);
          break;
        }
        val = getParameter((String) value, 0);
        if (isInMath) {
          rpn.addX(val);
          break;
        }
        if (val instanceof String)
          val = getStringObjectAsVariable((String) val, null);
        if (val instanceof List) {
          BitSet bs = ScriptVariable.unEscapeBitSetArray((List)val, true);
          if (bs == null)
            val = value;
          else
            val = bs;
        }
        if (val instanceof String)
          val = lookupIdentifierValue((String) value);
        rpn.addX(val);
        break;
      }
    }
    expressionResult = rpn.getResult(allowUnderflow);
    if (expressionResult == null) {
      if (allowUnderflow)
        return null;
      if (!isSyntaxCheck)
        rpn.dumpStacks("after getResult");
      error(ERROR_endOfStatementUnexpected);
    }
    expressionResult = ((ScriptVariable) expressionResult).value;
    if (expressionResult instanceof String
        && (mustBeBitSet || ((String) expressionResult).startsWith("({"))) {
      // allow for select @{x} where x is a string that can evaluate to a bitset
      expressionResult = (isSyntaxCheck ? new BitSet() : getAtomBitSet(this,
          (String) expressionResult));
    }
    if (!mustBeBitSet && !(expressionResult instanceof BitSet))
      return null; // because result is in expressionResult in that case
    BitSet bs = (expressionResult instanceof BitSet ? (BitSet) expressionResult
        : new BitSet());
    isBondSet = (expressionResult instanceof BondSet);
    if (!isBondSet) {
      viewer.excludeAtoms(bs, ignoreSubset);
    }
    if (tempStatement != null) {
      statement = tempStatement;
      tempStatement = null;
    }
    return bs;
  }

  private BitSet compareFloat(int tokWhat, float[] data, int tokOperator,
                              float comparisonFloat) {
    BitSet bs = new BitSet();
    int atomCount = viewer.getAtomCount();
    ModelSet modelSet = viewer.getModelSet();
    Atom[] atoms = modelSet.atoms;
    float propertyFloat = 0;
    viewer.autoCalculate(tokWhat);
    for (int i = atomCount; --i >= 0;) {
      boolean match = false;
      Atom atom = atoms[i];
      switch (tokWhat) {
      default:
        propertyFloat = Atom.atomPropertyFloat(viewer, atom, tokWhat);
        break;
      case Token.property:
        if (data == null || data.length <= i)
          continue;
        propertyFloat = data[i];
      }
      match = compareFloat(tokOperator, propertyFloat, comparisonFloat);
      if (match)
        bs.set(i);
    }
    return bs;
  }

  private BitSet compareString(int tokWhat, int tokOperator,
                               String comparisonString) throws ScriptException {
    BitSet bs = new BitSet();
    Atom[] atoms = viewer.getModelSet().atoms;
    int atomCount = viewer.getAtomCount();
    boolean isCaseSensitive = (tokWhat == Token.chain && viewer
        .getChainCaseSensitive());
    if (!isCaseSensitive)
      comparisonString = comparisonString.toLowerCase();
    for (int i = atomCount; --i >= 0;) {
      String propertyString = Atom.atomPropertyString(atoms[i], tokWhat);
      if (!isCaseSensitive)
        propertyString = propertyString.toLowerCase();
      if (compareString(tokOperator, propertyString, comparisonString))
        bs.set(i);
    }
    return bs;
  }

  protected BitSet compareInt(int tokWhat, int tokOperator, int comparisonValue) {
    int propertyValue = Integer.MAX_VALUE;
    BitSet propertyBitSet = null;
    int bitsetComparator = tokOperator;
    int bitsetBaseValue = comparisonValue;
    int atomCount = viewer.getAtomCount();
    ModelSet modelSet = viewer.getModelSet();
    Atom[] atoms = modelSet.atoms;
    int imax = -1;
    int imin = 0;
    int iModel = -1;
    int[] cellRange = null;
    int nOps = 0;
    BitSet bs;
    // preliminary setup
    switch (tokWhat) {
    case Token.symop:
      switch (bitsetComparator) {
      case Token.opGE:
      case Token.opGT:
        imax = Integer.MAX_VALUE;
        break;
      }
      break;
    case Token.atomindex:
      try {
        switch (tokOperator) {
        case Token.opLT:
          return BitSetUtil.newBitSet(0, comparisonValue);
        case Token.opLE:
          return BitSetUtil.newBitSet(0, comparisonValue + 1);
        case Token.opGE:
          return BitSetUtil.newBitSet(comparisonValue, atomCount);
        case Token.opGT:
          return BitSetUtil.newBitSet(comparisonValue + 1, atomCount);
        case Token.opEQ:
          return (comparisonValue < atomCount ? BitSetUtil.newBitSet(
              comparisonValue, comparisonValue + 1) : new BitSet());
        case Token.opNE:
        default:
          bs = BitSetUtil.setAll(atomCount);
          if (comparisonValue >= 0)
            bs.clear(comparisonValue);
          return bs;
        }
      } catch (Exception e) {
        return new BitSet();
      }
    }
    bs = new BitSet(atomCount);
    for (int i = 0; i < atomCount; ++i) {
      boolean match = false;
      Atom atom = atoms[i];
      switch (tokWhat) {
      default:
        propertyValue = Atom.atomPropertyInt(atom, tokWhat);
        break;
      case Token.configuration:
        // these are all-inclusive; no need to do a by-atom comparison
        return BitSetUtil.copy(viewer.getConformation(-1, comparisonValue - 1,
            false));
      case Token.symop:
        propertyBitSet = atom.getAtomSymmetry();
        if (propertyBitSet == null)
          continue;
        if (atom.getModelIndex() != iModel) {
          iModel = atom.getModelIndex();
          cellRange = modelSet.getModelCellRange(iModel);
          nOps = modelSet.getModelSymmetryCount(iModel);
        }
        if (bitsetBaseValue >= 200) {
          if (cellRange == null)
            continue;
          /*
           * symop>=1000 indicates symop*1000 + lattice_translation(555) for
           * this the comparision is only with the translational component; the
           * symop itself must match thus: select symop!=1655 selects all
           * symop=1 and translation !=655 select symop>=2555 selects all
           * symop=2 and translation >555 symop >=200 indicates any symop in the
           * specified translation (a few space groups have > 100 operations)
           *
           * Note that when normalization is not done, symop=1555 may not be in
           * the base unit cell. Everything is relative to wherever the base
           * atoms ended up, usually in 555, but not necessarily.
           *
           * The reason this is tied together an atom may have one translation
           * for one symop and another for a different one.
           *
           * Bob Hanson - 10/2006
           */
          comparisonValue = bitsetBaseValue % 1000;
          int symop = bitsetBaseValue / 1000 - 1;
          if (symop < 0) {
            match = true;
          } else if (nOps == 0 || symop >= 0
              && !(match = propertyBitSet.get(symop))) {
            continue;
          }
          bitsetComparator = Token.none;
          if (symop < 0)
            propertyValue = atom.getCellTranslation(comparisonValue, cellRange,
                nOps);
          else
            propertyValue = atom.getSymmetryTranslation(symop, cellRange, nOps);
        } else if (nOps > 0) {
          if (comparisonValue > nOps) {
            if (bitsetComparator != Token.opLT
                && bitsetComparator != Token.opLE)
              continue;
          }
          if (bitsetComparator == Token.opNE) {
            if (comparisonValue > 0 && comparisonValue <= nOps
                && !propertyBitSet.get(comparisonValue)) {
              bs.set(i);
            }
            continue;
          }
        }
        switch (bitsetComparator) {
        case Token.opLT:
          imax = comparisonValue - 1;
          break;
        case Token.opLE:
          imax = comparisonValue;
          break;
        case Token.opGE:
          imin = comparisonValue - 1;
          break;
        case Token.opGT:
          imin = comparisonValue;
          break;
        case Token.opEQ:
          imax = comparisonValue;
          imin = comparisonValue - 1;
          break;
        case Token.opNE:
          match = !propertyBitSet.get(comparisonValue);
          break;
        }
        if (imin < 0)
          imin = 0;
        if (imin < imax) {
          int pt = propertyBitSet.nextSetBit(imin);
          if (pt >= 0 && pt < imax)
            match = true;
        }
        // note that a symop property can be both LE and GT !
        if (!match || propertyValue == Integer.MAX_VALUE)
          tokOperator = Token.none;
      }
      switch (tokOperator) {
      case Token.none:
        break;
      case Token.opLT:
        match = (propertyValue < comparisonValue);
        break;
      case Token.opLE:
        match = (propertyValue <= comparisonValue);
        break;
      case Token.opGE:
        match = (propertyValue >= comparisonValue);
        break;
      case Token.opGT:
        match = (propertyValue > comparisonValue);
        break;
      case Token.opEQ:
        match = (propertyValue == comparisonValue);
        break;
      case Token.opNE:
        match = (propertyValue != comparisonValue);
        break;
      }
      if (match)
        bs.set(i);
    }
    return bs;
  }

  private boolean compareString(int tokOperator, String propertyValue,
                                String comparisonValue) throws ScriptException {
    switch (tokOperator) {
    case Token.opEQ:
    case Token.opNE:
      return (TextFormat.isMatch(propertyValue, comparisonValue, true, true) == (tokOperator == Token.opEQ));
    default:
      error(ERROR_invalidArgument);
    }
    return false;
  }

  private static boolean compareFloat(int tokOperator, float propertyFloat,
                                      float comparisonFloat) {
    switch (tokOperator) {
    case Token.opLT:
      return propertyFloat < comparisonFloat;
    case Token.opLE:
      return propertyFloat <= comparisonFloat;
    case Token.opGE:
      return propertyFloat >= comparisonFloat;
    case Token.opGT:
      return propertyFloat > comparisonFloat;
    case Token.opEQ:
      return propertyFloat == comparisonFloat;
    case Token.opNE:
      return propertyFloat != comparisonFloat;
    }
    return false;
  }

  private BitSet getAtomBits(int tokType, Object specInfo) {
    return (isSyntaxCheck ? new BitSet() : viewer
        .getAtomBits(tokType, specInfo));
  }

  private static int getSeqCode(Token instruction) {
    return (instruction.intValue != Integer.MAX_VALUE ? Group.getSeqcode(
        instruction.intValue, ' ') : ((Integer) instruction.value).intValue());
  }

  /*
   * ****************************************************************************
   * ============================================================== checks and
   * parameter retrieval
   * ==============================================================
   */

  private int checkLast(int i) throws ScriptException {
    return checkLength(i + 1) - 1;
  }

  private int checkLength(int length) throws ScriptException {
    if (length >= 0)
      return checkLength(length, 0);
    // max
    if (statementLength > -length) {
      iToken = -length;
      error(ERROR_badArgumentCount);
    }
    return statementLength;
  }

  private int checkLength(int length, int errorPt) throws ScriptException {
    if (statementLength != length) {
      iToken = errorPt > 0 ? errorPt : statementLength;
      error(errorPt > 0 ? ERROR_invalidArgument : ERROR_badArgumentCount);
    }
    return statementLength;
  }

  private int checkLength23() throws ScriptException {
    iToken = statementLength;
    if (statementLength != 2 && statementLength != 3)
      error(ERROR_badArgumentCount);
    return statementLength;
  }

  private int checkLength34() throws ScriptException {
    iToken = statementLength;
    if (statementLength != 3 && statementLength != 4)
      error(ERROR_badArgumentCount);
    return statementLength;
  }

  private int theTok;
  private Token theToken;

  private Token getToken(int i) throws ScriptException {
    if (!checkToken(i))
      error(ERROR_endOfStatementUnexpected);
    theToken = statement[i];
    theTok = theToken.tok;
    return theToken;
  }

  private int tokAt(int i) {
    return (i < statementLength && statement[i] != null ? statement[i].tok
        : Token.nada);
  }

  private int tokAt(int i, Token[] args) {
    return (i < args.length && args[i] != null ? args[i].tok : Token.nada);
  }

  private Token tokenAt(int i, Token[] args) {
    return (i < args.length ? args[i] : null);
  }

  private boolean checkToken(int i) {
    return (iToken = i) < statementLength;
  }

  private int modelNumberParameter(int index) throws ScriptException {
    int iFrame = 0;
    boolean useModelNumber = false;
    switch (tokAt(index)) {
    case Token.integer:
      useModelNumber = true;
      // fall through
    case Token.decimal:
      iFrame = getToken(index).intValue; // decimal Token intValue is
      // model/frame number encoded
      break;
    case Token.string:
      iFrame = JmolConstants.modelValue(stringParameter(index));
      break;
    default:
      error(ERROR_invalidArgument);
    }
    return viewer.getModelNumberIndex(iFrame, useModelNumber, true);
  }

  private String optParameterAsString(int i) throws ScriptException {
    if (i >= statementLength)
      return "";
    return parameterAsString(i);
  }

  private String parameterAsString(int i) throws ScriptException {
    getToken(i);
    if (theToken == null)
      error(ERROR_endOfStatementUnexpected);
    return (theTok == Token.integer ? "" + theToken.intValue : ""
        + theToken.value);
  }

  private int intParameter(int index) throws ScriptException {
    if (checkToken(index))
      if (getToken(index).tok == Token.integer)
        return theToken.intValue;
    error(ERROR_integerExpected);
    return 0;
  }

  private int intParameter(int i, int min, int max) throws ScriptException {
    int val = intParameter(i);
    if (val < min || val > max)
      integerOutOfRange(min, max);
    return val;
  }

  private boolean isFloatParameter(int index) {
    switch (tokAt(index)) {
    case Token.integer:
    case Token.decimal:
      return true;
    }
    return false;
  }

  private float floatParameter(int i, float min, float max)
      throws ScriptException {
    float val = floatParameter(i);
    if (val < min || val > max)
      numberOutOfRange(min, max);
    return val;
  }

  private float floatParameter(int index) throws ScriptException {
    if (checkToken(index)) {
      getToken(index);
      switch (theTok) {
      case Token.spec_seqcode_range:
        return -theToken.intValue;
      case Token.spec_seqcode:
      case Token.integer:
        return theToken.intValue;
      case Token.spec_model2:
      case Token.decimal:
        return ((Float) theToken.value).floatValue();
      }
    }
    error(ERROR_numberExpected);
    return 0;
  }

  private float[] floatParameterSet(int i, int nMin, int nMax)
      throws ScriptException {
    int tok = tokAt(i);
    if (tok == Token.spacebeforesquare)
      tok = tokAt(++i);
    boolean haveBrace = (tok == Token.leftbrace);
    boolean haveSquare = (tok == Token.leftsquare);
    float[] fparams = null;
    List v = new ArrayList();
    int n = 0;
    if (haveBrace || haveSquare)
      i++;
    Point3f pt;
    String s = null;
    switch (tokAt(i)) {
    case Token.string:
      s = ScriptVariable.sValue(statement[i]);
      s = TextFormat.replaceAllCharacters(s, "{},[]\"'", ' ');
      fparams = Parser.parseFloatArray(s);
      n = fparams.length;
      break;
    case Token.varray:
      fparams = ScriptVariable.flistValue(statement[i], 0);
      n = fparams.length;
      break;
    default:
      while (n < nMax) {
        tok = tokAt(i);
        if (haveBrace && tok == Token.rightbrace || haveSquare
            && tok == Token.rightsquare)
          break;
        switch (tok) {
        case Token.comma:
        case Token.leftbrace:
        case Token.rightbrace:
          break;
        case Token.string:
          break;
        case Token.point3f:
          pt = getPoint3f(i, false);
          v.add(new Float(pt.x));
          v.add(new Float(pt.y));
          v.add(new Float(pt.z));
          n += 3;
          break;
        case Token.point4f:
          Point4f pt4 = getPoint4f(i);
          v.add(new Float(pt4.x));
          v.add(new Float(pt4.y));
          v.add(new Float(pt4.z));
          v.add(new Float(pt4.w));
          n += 4;
          break;
        default:
          v.add(new Float(floatParameter(i)));
          n++;
          if (n == nMax && haveSquare && tokAt(i + 1) == Token.rightbrace)
            i++;
        }
        i++;
      }
    }
    if (haveBrace && tokAt(i++) != Token.rightbrace || haveSquare
        && tokAt(i++) != Token.rightsquare)
      error(ERROR_invalidArgument);
    iToken = i - 1;
    if (n < nMin || n > nMax)
      error(ERROR_invalidArgument);
    if (fparams == null) {
      fparams = new float[n];
      for (int j = 0; j < n; j++)
        fparams[j] = ((Float)v.get(j)).floatValue();
    }
    return fparams;
  }

  private Point3f[] getPointArray(int i, int nPoints) throws ScriptException {
    Point3f[] points = (nPoints < 0 ? null : new Point3f[nPoints]);
    List vp = (nPoints < 0 ? new ArrayList() : null);
    int tok = (i < 0 ? Token.varray : getToken(i++).tok);
    switch (tok) {
    case Token.varray:
      List v = ((ScriptVariable) theToken).getList();
      if (nPoints >= 0 && v.size() != nPoints)
        error(ERROR_invalidArgument);
      nPoints = v.size();
      if (points == null)
        points = new Point3f[nPoints];
      for (int j = 0; j < nPoints; j++)
        if ((points[j] = ScriptVariable.ptValue((ScriptVariable) v.get(j))) == null)
          error(ERROR_invalidArgument);
      return points;
    case Token.spacebeforesquare:
      tok = tokAt(i++);
      break;
    }
    if (tok != Token.leftsquare)
      error(ERROR_invalidArgument);
    int n = 0;
    while (tok != Token.rightsquare && tok != Token.nada) {
      tok = getToken(i).tok;
      switch (tok) {
      case Token.nada:
      case Token.rightsquare:
        break;
      case Token.comma:
        i++;
        break;
      default:
        if (nPoints >= 0 && n == nPoints) {
          tok = Token.nada;
          break;
        }
        Point3f pt = getPoint3f(i, true);
        if (points == null)
          vp.add(pt);
        else
          points[n] = pt;
        n++;
        i = iToken + 1;
      }
    }
    if (tok != Token.rightsquare)
      error(ERROR_invalidArgument);
    if (points == null) {
      points = new Point3f[vp.size()];
      for (int j = vp.size(); --j >= 0;)
        points[j] = (Point3f) vp.get(j);
    }
    return points;
  }

  private float[][] floatArraySet(int i, int nX, int nY) throws ScriptException {
    int tok = tokAt(i++);
    if (tok == Token.spacebeforesquare)
      tok = tokAt(i++);
    if (tok != Token.leftsquare)
      error(ERROR_invalidArgument);
    float[][] fparams = new float[nX][];
    int n = 0;
    while (tok != Token.rightsquare) {
      tok = getToken(i).tok;
      switch (tok) {
      case Token.spacebeforesquare:
      case Token.rightsquare:
        continue;
      case Token.comma:
        i++;
        break;
      case Token.leftsquare:
        i++;
        float[] f = new float[nY];
        fparams[n++] = f;
        for (int j = 0; j < nY; j++) {
          f[j] = floatParameter(i++);
          if (tokAt(i) == Token.comma)
            i++;
        }
        if (tokAt(i++) != Token.rightsquare)
          error(ERROR_invalidArgument);
        tok = Token.nada;
        if (n == nX && tokAt(i) != Token.rightsquare)
          error(ERROR_invalidArgument);
        break;
      default:
        error(ERROR_invalidArgument);
      }
    }
    return fparams;
  }

  private float[][][] floatArraySet(int i, int nX, int nY, int nZ)
      throws ScriptException {
    int tok = tokAt(i++);
    if (tok == Token.spacebeforesquare)
      tok = tokAt(i++);
    if (tok != Token.leftsquare || nX <= 0)
      error(ERROR_invalidArgument);
    float[][][] fparams = new float[nX][][];
    int n = 0;
    while (tok != Token.rightsquare) {
      tok = getToken(i).tok;
      switch (tok) {
      case Token.spacebeforesquare:
      case Token.rightsquare:
        continue;
      case Token.comma:
        i++;
        break;
      case Token.leftsquare:
        fparams[n++] = floatArraySet(i, nY, nZ);
        i = ++iToken;
        tok = Token.nada;
        if (n == nX && tokAt(i) != Token.rightsquare)
          error(ERROR_invalidArgument);
        break;
      default:
        error(ERROR_invalidArgument);
      }
    }
    return fparams;
  }

  private String stringParameter(int index) throws ScriptException {
    if (!checkToken(index) || getToken(index).tok != Token.string)
      error(ERROR_stringExpected);
    return (String) theToken.value;
  }

  private String[] stringParameterSet(int i) throws ScriptException {
    switch (tokAt(i)) {
    case Token.string:
      String s = stringParameter(i);
      if (s.startsWith("[\"")) {
        Object o = viewer.evaluateExpression(s);
        if (o instanceof String)
          return TextFormat.split((String) o, '\n');
      }
      return new String[] { s };
    case Token.spacebeforesquare:
      i += 2;
      break;
    case Token.leftsquare:
      ++i;
      break;
    case Token.varray:
      return ScriptVariable.listValue(getToken(i));
    default:
      error(ERROR_invalidArgument);
    }
    int tok;
    List v = new ArrayList();
    while ((tok = tokAt(i)) != Token.rightsquare) {
      switch (tok) {
      case Token.comma:
        break;
      case Token.string:
        v.add(stringParameter(i));
        break;
      default:
      case Token.nada:
        error(ERROR_invalidArgument);
      }
      i++;
    }
    iToken = i;
    int n = v.size();
    String[] sParams = new String[n];
    for (int j = 0; j < n; j++) {
      sParams[j] = (String) v.get(j);
    }
    return sParams;
  }

 
  private String objectNameParameter(int index) throws ScriptException {
    if (!checkToken(index))
      error(ERROR_objectNameExpected);
    return parameterAsString(index);
  }

  private boolean booleanParameter(int i) throws ScriptException {
    if (statementLength == i)
      return true;
    switch (getToken(checkLast(i)).tok) {
    case Token.on:
      return true;
    case Token.off:
      return false;
    default:
      error(ERROR_booleanExpected);
    }
    return false;
  }

  private Point3f atomCenterOrCoordinateParameter(int i) throws ScriptException {
    switch (getToken(i).tok) {
    case Token.bitset:
    case Token.expressionBegin:
      BitSet bs = atomExpression(statement, i, 0, true, false, false, true);
      if (bs != null)
        return viewer.getAtomSetCenter(bs);
      if (expressionResult instanceof Point3f)
        return (Point3f) expressionResult;
      error(ERROR_invalidArgument);
      break;
    case Token.leftbrace:
    case Token.point3f:
      return getPoint3f(i, true);
    }
    error(ERROR_invalidArgument);
    // impossible return
    return null;
  }

  private boolean isCenterParameter(int i) {
    int tok = tokAt(i);
    return (tok == Token.dollarsign || tok == Token.leftbrace
        || tok == Token.expressionBegin || tok == Token.point3f || tok == Token.bitset);
  }

  private Point3f centerParameter(int i) throws ScriptException {
    return centerParameter(i, Integer.MIN_VALUE);
  }

  private Point3f centerParameter(int i, int modelIndex) throws ScriptException {
    Point3f center = null;
    expressionResult = null;
    if (checkToken(i)) {
      switch (getToken(i).tok) {
      case Token.dollarsign:
        String id = objectNameParameter(++i);
        int index = Integer.MIN_VALUE;
        if (tokAt(i + 1) == Token.leftsquare) {
          index = ScriptVariable.iValue((ScriptVariable) parameterExpressionList(-i - 1, true).get(0));
          if (getToken(--iToken).tok != Token.rightsquare)
            error(ERROR_invalidArgument);
        }
        if (isSyntaxCheck)
          return new Point3f();
        if (tokAt(i + 1) == Token.per
            && (tokAt(i + 2) == Token.length || tokAt(i + 2) == Token.size)) {
          index = Integer.MAX_VALUE;
          iToken = i + 2;
        }
        if ((center = getObjectCenter(id, index, modelIndex)) == null)
          error(ERROR_drawObjectNotDefined, id);
        break;
      case Token.bitset:
      case Token.expressionBegin:
      case Token.leftbrace:
      case Token.point3f:
        center = atomCenterOrCoordinateParameter(i);
        break;
      }
    }
    if (center == null)
      error(ERROR_coordinateOrNameOrExpressionRequired);
    return center;
  }

  private Point4f planeParameter(int i) throws ScriptException {
    Vector3f vAB = new Vector3f();
    Vector3f vAC = new Vector3f();
    Point4f plane = null;
    boolean isNegated = (tokAt(i) == Token.minus);
    if (isNegated)
      i++;
    if (i < statementLength)
      switch (getToken(i).tok) {
      case Token.point4f:
        plane = (Point4f) theToken.value;
        break;
      case Token.dollarsign:
        String id = objectNameParameter(++i);
        if (isSyntaxCheck)
          return new Point4f();
        int shapeType = shapeManager.getShapeIdFromObjectName(id);
        switch (shapeType) {
        case JmolConstants.SHAPE_DRAW:
          setShapeProperty(JmolConstants.SHAPE_DRAW, "thisID", id);
          Point3f[] points = (Point3f[]) getShapeProperty(
              JmolConstants.SHAPE_DRAW, "vertices");
          if (points == null || points.length < 3 || points[0] == null
              || points[1] == null || points[2] == null)
            break;
          plane = Measure.getPlaneThroughPoints(points[0], points[1],
              points[2], new Vector3f(), vAB, vAC);
          break;
        case JmolConstants.SHAPE_ISOSURFACE:
          setShapeProperty(JmolConstants.SHAPE_ISOSURFACE, "thisID", id);
          plane = (Point4f) getShapeProperty(JmolConstants.SHAPE_ISOSURFACE,
              "plane");
          break;
        }
        break;
      case Token.x:
        if (!checkToken(++i) || getToken(i++).tok != Token.opEQ)
          evalError("x=?", null);
        plane = new Point4f(1, 0, 0, -floatParameter(i));
        break;
      case Token.y:
        if (!checkToken(++i) || getToken(i++).tok != Token.opEQ)
          evalError("y=?", null);
        plane = new Point4f(0, 1, 0, -floatParameter(i));
        break;
      case Token.z:
        if (!checkToken(++i) || getToken(i++).tok != Token.opEQ)
          evalError("z=?", null);
        plane = new Point4f(0, 0, 1, -floatParameter(i));
        break;
      case Token.identifier:
      case Token.string:
        String str = parameterAsString(i);
        if (str.equalsIgnoreCase("xy"))
          return new Point4f(0, 0, 1, 0);
        if (str.equalsIgnoreCase("xz"))
          return new Point4f(0, 1, 0, 0);
        if (str.equalsIgnoreCase("yz"))
          return new Point4f(1, 0, 0, 0);
        iToken += 2;
        break;
      case Token.leftbrace:
        if (!isPoint3f(i)) {
          plane = getPoint4f(i);
          break;
        }
        // fall through
      case Token.bitset:
      case Token.expressionBegin:
        Point3f pt1 = atomCenterOrCoordinateParameter(i);
        if (getToken(++iToken).tok == Token.comma)
          ++iToken;
        Point3f pt2 = atomCenterOrCoordinateParameter(iToken);
        if (getToken(++iToken).tok == Token.comma)
          ++iToken;
        Point3f pt3 = atomCenterOrCoordinateParameter(iToken);
        i = iToken;
        Vector3f norm = new Vector3f();
        float w = Measure.getNormalThroughPoints(pt1, pt2, pt3, norm, vAB, vAC);
        plane = new Point4f(norm.x, norm.y, norm.z, w);
        if (!isSyntaxCheck && Logger.debugging)
          Logger.debug("points: " + pt1 + pt2 + pt3 + " defined plane: "
              + plane);
        break;
      }
    if (plane == null)
      planeExpected();
    if (isNegated) {
      plane.scale(-1);
    }
    return plane;
  }

  private Point4f hklParameter(int i) throws ScriptException {
    if (!isSyntaxCheck && viewer.getCurrentUnitCell() == null)
      error(ERROR_noUnitCell);
    Point3f pt = (Point3f) getPointOrPlane(i, false, true, false, true, 3, 3);
    Point4f p = getHklPlane(pt);
    if (p == null)
      error(ERROR_badMillerIndices);
    if (!isSyntaxCheck && Logger.debugging)
      Logger.info("defined plane: " + p);
    return p;
  }

  protected Point4f getHklPlane(Point3f pt) {
    Vector3f vAB = new Vector3f();
    Vector3f vAC = new Vector3f();
    Point3f pt1 = new Point3f(pt.x == 0 ? 1 : 1 / pt.x, 0, 0);
    Point3f pt2 = new Point3f(0, pt.y == 0 ? 1 : 1 / pt.y, 0);
    Point3f pt3 = new Point3f(0, 0, pt.z == 0 ? 1 : 1 / pt.z);
    // trick for 001 010 100 is to define the other points on other edges
    if (pt.x == 0 && pt.y == 0 && pt.z == 0) {
      return null;
    } else if (pt.x == 0 && pt.y == 0) {
      pt1.set(1, 0, pt3.z);
      pt2.set(0, 1, pt3.z);
    } else if (pt.y == 0 && pt.z == 0) {
      pt2.set(pt1.x, 0, 1);
      pt3.set(pt1.x, 1, 0);
    } else if (pt.z == 0 && pt.x == 0) {
      pt3.set(0, pt2.y, 1);
      pt1.set(1, pt2.y, 0);
    } else if (pt.x == 0) {
      pt1.set(1, pt2.y, 0);
    } else if (pt.y == 0) {
      pt2.set(0, 1, pt3.z);
    } else if (pt.z == 0) {
      pt3.set(pt1.x, 0, 1);
    }
    // base this one on the currently defined unit cell
    viewer.toCartesian(pt1, false);
    viewer.toCartesian(pt2, false);
    viewer.toCartesian(pt3, false);
    Vector3f plane = new Vector3f();
    float w = Measure.getNormalThroughPoints(pt1, pt2, pt3, plane, vAB, vAC);
    return new Point4f(plane.x, plane.y, plane.z, w);
  }

  private int getMadParameter() throws ScriptException {
    // wireframe, ssbond, hbond, struts
    int mad = 1;
    switch (getToken(1).tok) {
    case Token.only:
      restrictSelected(false, false);
      break;
    case Token.on:
      break;
    case Token.off:
      mad = 0;
      break;
    case Token.integer:
      int radiusRasMol = intParameter(1, 0, 750);
      mad = radiusRasMol * 4 * 2;
      break;
    case Token.decimal:
      mad = (int) (floatParameter(1, -3, 3) * 1000 * 2);
      if (mad < 0) {
        restrictSelected(false, false);
        mad = -mad;
      }
      break;
    default:
      error(ERROR_booleanOrNumberExpected);
    }
    return mad;
  }

  private int getSetAxesTypeMad(int index) throws ScriptException {
    if (index == statementLength)
      return 1;
    switch (getToken(checkLast(index)).tok) {
    case Token.on:
      return 1;
    case Token.off:
      return 0;
    case Token.dotted:
      return -1;
    case Token.integer:
      return intParameter(index, -1, 19);
    case Token.decimal:
      float angstroms = floatParameter(index, 0, 2);
      return (int) (angstroms * 1000 * 2);
    }
    error(ERROR_booleanOrWhateverExpected, "\"DOTTED\"");
    return 0;
  }

  private boolean isColorParam(int i) {
    int tok = tokAt(i);
    return (tok == Token.navy
        || tok == Token.spacebeforesquare || tok == Token.leftsquare
        || tok == Token.point3f || isPoint3f(i)
        || (tok == Token.string || Token.tokAttr(tok, Token.identifier))
        && Graphics3D.getArgbFromString((String) statement[i].value) != 0);
  }

  private int getArgbParam(int index) throws ScriptException {
    return getArgbParam(index, false);
  }

  private int getArgbParamLast(int index, boolean allowNone)
      throws ScriptException {
    int icolor = getArgbParam(index, allowNone);
    checkLast(iToken);
    return icolor;
  }

  private int getArgbParam(int index, boolean allowNone) throws ScriptException {
    Point3f pt = null;
    if (checkToken(index)) {
      switch (getToken(index).tok) {
      default:
        if (!Token.tokAttr(theTok, Token.identifier))
          break;
        // fall through
      case Token.navy:
      case Token.string:
        return Graphics3D.getArgbFromString(parameterAsString(index));
      case Token.spacebeforesquare:
        return getColorTriad(index + 2);
      case Token.leftsquare:
        return getColorTriad(++index);
      case Token.point3f:
        pt = (Point3f) theToken.value;
        break;
      case Token.leftbrace:
        pt = getPoint3f(index, false);
        break;
      case Token.none:
        if (allowNone)
          return 0;
      }
    }
    if (pt == null)
      error(ERROR_colorExpected);
    return Graphics3D.colorPtToInt(pt);
  }

  private int getColorTriad(int i) throws ScriptException {
    float[] colors = new float[3];
    int n = 0;
    String hex = "";
    getToken(i);
    Point3f pt = null;
    float val = 0;
    out: switch (theTok) {
    case Token.integer:
    case Token.spec_seqcode:
    case Token.decimal:
      for (; i < statementLength; i++) {
        switch (getToken(i).tok) {
        case Token.comma:
          continue;
        case Token.identifier:
          if (n != 1 || colors[0] != 0)
            error(ERROR_badRGBColor);
          hex = "0" + parameterAsString(i);
          break out;
        case Token.decimal:
          if (n > 2)
            error(ERROR_badRGBColor);
          val = floatParameter(i);
          break;
        case Token.integer:
          if (n > 2)
            error(ERROR_badRGBColor);
          val = theToken.intValue;
          break;
        case Token.spec_seqcode:
          if (n > 2)
            error(ERROR_badRGBColor);
          val = ((Integer) theToken.value).intValue() % 256;
          break;
        case Token.rightsquare:
          if (n != 3)
            error(ERROR_badRGBColor);
          --i;
          pt = new Point3f(colors[0], colors[1], colors[2]);
          break out;
        default:
          error(ERROR_badRGBColor);
        }
        colors[n++] = val;
      }
      error(ERROR_badRGBColor);
      break;
    case Token.point3f:
      pt = (Point3f) theToken.value;
      break;
    case Token.identifier:
      hex = parameterAsString(i);
      break;
    default:
      error(ERROR_badRGBColor);
    }
    if (getToken(++i).tok != Token.rightsquare)
      error(ERROR_badRGBColor);
    if (pt != null)
      return Graphics3D.colorPtToInt(pt);
    if ((n = Graphics3D.getArgbFromString("[" + hex + "]")) == 0)
      error(ERROR_badRGBColor);
    return n;
  }

  private boolean coordinatesAreFractional;

  private boolean isPoint3f(int i) {
    // first check for simple possibilities:
    boolean isOK;
    if ((isOK = (tokAt(i) == Token.point3f)) || tokAt(i) == Token.point4f
        || isFloatParameter(i + 1) && isFloatParameter(i + 2)
        && isFloatParameter(i + 3) && isFloatParameter(i + 4))
      return isOK;
    ignoreError = true;
    int t = iToken;
    isOK = true;
    try {
      getPoint3f(i, true);
    } catch (Exception e) {
      isOK = false;
    }
    ignoreError = false;
    iToken = t;
    return isOK;
  }

  private Point3f getPoint3f(int i, boolean allowFractional)
      throws ScriptException {
    return (Point3f) getPointOrPlane(i, false, allowFractional, true, false, 3,
        3);
  }

  private Point4f getPoint4f(int i) throws ScriptException {
    return (Point4f) getPointOrPlane(i, false, false, false, false, 4, 4);
  }

  private Object getPointOrPlane(int index, boolean integerOnly,
                                 boolean allowFractional, boolean doConvert,
                                 boolean implicitFractional, int minDim,
                                 int maxDim) throws ScriptException {
    // { x y z } or {a/b c/d e/f} are encoded now as seqcodes and model numbers
    // so we decode them here. It's a bit of a pain, but it isn't too bad.
    float[] coord = new float[6];
    int n = 0;
    coordinatesAreFractional = implicitFractional;
    if (tokAt(index) == Token.point3f) {
      if (minDim <= 3 && maxDim >= 3)
        return (Point3f) getToken(index).value;
      error(ERROR_invalidArgument);
    }
    if (tokAt(index) == Token.point4f) {
      if (minDim <= 4 && maxDim >= 4)
        return (Point4f) getToken(index).value;
      error(ERROR_invalidArgument);
    }
    int multiplier = 1;
    out: for (int i = index; i < statement.length; i++) {
      switch (getToken(i).tok) {
      case Token.leftbrace:
      case Token.comma:
      case Token.opAnd:
      case Token.opAND:
        break;
      case Token.rightbrace:
        break out;
      case Token.minus:
        multiplier = -1;
        break;
      case Token.spec_seqcode_range:
        if (n == 6)
          error(ERROR_invalidArgument);
        coord[n++] = theToken.intValue;
        multiplier = -1;
        break;
      case Token.integer:
      case Token.spec_seqcode:
        if (n == 6)
          error(ERROR_invalidArgument);
        coord[n++] = theToken.intValue * multiplier;
        multiplier = 1;
        break;
      case Token.divide:
        getToken(++i);
      case Token.spec_model: // after a slash
        n--;
        if (n < 0 || integerOnly)
          error(ERROR_invalidArgument);
        if (theToken.value instanceof Integer || theTok == Token.integer) {
          coord[n++] /= (theToken.intValue == Integer.MAX_VALUE ? ((Integer) theToken.value)
              .intValue()
              : theToken.intValue);
        } else if (theToken.value instanceof Float) {
          coord[n++] /= ((Float) theToken.value).floatValue();
        }
        coordinatesAreFractional = true;
        break;
      case Token.decimal:
      case Token.spec_model2:
        if (integerOnly)
          error(ERROR_invalidArgument);
        if (n == 6)
          error(ERROR_invalidArgument);
        coord[n++] = ((Float) theToken.value).floatValue();
        break;
      default:
        error(ERROR_invalidArgument);
      }
    }
    if (n < minDim || n > maxDim)
      error(ERROR_invalidArgument);
    if (n == 3) {
      Point3f pt = new Point3f(coord[0], coord[1], coord[2]);
      if (coordinatesAreFractional && doConvert && !isSyntaxCheck) {
        viewer.toCartesian(pt, !viewer.getFractionalRelative());
      }
      return pt;
    }
    if (n == 4) {
      if (coordinatesAreFractional) // no fractional coordinates for planes (how
        // to convert?)
        error(ERROR_invalidArgument);
      Point4f plane = new Point4f(coord[0], coord[1], coord[2], coord[3]);
      return plane;
    }
    return coord;
  }

  private Point3f xypParameter(int index) throws ScriptException {
    // [x y] or [x,y] refers to an xy point on the screen
    // just a Point3f with z = Float.MAX_VALUE
    // [x y %] or [x,y %] refers to an xy point on the screen
    // as a percent
    // just a Point3f with z = -Float.MAX_VALUE

    int tok = tokAt(index);
    if (tok == Token.spacebeforesquare)
      tok = tokAt(++index);
    if (tok != Token.leftsquare || !isFloatParameter(++index))
      return null;
    Point3f pt = new Point3f();
    pt.x = floatParameter(index);
    if (tokAt(++index) == Token.comma)
      index++;
    if (!isFloatParameter(index))
      return null;
    pt.y = floatParameter(index);
    boolean isPercent = (tokAt(++index) == Token.percent);
    if (isPercent)
      ++index;
    if (tokAt(index) != Token.rightsquare)
      return null;
    iToken = index;
    pt.z = (isPercent ? -1 : 1) * Float.MAX_VALUE;
    return pt;
  }

  /*
   * ****************************************************************
   * =============== command dispatch ===============================
   */

  /**
   * 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]);
  }

  /**
   * 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
   */
  private boolean checkContinue() {
    if (interruptExecution)
      return false;

    if (executionStepping && isCommandDisplayable(pc)) {
      viewer.scriptStatus("Next: " + getNextStatement(),
          "stepping -- type RESUME to continue", 0, null);
      executionPaused = true;
    } else if (!executionPaused) {
      return true;
    }

    if (Logger.debugging) {
      Logger.info("script execution paused at command " + (pc + 1) + " level "
          + scriptLevel + ": " + thisCommand);
    }

    try {
      refresh();
      while (executionPaused) {
        viewer.popHoldRepaint("pause"); // does not actually do a repaint
        Thread.sleep(100);
        String script = viewer.getInterruptScript();
        if (script != "") {
          resumePausedExecution();
          setErrorMessage(null);
          ScriptContext scSave = getScriptContext();
          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) {
            scriptStatusOrBuffer(errorMessage);
            setErrorMessage(null);
          }
          restoreScriptContext(scSave, true, false, false);
          pauseExecution(false);
        }
        viewer.pushHoldRepaint("pause");
      }
      if (!isSyntaxCheck && !interruptExecution && !executionStepping) {
        viewer.scriptStatus("script execution "
            + (error || interruptExecution ? "interrupted" : "resumed"));
      }
    } catch (Exception e) {
      viewer.pushHoldRepaint("pause");
    }
    Logger.debug("script execution resumed");
    // once more to trap quit during pause
    return !error && !interruptExecution;
  }

  /**
   * here we go -- everything else in this class is called by this method or one
   * of its subsidiary methods.
   *
   *
   * @param doList
   * @throws ScriptException
   */
  private void instructionDispatchLoop(boolean doList) throws ScriptException {
    long timeBegin = 0;
    vProcess = null;
    boolean isForCheck = false; // indicates the stage of the for command loop
    if (shapeManager == null)
      shapeManager = viewer.getShapeManager();
    debugScript = logMessages = false;
    if (!isSyntaxCheck)
      setDebugging();
    if (logMessages) {
      timeBegin = System.currentTimeMillis();
      viewer.scriptStatus("Eval.instructionDispatchLoop():" + timeBegin);
      viewer.scriptStatus(script);
    }
    if (pcEnd == 0)
      pcEnd = Integer.MAX_VALUE;
    if (lineEnd == 0)
      lineEnd = Integer.MAX_VALUE;
    String lastCommand = "";
    if (aatoken == null)
      return;
    for (; pc < aatoken.length && pc < pcEnd; pc++) {
      if (!isSyntaxCheck && !checkContinue())
        break;
      if (lineNumbers[pc] > lineEnd)
        break;
      theToken = (aatoken[pc].length == 0 ? null : aatoken[pc][0]);
      // when checking scripts, we can't check statments
      // containing @{...}
      if (!historyDisabled && !isSyntaxCheck
          && scriptLevel <= commandHistoryLevelMax && !tQuiet) {
        String cmdLine = getCommand(pc, true, true);
        if (theToken != null
            && cmdLine.length() > 0
            && !cmdLine.equals(lastCommand)
            && (theToken.tok == Token.function
                || theToken.tok == Token.parallel || !Token.tokAttr(
                theToken.tok, Token.flowCommand)))
          viewer.addCommand(lastCommand = cmdLine);
      }
      if (!isSyntaxCheck) {
        String script = viewer.getInterruptScript();
        if (script != "")
          runScript(script);
      }
      if (!setStatement(pc)) {
        Logger.info(getCommand(pc, true, false)
            + " -- STATEMENT CONTAINING @{} SKIPPED");
        continue;
      }
      thisCommand = getCommand(pc, false, true);
      fullCommand = thisCommand + getNextComment();
      getToken(0);
      iToken = 0;
      if (doList || !isSyntaxCheck) {
        int milliSecDelay = viewer.getScriptDelay();
        if (doList || milliSecDelay > 0 && scriptLevel > 0) {
          if (milliSecDelay > 0)
            delay(-(long) milliSecDelay);
          viewer.scriptEcho("$[" + scriptLevel + "." + lineNumbers[pc] + "."
              + (pc + 1) + "] " + thisCommand);
        }
      }
      if (vProcess != null
          && (theTok != Token.end || statementLength < 2 || statement[1].tok != Token.process)) {
        vProcess.add(statement);
        continue;
      }
      if (isSyntaxCheck) {
        if (isCmdLine_c_or_C_Option)
          Logger.info(thisCommand);
        if (statementLength == 1 && statement[0].tok != Token.function
            && statement[0].tok != Token.parallel)
          continue;
      } else {
        if (debugScript)
          logDebugScript(0);
        if (scriptLevel == 0 && viewer.logCommands())
          viewer.log(thisCommand);
        if (logMessages && theToken != null)
          Logger.debug(theToken.toString());
      }
      if (theToken == null)
        continue;
      if (Token.tokAttr(theToken.tok, Token.shapeCommand))
        processShapeCommand(theToken.tok);
      else
        switch (theToken.tok) {
        case Token.nada:
          if (isSyntaxCheck || !viewer.getMessageStyleChime())
            break;
          String s = (String) theToken.value;
          if (s == null)
            break;
          if (outputBuffer == null)
            viewer.showMessage(s);
          scriptStatusOrBuffer(s);
          break;
        case Token.push:
          pushContext((ContextToken) theToken);
          break;
        case Token.pop:
          popContext(true, false);
          break;
        case Token.colon:
          break;
        case Token.catchcmd:
        case Token.breakcmd:
        case Token.continuecmd:
        case Token.elsecmd:
        case Token.elseif:
        case Token.end:
        case Token.endifcmd:
        case Token.forcmd:
        case Token.gotocmd:
        case Token.ifcmd:
        case Token.switchcmd:
        case Token.casecmd:
        case Token.defaultcmd:
        case Token.loop:
        case Token.process:
        case Token.whilecmd:
          isForCheck = flowControl(theToken.tok, isForCheck);
          break;
        case Token.animation:
          animation();
          break;
        case Token.assign:
          assign();
          break;
        case Token.background:
          background(1);
          break;
        case Token.bind:
          bind();
          break;
        case Token.bondorder:
          bondorder();
          break;
        case Token.calculate:
          calculate();
          break;
        case Token.cd:
          cd();
          break;
        case Token.center:
          center(1);
          break;
        case Token.centerAt:
          centerAt();
          break;
        case Token.color:
          color();
          break;
        case Token.compare:
          compare();
          break;
        case Token.configuration:
          configuration();
          break;
        case Token.connect:
          connect(1);
          break;
        case Token.console:
          console();
          break;
        case Token.data:
          data();
          break;
        case Token.define:
          define();
          break;
        case Token.delay:
          delay();
          break;
        case Token.delete:
          delete();
          break;
        case Token.depth:
          slab(true);
          break;
        case Token.display:
          display(true);
          break;
        case Token.exit: // flush the queue and...
        case Token.quit: // quit this only if it isn't the first command
          if (isSyntaxCheck)
            break;
          if (pc > 0 && theToken.tok == Token.exit)
            viewer.clearScriptQueue();
          interruptExecution = (pc > 0 || !viewer.usingScriptQueue());
          break;
        case Token.exitjmol:
          if (isSyntaxCheck || viewer.isApplet())
            return;
          viewer.exitJmol();
          break;
        case Token.file:
          file();
          break;
        case Token.fixed:
          fixed();
          break;
        case Token.font:
          font(-1, 0);
          break;
        case Token.frame:
        case Token.model:
          frame(1);
          break;
        case Token.parallel: // not actually found
        case Token.function:
        case Token.identifier:
          function(); // when a function is a command
          break;
        case Token.getproperty:
          getProperty();
          break;
        case Token.help:
          help();
          break;
        case Token.hide:
          display(false);
          break;
        case Token.hbond:
          hbond(true);
          break;
        case Token.history:
          history(1);
          break;
        case Token.hover:
          hover();
          break;
        case Token.initialize:
          viewer.initialize();
          break;
        case Token.invertSelected:
          invertSelected();
          break;
        case Token.javascript:
          script(Token.javascript, null);
          break;
        case Token.load:
          load();
          break;
        case Token.log:
          log();
          break;
        case Token.mapProperty:
          mapProperty();
          break;
        case Token.message:
          message();
          break;
        case Token.minimize:
          minimize();
          break;
        case Token.move:
          move();
          break;
        case Token.moveto:
          moveto();
          break;
        case Token.navigate:
          navigate();
          break;
        case Token.pause: // resume is done differently
          pause();
          break;
        case Token.plot:
        case Token.quaternion:
        case Token.ramachandran:
          plot(statement);
          break;
        case Token.print:
          print();
          break;
        case Token.prompt:
          prompt();
          break;
        case Token.refresh:
          refresh();
          break;
        case Token.reset:
          reset();
          break;
        case Token.restore:
          restore();
          break;
        case Token.restrict:
          restrict();
          break;
        case Token.resume:
          if (!isSyntaxCheck)
            resumePausedExecution();
          break;
        case Token.returncmd:
          returnCmd(null);
          break;
        case Token.rotate:
          rotate(false, false);
          break;
        case Token.rotateSelected:
          rotate(false, true);
          break;
        case Token.save:
          save();
          break;
        case Token.set:
          set();
          break;
        case Token.script:
          script(Token.script, null);
          break;
        case Token.select:
          select(1);
          break;
        case Token.selectionhalos:
          selectionHalo(1);
          break;
        case Token.show:
          show();
          break;
        case Token.slab:
          slab(false);
          break;
        case Token.spin:
          rotate(true, false);
          break;
        case Token.ssbond:
          ssbond();
          break;
        case Token.step:
          if (pause())
            stepPausedExecution();
          break;
        case Token.stereo:
          stereo();
          break;
        case Token.structure:
          structure();
          break;
        case Token.subset:
          subset();
          break;
        case Token.sync:
          sync();
          break;
        case Token.timeout:
          timeout(1);
          break;
        case Token.translate:
          translate(false);
          break;
        case Token.translateSelected:
          translate(true);
          break;
        case Token.unbind:
          unbind();
          break;
        case Token.vibration:
          vibration();
          break;
        case Token.write:
          write(null);
          break;
        case Token.zap:
          zap(true);
          break;
        case Token.zoom:
          zoom(false);
          break;
        case Token.zoomTo:
          zoom(true);
          break;
        default:
          error(ERROR_unrecognizedCommand);
        }
      setCursorWait(false);
      // at end because we could use continue to avoid it
      if (executionStepping) {
        executionPaused = (isCommandDisplayable(pc + 1));
      }
    }
  }

  private void setCursorWait(boolean TF) {
    if (!isSyntaxCheck)
      viewer.setCursor(TF ? Viewer.CURSOR_WAIT : Viewer.CURSOR_DEFAULT);
  }
  private void processShapeCommand(int tok) throws ScriptException {
    int iShape = 0;
    switch (tok) {
    case Token.axes:
      iShape = JmolConstants.SHAPE_AXES;
      break;
    case Token.backbone:
      iShape = JmolConstants.SHAPE_BACKBONE;
      break;
    case Token.boundbox:
      iShape = JmolConstants.SHAPE_BBCAGE;
      break;
    case Token.cartoon:
      iShape = JmolConstants.SHAPE_CARTOON;
      break;
    case Token.dipole:
      iShape = JmolConstants.SHAPE_DIPOLES;
      break;
    case Token.dots:
      iShape = JmolConstants.SHAPE_DOTS;
      break;
    case Token.draw:
      iShape = JmolConstants.SHAPE_DRAW;
      break;
    case Token.echo:
      iShape = JmolConstants.SHAPE_ECHO;
      break;
    case Token.ellipsoid:
      iShape = JmolConstants.SHAPE_ELLIPSOIDS;
      break;
    case Token.frank:
      iShape = JmolConstants.SHAPE_FRANK;
      break;
    case Token.geosurface:
      iShape = JmolConstants.SHAPE_GEOSURFACE;
      break;
    case Token.halo:
      iShape = JmolConstants.SHAPE_HALOS;
      break;
    case Token.isosurface:
      iShape = JmolConstants.SHAPE_ISOSURFACE;
      break;
    case Token.label:
      iShape = JmolConstants.SHAPE_LABELS;
      break;
    case Token.lcaocartoon:
      iShape = JmolConstants.SHAPE_LCAOCARTOON;
      break;
    case Token.measurements:
    case Token.measure:
      iShape = JmolConstants.SHAPE_MEASURES;
      break;
    case Token.meshRibbon:
      iShape = JmolConstants.SHAPE_MESHRIBBON;
      break;
    case Token.mo:
      iShape = JmolConstants.SHAPE_MO;
      break;
    case Token.plot3d:
      iShape = JmolConstants.SHAPE_PLOT3D;
      break;
    case Token.pmesh:
      iShape = JmolConstants.SHAPE_PMESH;
      break;
    case Token.polyhedra:
      iShape = JmolConstants.SHAPE_POLYHEDRA;
      break;
    case Token.ribbon:
      iShape = JmolConstants.SHAPE_RIBBONS;
      break;
    case Token.rocket:
      iShape = JmolConstants.SHAPE_ROCKETS;
      break;
    case Token.spacefill: // aka cpk
      iShape = JmolConstants.SHAPE_BALLS;
      break;
    case Token.star:
      iShape = JmolConstants.SHAPE_STARS;
      break;
    case Token.strands:
      iShape = JmolConstants.SHAPE_STRANDS;
      break;
    case Token.struts:
      iShape = JmolConstants.SHAPE_STRUTS;
      break;
    case Token.trace:
      iShape = JmolConstants.SHAPE_TRACE;
      break;
    case Token.unitcell:
      iShape = JmolConstants.SHAPE_UCCAGE;
      break;
    case Token.vector:
      iShape = JmolConstants.SHAPE_VECTORS;
      break;
    case Token.wireframe:
      iShape = JmolConstants.SHAPE_STICKS;
      break;
    default:
      error(ERROR_unrecognizedCommand);
    }

    // atom objects:

    switch (tok) {
    case Token.backbone:
    case Token.cartoon:
    case Token.meshRibbon:
    case Token.ribbon:
    case Token.rocket:
    case Token.strands:
    case Token.trace:
      proteinShape(iShape);
      return;
    case Token.dots:
    case Token.geosurface:
      dots(iShape);
      return;
    case Token.ellipsoid:
      ellipsoid();
      return;
    case Token.halo:
    case Token.spacefill: // aka cpk
    case Token.star:
      setAtomShapeSize(iShape, (tok == Token.halo ? -1f : 1f));
      return;
    case Token.label:
      label(1);
      return;
    case Token.lcaocartoon:
      lcaoCartoon();
      return;
    case Token.polyhedra:
      polyhedra();
      return;
    case Token.struts:
      struts();
      return;
    case Token.vector:
      vector();
      return;
    case Token.wireframe:
      wireframe();
      return;
    }

    // other objects:

    switch (tok) {
    case Token.axes:
      axes(1);
      return;
    case Token.boundbox:
      boundbox(1);
      return;
    case Token.dipole:
      dipole();
      return;
    case Token.draw:
      draw();
      return;
    case Token.echo:
      echo(1, false);
      return;
    case Token.frank:
      frank(1);
      return;
    case Token.isosurface:
    case Token.plot3d:
    case Token.pmesh:
      isosurface(iShape);
      return;
    case Token.measurements:
    case Token.measure:
      measure();
      return;
    case Token.mo:
      mo(false);
      return;
    case Token.unitcell:
      unitcell(1);
      return;
    }
  }

  private boolean flowControl(int tok, boolean isForCheck)
      throws ScriptException {
    switch (tok) {
    case Token.gotocmd:
      String strTo = parameterAsString(checkLast(1));
      gotoCmd(strTo);
      return isForCheck;
    case Token.loop:
      // back to the beginning of this script
      delay();
      if (!isSyntaxCheck)
        pc = -1;
      return isForCheck;
    }
    int pt = statement[0].intValue;
    boolean isDone = (pt < 0 && !isSyntaxCheck);
    boolean isOK = true;
    int ptNext = 0;
    switch (tok) {
    case Token.catchcmd:
      pushContext((ContextToken) theToken);
      isOK = !isDone;
      break;
    case Token.process:
      pushContext((ContextToken) theToken);
      isDone = isOK = true;
      addProcess(pc, pt, true);
      break;
    case Token.switchcmd:
    case Token.defaultcmd:
    case Token.casecmd:
      ptNext = Math.abs(aatoken[Math.abs(pt)][0].intValue);
      //System.out.println(theToken + " pc=" + pc + " pt=" + pt + " ptNext="
      //  + ptNext);
      switch (isDone ? 0 : switchCmd((ContextToken) theToken, tok)) {
      case 0:
        // done
        ptNext = -ptNext;
        isOK = false;
        break;
      case -1:
        // skip this case
        isOK = false;
        break;
      case 1:
        // do this one
      }
      aatoken[pc][0].intValue = Math.abs(pt);
      theToken = aatoken[Math.abs(pt)][0];
      if (theToken.tok != Token.end)
        theToken.intValue = ptNext;
      break;
    case Token.ifcmd:
    case Token.elseif:
      isOK = (!isDone && ifCmd());
      if (isSyntaxCheck)
        break;
      ptNext = Math.abs(aatoken[Math.abs(pt)][0].intValue);
      ptNext = (isDone || isOK ? -ptNext : ptNext);
      aatoken[Math.abs(pt)][0].intValue = ptNext;
      if (tok == Token.catchcmd)
        aatoken[pc][0].intValue = -pt; // reset to "done" state
      break;
    case Token.elsecmd:
      checkLength(1);
      if (pt < 0 && !isSyntaxCheck)
        pc = -pt - 1;
      break;
    case Token.endifcmd:
      checkLength(1);
      break;
    case Token.whilecmd:
      if (!isForCheck)
        pushContext((ContextToken) theToken);
      isForCheck = false;
      if (!ifCmd() && !isSyntaxCheck) {
        pc = pt;
        popContext(true, false);
      }
      break;
    case Token.breakcmd:
      if (!isSyntaxCheck) {
        breakCmd(pt);
        break;
      }
      if (statementLength == 1)
        break;
      int n = intParameter(checkLast(1));
      if (isSyntaxCheck)
        break;
      for (int i = 0; i < n; i++)
        popContext(true, false);
      break;
    case Token.continuecmd:
      isForCheck = true;
      if (!isSyntaxCheck)
        pc = pt - 1;
      if (statementLength > 1)
        intParameter(checkLast(1));
      break;
    case Token.forcmd:
      // for (i = 1; i < 3; i = i + 1);
      // for (var i = 1; i < 3; i = i + 1);
      // for (;;;);
      // for (var x in {...}) { xxxxx }
      int[] pts = new int[2];
      int j = 0;
      for (int i = 1, nSkip = 0; i < statementLength && j < 2; i++) {
        switch (tokAt(i)) {
        case Token.semicolon:
          if (nSkip > 0)
            nSkip--;
          else
            pts[j++] = i;
          break;
        case Token.select:
          nSkip += 2;
          break;
        }

      }
      if (isForCheck) {
        j = pts[1] + 1;
        isForCheck = false;
      } else {
        pushContext((ContextToken) theToken);
        j = 2;
        if (tokAt(j) == Token.var)
          j++;
      }
      String key = parameterAsString(j);
      boolean isMinusMinus = key.equals("--") || key.equals("++");
      if (isMinusMinus) {
        key = parameterAsString(++j);
      }
      if (Token.tokAttr(tokAt(j), Token.misc)
          || getContextVariableAsVariable(key) != null) {
        if (!isMinusMinus && getToken(++j).tok != Token.opEQ)
          error(ERROR_invalidArgument);
        if (isMinusMinus)
          j -= 2;
        setVariable(++j, statementLength - 1, key, 0);
      }
      isOK = parameterExpressionBoolean(pts[0] + 1, pts[1]);
      pt++;
      if (!isOK)
        popContext(true, false);
      break;
    case Token.end: // function, if, for, while, catch, switch
      switch (getToken(checkLast(1)).tok) {
      case Token.trycmd:
        ScriptFunction trycmd = (ScriptFunction) getToken(1).value;
        if (isSyntaxCheck)
          return false;
        Hashtable cv = (Hashtable) runFunction(trycmd, "try", null, null, true).value;
        ScriptVariable ret = (ScriptVariable) cv.get("_tryret");
        if (ret.value != null || ret.intValue != Integer.MAX_VALUE) {
          returnCmd(ret);
          return false;
        }
        String errMsg = (String) ((ScriptVariable) cv.get("_errorval")).value;
        if (errMsg.length() == 0) {
          int iBreak = ((ScriptVariable) cv.get("_breakval")).intValue;
          if (iBreak != Integer.MAX_VALUE) {
            breakCmd(pc - iBreak);
            return false;
          }
        }
        // normal return will skip the catch
        if (pc + 1 < aatoken.length && aatoken[pc + 1][0].tok == Token.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.contextVariables.put(ct.name0, ScriptVariable
                .getVariable(errMsg));
          ct.intValue = (errMsg.length() > 0 ? 1 : -1) * Math.abs(ct.intValue);
        }
        return false;
      case Token.catchcmd:
        popContext(true, false);
        break;
      case Token.function:
      case Token.parallel:
        viewer.addFunction((ScriptFunction) theToken.value);
        return isForCheck;
      case Token.process:
        addProcess(pt, pc, false);
        popContext(true, false);
        break;
      case Token.switchcmd:
        if (pt > 0 && switchCmd((ContextToken) aatoken[pt][0], 0) == -1) {
          // check for the default position
          for (; pt < pc; pt++)
            if ((tok = aatoken[pt][0].tok) != Token.defaultcmd
                && tok != Token.casecmd)
              break;
          isOK = (pc == pt);
        }
        break;
      }
      if (isOK)
        isOK = (theTok == Token.catchcmd || theTok == Token.process
            || theTok == Token.ifcmd || theTok == Token.switchcmd);
      isForCheck = (theTok == Token.forcmd || theTok == Token.whilecmd);
      break;
    }
    if (!isOK && !isSyntaxCheck)
      pc = Math.abs(pt) - 1;
    return isForCheck;
  }
 
  private void gotoCmd(String strTo) throws ScriptException {
    int pcTo = (strTo == null ? aatoken.length - 1 : -1);
    String s = null;
    for (int i = pcTo + 1; i < aatoken.length; i++) {
      Token[] tokens = aatoken[i];
      int tok = tokens[0].tok;
      switch (tok) {
      case Token.message:
      case Token.nada:
        s = (String) tokens[tokens.length - 1].value;
        if (tok == Token.nada)
          s = s.substring(s.startsWith("#") ? 1 : 2);
        break;
      default:
        continue;
      }
      if (s.equalsIgnoreCase(strTo)) {
        pcTo = i;
        break;
      }
    }
    if (pcTo < 0)
      error(ERROR_invalidArgument);
    if (strTo == null)
      pcTo = 0;
    int di = (pcTo < pc ? 1 : -1);
    int nPush = 0;
    for (int i = pcTo; i != pc; i += di) {
      switch (aatoken[i][0].tok) {
      case Token.push:
      case Token.process:
      case Token.forcmd:
      case Token.catchcmd:
      case Token.whilecmd:
        nPush++;
        break;
      case Token.pop:
        nPush--;
        break;
      case Token.end:
        switch (aatoken[i][1].tok) {
        case Token.process:
        case Token.forcmd:
        case Token.catchcmd:
        case Token.whilecmd:
          nPush--;
        }
        break;
      }
    }
    if (strTo == null) {
      pcTo = Integer.MAX_VALUE;
      for (; nPush > 0; --nPush)
        popContext(false, false);
    }
    if (nPush != 0)
      error(ERROR_invalidArgument);
    if (!isSyntaxCheck)
      pc = pcTo - 1; // ... resetting the program counter
  }

  private void breakCmd(int pt) {
    // pt is a backward reference
    if (pt < 0) {
      // this is a break within a try{...} block
      getContextVariableAsVariable("_breakval").intValue = -pt;
      pcEnd = pc;
      return;
    }
    pc = Math.abs(aatoken[pt][0].intValue);
    int tok = aatoken[pt][0].tok;
    if (tok == Token.casecmd || tok == Token.defaultcmd) {
      theToken = aatoken[pc--][0];
      int ptNext = Math.abs(theToken.intValue);
      if (theToken.tok != Token.end)
        theToken.intValue = -ptNext;
    } else {
      while (thisContext != null && !ScriptCompiler.isBreakableContext(thisContext.token.tok))
        popContext(true, false);
      popContext(true, false);
    }
  }

  private List vProcess;
  static int iProcess;

  private void addProcess(int pc, int pt, boolean isStart) {
    if (parallelProcessor == null)
      return;
    if (isStart) {
      vProcess = new ArrayList();
    } else {

      Token[][] statements = new Token[pt][];
      for (int i = 0; i < vProcess.size(); i++)
        statements[i + 1 - pc] = (Token[]) vProcess.get(i);
      ScriptContext context = getScriptContext();
      context.aatoken = statements;
      context.pc = 1 - pc;
      context.pcEnd = pt;
      parallelProcessor.addProcess("p" + (++iProcess), context);
      vProcess = null;
    }
  }

  private int switchCmd(ContextToken c, int tok)
      throws ScriptException {
    if (tok == Token.switchcmd)
      c.addName("_var");
    ScriptVariable var = (ScriptVariable) c.contextVariables.get("_var");
    if (var == null)
      return 1; // OK, case found -- no more testing
    if (tok == 0) {
      // end: remove variable and do default
      //      this causes all other cases to
      //      skip
      c.contextVariables.remove("_var");
      return -1;
    }
    if (tok == Token.defaultcmd) // never do the default one directly
      return -1;
    ScriptVariable v = parameterExpressionToken(1);
    if (tok == Token.casecmd) {
      boolean isOK = ScriptVariable.areEqual(var, v);
      if (isOK)
        c.contextVariables.remove("_var");
      return isOK ? 1 : -1;
    }
    c.contextVariables.put("_var", v);
    return 1;
  }

  private boolean ifCmd() throws ScriptException {
    return parameterExpressionBoolean(1, 0);
  }

  private void returnCmd(ScriptVariable tv) throws ScriptException {
    ScriptVariable t = getContextVariableAsVariable("_retval");
    if (t == null) {
      if (!isSyntaxCheck)
        gotoCmd(null);
      return;
    }
    ScriptVariable v = (tv != null || statementLength == 1 ? null
        : parameterExpressionToken(1));
    if (isSyntaxCheck)
      return;
    if (tv == null)
      tv = (v == null ? ScriptVariable.intVariable(0) : v);
    t.value = tv.value;
    t.intValue = tv.intValue;
    t.tok = tv.tok;
    gotoCmd(null);
  }

  private void help() throws ScriptException {
    if (isSyntaxCheck)
      return;
    String what = optParameterAsString(1).toLowerCase();
    int pt = 0;
    if (what.startsWith("mouse") && (pt = what.indexOf(" ")) >= 0
        && pt == what.lastIndexOf(" ")) {
      showString(viewer.getBindingInfo(what.substring(pt + 1)));
      return;
    }
    if (Token.tokAttr(Token.getTokFromName(what), Token.scriptCommand))
      what = "?command=" + what;
    viewer.getHelp(what);
  }

  private void move() throws ScriptException {
    if (statementLength > 11)
      error(ERROR_badArgumentCount);
    // rotx roty rotz zoom transx transy transz slab seconds fps
    Vector3f dRot = new Vector3f(floatParameter(1), floatParameter(2),
        floatParameter(3));
    float dZoom = floatParameter(4);
    Vector3f dTrans = new Vector3f(intParameter(5), intParameter(6),
        intParameter(7));
    float dSlab = floatParameter(8);
    float floatSecondsTotal = floatParameter(9);
    int fps = (statementLength == 11 ? intParameter(10) : 30);
    if (isSyntaxCheck)
      return;
    refresh();
    viewer.move(dRot, dZoom, dTrans, dSlab, floatSecondsTotal, fps);
  }

  private void moveto() throws ScriptException {
    // moveto time
    // moveto [time] { x y z deg} zoom xTrans yTrans (rotCenter) rotationRadius
    // (navCenter) xNav yNav navDepth
    // moveto [time] { x y z deg} 0 xTrans yTrans (rotCenter) [zoom factor]
    // (navCenter) xNav yNav navDepth
    // moveto [time] { x y z deg} (rotCenter) [zoom factor] (navCenter) xNav
    // yNav navDepth
    // where [zoom factor] is [0|n|+n|-n|*n|/n|IN|OUT]
    // moveto [time] front|back|left|right|top|bottom
    if (statementLength == 2 && tokAt(1) == Token.stop) {
      if (!isSyntaxCheck)
        viewer.stopMotion();
      return;
    }
    if (statementLength == 2 && isFloatParameter(1)) {
      float f = floatParameter(1);
      if (isSyntaxCheck)
        return;
      if (f > 0)
        refresh();
      viewer.moveTo(f, null, JmolConstants.axisZ, 0, null, 100, 0, 0, 0, null,
          Float.NaN, Float.NaN, Float.NaN);
      return;
    }
    Vector3f axis = new Vector3f(Float.NaN, 0, 0);
    Point3f center = null;
    int i = 1;
    float floatSecondsTotal = (isFloatParameter(i) ? floatParameter(i++) : 2.0f);
    float degrees = 90;
    BitSet bsCenter = null;
    switch (getToken(i).tok) {
    case Token.quaternion:
      Quaternion q;
      boolean isMolecular = false;
      if (tokAt(++i) == Token.molecular) {
        // see comment below
        isMolecular = true;
        i++;
      }
      if (tokAt(i) == Token.bitset || tokAt(i) == Token.expressionBegin) {
        isMolecular = true;
        center = centerParameter(i);
        if (!(expressionResult instanceof BitSet))
          error(ERROR_invalidArgument);
        bsCenter = (BitSet) expressionResult;
        q = (isSyntaxCheck ? new Quaternion() : viewer
            .getAtomQuaternion(bsCenter.nextSetBit(0)));
      } else {
        q = getQuaternionParameter(i);
      }
      i = iToken + 1;
      if (q == null)
        error(ERROR_invalidArgument);
      AxisAngle4f aa = q.toAxisAngle4f();
      axis.set(aa.x, aa.y, aa.z);
      /*
       * The quaternion angle for an atom represents the angle by which the
       * reference frame must be rotated to match the frame defined for the
       * residue.
       *
       * However, to "moveTo" this frame as the REFERENCE frame, what we have to
       * do is take that quaternion frame and rotate it BACKWARD by that many
       * degrees. Then it will match the reference frame, which is ultimately
       * our window frame.
       *
       * We only apply this for molecular-type quaternions, because in general
       * the orientation quaternion refers to how the reference plane has been
       * changed (the orientation matrix)
       */
      degrees = (isMolecular ? -1 : 1) * (float) (aa.angle * 180.0 / Math.PI);
      break;
    case Token.point4f:
    case Token.point3f:
    case Token.leftbrace:
      // {X, Y, Z} deg or {x y z deg}
      if (isPoint3f(i)) {
        axis.set(getPoint3f(i, true));
        i = iToken + 1;
        degrees = floatParameter(i++);
      } else {
        Point4f pt4 = getPoint4f(i);
        i = iToken + 1;
        axis.set(pt4.x, pt4.y, pt4.z);
        degrees = (pt4.x == 0 && pt4.y == 0 && pt4.z == 0 ? Float.NaN : pt4.w);
      }
      break;
    case Token.front:
      axis.set(1, 0, 0);
      degrees = 0f;
      checkLength(++i);
      break;
    case Token.back:
      axis.set(0, 1, 0);
      degrees = 180f;
      checkLength(++i);
      break;
    case Token.left:
      axis.set(0, 1, 0);
      checkLength(++i);
      break;
    case Token.right:
      axis.set(0, -1, 0);
      checkLength(++i);
      break;
    case Token.top:
      axis.set(1, 0, 0);
      checkLength(++i);
      break;
    case Token.bottom:
      axis.set(-1, 0, 0);
      checkLength(++i);
      break;
    default:
      // X Y Z deg
      axis = new Vector3f(floatParameter(i++), floatParameter(i++),
          floatParameter(i++));
      degrees = floatParameter(i++);
    }
    if (Float.isNaN(axis.x) || Float.isNaN(axis.y) || Float.isNaN(axis.z))
      axis.set(0, 0, 0);
    else if (axis.length() == 0 && degrees == 0)
      degrees = Float.NaN;
    boolean isChange = !viewer.isInPosition(axis, degrees);
    // optional zoom
    float zoom = (isFloatParameter(i) ? floatParameter(i++) : Float.NaN);
    // optional xTrans yTrans
    float xTrans = 0;
    float yTrans = 0;
    if (isFloatParameter(i) && !isCenterParameter(i)) {
      xTrans = floatParameter(i++);
      yTrans = floatParameter(i++);
      if (!isChange && Math.abs(xTrans - viewer.getTranslationXPercent()) >= 1)
        isChange = true;
      if (!isChange && Math.abs(yTrans - viewer.getTranslationYPercent()) >= 1)
        isChange = true;
    }
    if (bsCenter == null && i != statementLength) {
      // if any more, required (center)
      center = centerParameter(i);
      if (expressionResult instanceof BitSet)
        bsCenter = (BitSet) expressionResult;
      i = iToken + 1;
    }
    float rotationRadius = Float.NaN;
    float zoom0 = viewer.getZoomSetting();
    if (center != null) {
      if (!isChange && center.distance(viewer.getRotationCenter()) >= 0.1)
        isChange = true;
      // optional {center} rotationRadius
      if (isFloatParameter(i))
        rotationRadius = floatParameter(i++);
      if (!isCenterParameter(i)) {
        if ((rotationRadius == 0 || Float.isNaN(rotationRadius))
            && (zoom == 0 || Float.isNaN(zoom))) {
          // alternative (atom expression) 0 zoomFactor
          float newZoom = Math
              .abs(getZoom(i, bsCenter, (zoom == 0 ? 0 : zoom0)));
          i = iToken + 1;
          zoom = newZoom;
        } else {
          if (!isChange
              && Math.abs(rotationRadius - viewer.getRotationRadius()) >= 0.1)
            isChange = true;
        }
      }
    }
    if (zoom == 0 || Float.isNaN(zoom))
      zoom = 100;
    if (Float.isNaN(rotationRadius))
      rotationRadius = 0;

    if (!isChange && Math.abs(zoom - zoom0) >= 1)
      isChange = true;
    // (navCenter) xNav yNav navDepth

    Point3f navCenter = null;
    float xNav = Float.NaN;
    float yNav = Float.NaN;
    float navDepth = Float.NaN;

    if (i != statementLength) {
      navCenter = centerParameter(i);
      i = iToken + 1;
      if (i != statementLength) {
        xNav = floatParameter(i++);
        yNav = floatParameter(i++);
      }
      if (i != statementLength)
        navDepth = floatParameter(i++);
    }

    if (i != statementLength)
      error(ERROR_badArgumentCount);

    if (isSyntaxCheck)
      return;
    if (!isChange)
      floatSecondsTotal = 0;
    if (floatSecondsTotal > 0)
      refresh();
    viewer.moveTo(floatSecondsTotal, center, axis, degrees, null, zoom, xTrans,
        yTrans, rotationRadius, navCenter, xNav, yNav, navDepth);
  }

  private void navigate() throws ScriptException {
    /*
     * navigation on/off navigation depth p # would be as a depth value, like
     * slab, in percent, but could be negative navigation nSec translate X Y #
     * could be percentages navigation nSec translate $object # could be a draw
     * object navigation nSec translate (atom selection) #average of values
     * navigation nSec center {x y z} navigation nSec center $object navigation
     * nSec center (atom selection) navigation nSec path $object navigation nSec
     * path {x y z theta} {x y z theta}{x y z theta}{x y z theta}... navigation
     * nSec trace (atom selection)
     */
    if (statementLength == 1) {
      setBooleanProperty("navigationMode", true);
      return;
    }
    Vector3f rotAxis = new Vector3f(0, 1, 0);
    Point3f pt;
    if (statementLength == 2) {
      switch (getToken(1).tok) {
      case Token.on:
      case Token.off:
        if (isSyntaxCheck)
          return;
        setObjectMad(JmolConstants.SHAPE_AXES, "axes", 1);
        setShapeProperty(JmolConstants.SHAPE_AXES, "position", new Point3f(50,
            50, Float.MAX_VALUE));
        setBooleanProperty("navigationMode", true);
        viewer.setNavOn(theTok == Token.on);
        return;
      case Token.stop:
        if (!isSyntaxCheck)
          viewer.setNavXYZ(0, 0, 0);
        return;
      case Token.point3f:
        break;
      default:
        error(ERROR_invalidArgument);
      }
    }
    if (!viewer.getNavigationMode())
      setBooleanProperty("navigationMode", true);
    for (int i = 1; i < statementLength; i++) {
      float timeSec = (isFloatParameter(i) ? floatParameter(i++) : 2f);
      if (timeSec < 0)
        error(ERROR_invalidArgument);
      if (!isSyntaxCheck && timeSec > 0)
        refresh();
      switch (getToken(i).tok) {
      case Token.point3f:
      case Token.leftbrace:
        // navigate {x y z}
        pt = getPoint3f(i, true);
        iToken++;
        if (iToken != statementLength)
          error(ERROR_invalidArgument);
        if (isSyntaxCheck)
          return;
        viewer.setNavXYZ(pt.x, pt.y, pt.z);
        return;
      case Token.depth:
        float depth = floatParameter(++i);
        if (!isSyntaxCheck)
          viewer.setNavigationDepthPercent(timeSec, depth);
        continue;
      case Token.center:
        pt = centerParameter(++i);
        i = iToken;
        if (!isSyntaxCheck)
          viewer.navigate(timeSec, pt);
        continue;
      case Token.rotate:
        switch (getToken(++i).tok) {
        case Token.x:
          rotAxis.set(1, 0, 0);
          i++;
          break;
        case Token.y:
          rotAxis.set(0, 1, 0);
          i++;
          break;
        case Token.z:
          rotAxis.set(0, 0, 1);
          i++;
          break;
        case Token.identifier:
          error(ERROR_invalidArgument); // for now
          break;
        case Token.point3f:
        case Token.leftbrace:
          rotAxis.set(getPoint3f(i, true));
          i = iToken + 1;
          break;
        }
        float degrees = floatParameter(i);
        if (!isSyntaxCheck)
          viewer.navigate(timeSec, rotAxis, degrees);
        continue;
      case Token.translate:
        float x = Float.NaN;
        float y = Float.NaN;
        if (isFloatParameter(++i)) {
          x = floatParameter(i);
          y = floatParameter(++i);
        } else {
          switch (tokAt(i)) {
          case Token.x:
            x = floatParameter(++i);
            break;
          case Token.y:
            y = floatParameter(++i);
            break;
          default:
            pt = centerParameter(i);
            i = iToken;
            if (!isSyntaxCheck)
              viewer.navTranslate(timeSec, pt);
            continue;
          }
        }
        if (!isSyntaxCheck)
          viewer.navTranslatePercent(timeSec, x, y);
        continue;
      case Token.divide:
        continue;
      case Token.trace:
        Point3f[][] pathGuide;
        List vp = new ArrayList();
        BitSet bs = atomExpression(++i);
        i = iToken;
        if (isSyntaxCheck)
          return;
        viewer.getPolymerPointsAndVectors(bs, vp);
        int n;
        if ((n = vp.size()) > 0) {
          pathGuide = new Point3f[n][];
          for (int j = 0; j < n; j++) {
            pathGuide[j] = (Point3f[]) vp.get(j);
          }
          viewer.navigate(timeSec, pathGuide);
          continue;
        }
        break;
      case Token.surface:
        if (i != 1)
          error(ERROR_invalidArgument);
        if (isSyntaxCheck)
          return;
        viewer.navigateSurface(timeSec, optParameterAsString(2));
        continue;
      case Token.path:
        Point3f[] path;
        float[] theta = null; // orientation; null for now
        if (getToken(i + 1).tok == Token.dollarsign) {
          i++;
          // navigate timeSeconds path $id indexStart indexEnd
          String pathID = objectNameParameter(++i);
          if (isSyntaxCheck)
            return;
          setShapeProperty(JmolConstants.SHAPE_DRAW, "thisID", pathID);
          path = (Point3f[]) getShapeProperty(JmolConstants.SHAPE_DRAW,
              "vertices");
          refresh();
          if (path == null)
            error(ERROR_invalidArgument);
          int indexStart = (int) (isFloatParameter(i + 1) ? floatParameter(++i)
              : 0);
          int indexEnd = (int) (isFloatParameter(i + 1) ? floatParameter(++i)
              : Integer.MAX_VALUE);
          if (!isSyntaxCheck)
            viewer.navigate(timeSec, path, theta, indexStart, indexEnd);
          continue;
        }
        List v = new ArrayList();
        while (isCenterParameter(i + 1)) {
          v.add(centerParameter(++i));
          i = iToken;
        }
        if (v.size() > 0) {
          path = new Point3f[v.size()];
          for (int j = 0; j < v.size(); j++) {
            path[j] = (Point3f) v.get(j);
          }
          if (!isSyntaxCheck)
            viewer.navigate(timeSec, path, theta, 0, Integer.MAX_VALUE);
          continue;
        }
        // fall through
      default:
        error(ERROR_invalidArgument);
      }
    }
  }

  private void bondorder() throws ScriptException {
    checkLength(-3);
    int order = 0;
    switch (getToken(1).tok) {
    case Token.integer:
    case Token.decimal:
      if ((order = JmolConstants.getBondOrderFromFloat(floatParameter(1))) == JmolEdge.BOND_ORDER_NULL)
        error(ERROR_invalidArgument);
      break;
    default:
      if ((order = JmolConstants.getBondOrderFromString(parameterAsString(1))) == JmolEdge.BOND_ORDER_NULL)
        error(ERROR_invalidArgument);
      // generic partial can be indicated by "partial n.m"
      if (order == JmolEdge.BOND_PARTIAL01 && tokAt(2) == Token.decimal) {
        order = JmolConstants
            .getPartialBondOrderFromInteger(statement[2].intValue);
      }
    }
    setShapeProperty(JmolConstants.SHAPE_STICKS, "bondOrder",
        new Integer(order));
  }

  private void console() throws ScriptException {
    switch (getToken(1).tok) {
    case Token.off:
      if (!isSyntaxCheck)
        viewer.showConsole(false);
      break;
    case Token.on:
      if (isSyntaxCheck)
        break;
      viewer.showConsole(true);
      viewer.clearConsole();
      break;
    default:
      error(ERROR_invalidArgument);
    }
  }

  private void centerAt() throws ScriptException {
    String relativeTo = null;
    switch (getToken(1).tok) {
    case Token.absolute:
      relativeTo = "absolute";
      break;
    case Token.average:
      relativeTo = "average";
      break;
    case Token.boundbox:
      relativeTo = "boundbox";
      break;
    default:
      error(ERROR_invalidArgument);
    }
    Point3f pt = new Point3f(0, 0, 0);
    if (statementLength == 5) {
      // centerAt xxx x y z
      pt.x = floatParameter(2);
      pt.y = floatParameter(3);
      pt.z = floatParameter(4);
    } else if (isCenterParameter(2)) {
      pt = centerParameter(2);
      checkLast(iToken);
    } else {
      checkLength(2);
    }
    if (!isSyntaxCheck)
      viewer.setCenterAt(relativeTo, pt);
  }

  private void stereo() throws ScriptException {
    int stereoMode = JmolConstants.STEREO_DOUBLE;
    // see www.usm.maine.edu/~rhodes/0Help/StereoViewing.html
    // stereo on/off
    // stereo color1 color2 6
    // stereo redgreen 5

    float degrees = JmolConstants.DEFAULT_STEREO_DEGREES;
    boolean degreesSeen = false;
    int[] colors = null;
    int colorpt = 0;
    for (int i = 1; i < statementLength; ++i) {
      if (isColorParam(i)) {
        if (colorpt > 1)
          error(ERROR_badArgumentCount);
        if (colorpt == 0)
          colors = new int[2];
        if (!degreesSeen)
          degrees = 3;
        colors[colorpt] = getArgbParam(i);
        if (colorpt++ == 0)
          colors[1] = ~colors[0];
        i = iToken;
        continue;
      }
      switch (getToken(i).tok) {
      case Token.on:
        checkLast(iToken = 1);
        iToken = 1;
        break;
      case Token.off:
        checkLast(iToken = 1);
        stereoMode = JmolConstants.STEREO_NONE;
        break;
      case Token.integer:
      case Token.decimal:
        degrees = floatParameter(i);
        degreesSeen = true;
        break;
      case Token.identifier:
        if (!degreesSeen)
          degrees = 3;
        stereoMode = JmolConstants.getStereoMode(parameterAsString(i));
        if (stereoMode != JmolConstants.STEREO_UNKNOWN)
          break;
        // fall into
      default:
        error(ERROR_invalidArgument);
      }
    }
    if (isSyntaxCheck)
      return;
    viewer.setStereoMode(colors, stereoMode, degrees);
  }

  private void compare() throws ScriptException {
    // compare {model1} {model2} [atoms] {bsAtoms1} {bsAtoms2}
    // compare {model1} {model2} orientations
    // compare {model1} {model2} orientations {bsAtoms1} {bsAtoms2}
    // compare {model1} {model2} [orientations] [quaternionList1] [quaternionList2]
    // compare {model1} {model2} SMILES "....."
    // compare {model1} {model2} SMARTS "....."

    boolean isQuaternion = false;
    boolean doRotate = false;
    boolean doTranslate = false;
    boolean doAnimate = false;
    float nSeconds = Float.NaN;
    Quaternion[] data1 = null, data2 = null;
    BitSet bsAtoms1 = null, bsAtoms2 = null;
    List vAtomSets = null;
    List vQuatSets = null;
    BitSet bsFrom = atomExpression(1);
    BitSet bsTo = atomExpression(++iToken);
    BitSet bsSubset = null;
    boolean isSmiles = false;
    String strSmiles = null;
    for (int i = iToken + 1; i < statementLength; ++i) {
      switch (getToken(i).tok) {
      case Token.smiles:
        isSmiles = true;
        // fall through
      case Token.search:
        strSmiles = stringParameter(++i);
        break;
      case Token.decimal:
      case Token.integer:
        nSeconds = Math.abs(floatParameter(i));
        if (nSeconds > 0)
          doAnimate = true;
        break;
      case Token.comma:
        break;
      case Token.subset:
        bsSubset = atomExpression(++i);
        i = iToken;
        break;
      case Token.bitset:
      case Token.expressionBegin:
        if (vQuatSets != null)
          error(ERROR_invalidArgument);
        bsAtoms1 = atomExpression(iToken);
        int tok = tokAt(iToken + 1);
        bsAtoms2 = (tok == Token.bitset || tok == Token.expressionBegin ? atomExpression(++iToken)
            : BitSetUtil.copy(bsAtoms1));
        bsAtoms1.and(bsFrom);
        bsAtoms2.and(bsTo);
        if (bsSubset != null) {
          bsAtoms1.and(bsSubset);
          bsAtoms2.and(bsSubset);
        }
        if (vAtomSets == null)
          vAtomSets = new ArrayList();
        vAtomSets.add(new BitSet[] { bsAtoms1, bsAtoms2 });
        i = iToken;
        break;
      case Token.varray:
        if (vAtomSets != null)
          error(ERROR_invalidArgument);
        isQuaternion = true;
        data1 = ScriptMathProcessor
            .getQuaternionArray(((ScriptVariable) theToken).getList());
        getToken(++i);
        data2 = ScriptMathProcessor
            .getQuaternionArray(((ScriptVariable) theToken).getList());
        if (vQuatSets == null)
          vQuatSets = new ArrayList();
        vQuatSets.add(new Object[] { data1, data2 });
        break;
      case Token.orientation:
        isQuaternion = true;
        break;
      case Token.point:

      case Token.atoms:
        isQuaternion = false;
        break;
      case Token.rotate:
        doRotate = true;
        break;
      case Token.translate:
        doTranslate = true;
        break;
      default:
        error(ERROR_invalidArgument);
      }
    }
    if (isSyntaxCheck)
      return;
    float[] retStddev = new float[2]; // [0] final, [1] initial for atoms
    Quaternion q = null;
    List vQ = new ArrayList();
    Point3f[][] centerAndPoints = null;
    if (isQuaternion) {
      if (vAtomSets == null && vQuatSets == null) {
        vAtomSets = new ArrayList();
        vAtomSets.add(new BitSet[] { bsFrom, bsTo });
      }
      if (vQuatSets == null) {
        for (int i = 0; i < vAtomSets.size(); i++) {
          BitSet[] bss = (BitSet[]) vAtomSets.get(i);
          data1 = viewer.getAtomGroupQuaternions(bss[0], Integer.MAX_VALUE);
          data2 = viewer.getAtomGroupQuaternions(bss[1], Integer.MAX_VALUE);
          for (int j = 0; j < data1.length && j < data2.length; j++)
            vQ.add(data2[j].div(data1[j]));
        }
      } else {
        for (int j = 0; j < data1.length && j < data2.length; j++)
          vQ.add(data2[j].div(data1[j]));
      }
      retStddev[0] = 0;
      data1 = new Quaternion[vQ.size()];
      for (int i = vQ.size(); --i >= 0;)
        data1[i] = (Quaternion) vQ.get(i);
      q = Quaternion.sphereMean(data1, retStddev, 0.0001f);
      showString("RMSD = " + retStddev[0] + " degrees");
    } else if (strSmiles != null) {
      if (vAtomSets == null)
        vAtomSets = new ArrayList();
      bsAtoms1 = BitSetUtil.copy(bsFrom);
      bsAtoms2 = BitSetUtil.copy(bsTo);
      vAtomSets.add(new BitSet[] { bsAtoms1, bsAtoms2 });
      Matrix4f m4 = new Matrix4f();
      float stddev = getSmilesCorrelation(bsFrom, bsTo, strSmiles, null, null,
          m4, null, !isSmiles);
      if (Float.isNaN(stddev))
        error(ERROR_invalidArgument);
      Vector3f translation = new Vector3f();
      m4.get(translation);
      Matrix3f m3 = new Matrix3f();
      m4.get(m3);
      q = new Quaternion(m3);
    } else {
      // atoms
      if (bsAtoms1 == null) {
        bsAtoms1 = viewer.getAtomBitSet("spine");
        if (bsAtoms1.nextSetBit(0) < 0) {
          bsAtoms1 = bsFrom;
          bsAtoms2 = bsTo;
        } else {
          bsAtoms2 = BitSetUtil.copy(bsAtoms1);
          bsAtoms1.and(bsFrom);
          bsAtoms2.and(bsTo);
        }
        vAtomSets = new ArrayList();
        vAtomSets.add(new BitSet[] { bsAtoms1, bsAtoms2 });
      }
      centerAndPoints = viewer.getCenterAndPoints(vAtomSets, true);
      q = Measure.calculateQuaternionRotation(centerAndPoints, retStddev, true);
      float r0 = (Float.isNaN(retStddev[1]) ? Float.NaN
          : (int) (retStddev[0] * 100) / 100f);
      float r1 = (Float.isNaN(retStddev[1]) ? Float.NaN
          : (int) (retStddev[1] * 100) / 100f);
      showString("RMSD " + r0 + " --> " + r1 + " Angstroms");
    }
    Point3f pt1 = new Point3f();
    if (centerAndPoints == null)
      centerAndPoints = viewer.getCenterAndPoints(vAtomSets, true);
    if (Float.isNaN(nSeconds) || nSeconds < 0) {
      nSeconds = 1;
    } else if (!doRotate && !doTranslate) {
      doAnimate = doRotate = doTranslate = true;
    }
    doAnimate = (nSeconds != 0);
    float endDegrees = Float.NaN;
    Vector3f translation = null;
    if (doTranslate) {
      translation = new Vector3f(centerAndPoints[1][0]);
      translation.sub(centerAndPoints[0][0]);
      endDegrees = 0;
    }
    if (doRotate) {
      if (q == null)
        evalError("option not implemented", null);
      pt1.set(centerAndPoints[0][0]);
      pt1.add(q.getNormal());
      endDegrees = q.getTheta();
    }
    if (Float.isNaN(endDegrees) || Float.isNaN(pt1.x))
      return;
    List ptsB = null;
    if (doRotate && doTranslate && nSeconds != 0) {
      List ptsA = viewer.getAtomPointVector(bsFrom);
      Matrix4f m4 = ScriptMathProcessor.getMatrix4f(q.getMatrix(), translation);
      ptsB = Measure.transformPoints(ptsA, m4, centerAndPoints[0][0]);
    }
    viewer.rotateAboutPointsInternal(centerAndPoints[0][0], pt1, endDegrees
        / nSeconds, endDegrees, doAnimate, bsFrom, translation, ptsB);
  }

  float getSmilesCorrelation(BitSet bsA, BitSet bsB, String smiles,
                             List ptsA, List ptsB, Matrix4f m,
                             List vReturn, boolean isSmarts)
      throws ScriptException {
    float tolerance = 0.1f; // TODO
    try {
      if (ptsA == null) {
        ptsA = new ArrayList();
        ptsB = new ArrayList();
      }
      if (m == null)
        m = new Matrix4f();

      Atom[] atoms = viewer.getModelSet().atoms;
      int atomCount = viewer.getAtomCount();
      int[][] maps = viewer.getSmilesMatcher().getCorrelationMaps(smiles,
          atoms, atomCount, bsA, isSmarts, true);
      if (maps == null)
        evalError(viewer.getSmilesMatcher().getLastException(), null);
      if (maps.length == 0)
        return Float.NaN;
      for (int i = 0; i < maps[0].length; i++)
        ptsA.add(atoms[maps[0][i]]);
      maps = viewer.getSmilesMatcher().getCorrelationMaps(smiles, atoms,
          atomCount, bsB, isSmarts, false);
      if (maps == null)
        evalError(viewer.getSmilesMatcher().getLastException(), null);
      if (maps.length == 0)
        return Float.NaN;
      float lowestStdDev = Float.MAX_VALUE;
      int[] mapB = null;
      for (int i = 0; i < maps.length; i++) {
        ptsB.clear();
        for (int j = 0; j < maps[i].length; j++)
          ptsB.add(atoms[maps[i][j]]);
        float stddev = Measure.getTransformMatrix4(ptsA, ptsB, m, null);
        Logger.info("getSmilesCorrelation stddev=" + stddev);
        if (vReturn != null) {
          if (stddev < tolerance) {
            BitSet bs = new BitSet();
            for (int j = 0; j < maps[i].length; j++)
              bs.set(maps[i][j]);
            vReturn.add(bs);
          }
        }
        if (stddev < lowestStdDev) {
          mapB = maps[i];
          lowestStdDev = stddev;
        }
      }
      for (int i = 0; i < mapB.length; i++)
        ptsB.add(atoms[mapB[i]]);
      return lowestStdDev;
    } catch (Exception e) {
      // e.printStackTrace();
      evalError(e.getMessage(), null);
      return 0; // unattainable
    }
  }

  Object getSmilesMatches(String pattern, String smiles, BitSet bsSelected,
                          BitSet bsMatch3D, boolean isSmarts,
                          boolean asOneBitset) throws ScriptException {
    if (isSyntaxCheck) {
      if (asOneBitset)
        return new BitSet();
      return new String[] { "({})" };
    }

    // just retrieving the SMILES or bioSMILES string

    if (pattern.length() == 0) {
      boolean isBioSmiles = (!asOneBitset);
      Object ret = viewer.getSmiles(0, 0, bsSelected, isBioSmiles, false, true,
          true);
      if (ret == null)
        evalError(viewer.getSmilesMatcher().getLastException(), null);
      return ret;
    }

    boolean asAtoms = true;
    BitSet[] b;
    if (bsMatch3D == null) {

      // getting a BitSet or BitSet[] from a set of atoms or a pattern.

      asAtoms = (smiles == null);
      if (asAtoms)
        b = viewer.getSmilesMatcher().getSubstructureSetArray(pattern,
            viewer.getModelSet().atoms, viewer.getAtomCount(), bsSelected,
            null, isSmarts, false);
      else
        b = viewer.getSmilesMatcher().find(pattern, smiles, isSmarts, false);

      if (b == null) {
        showString(viewer.getSmilesMatcher().getLastException(), false);
        if (!asAtoms && !isSmarts)
          return Integer.valueOf(-1);
        return "?";
      }
    } else {

      // getting a correlation

      List vReturn = new ArrayList();
      float stddev = getSmilesCorrelation(bsMatch3D, bsSelected, pattern, null,
          null, null, vReturn, isSmarts);
      if (Float.isNaN(stddev)) {
        if (asOneBitset)
          return new BitSet();
        return new String[] {};
      }
      showString("RMSD " + stddev + " Angstroms");
      b = new BitSet[vReturn.size()];
      for (int j = 0; j < b.length; j++)
        b[j] = (BitSet) vReturn.get(j);
    }
    if (asOneBitset) {
      // sum total of all now, not just first
      BitSet bs = new BitSet();
      for (int j = 0; j < b.length; j++)
        bs.or(b[j]);
      if (asAtoms)
        return bs;
      if (!isSmarts)
        return Integer.valueOf(bs.cardinality());
      int[] iarray = new int[bs.cardinality()];
      int pt = 0;
      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
        iarray[pt++] = i + 1;
      return iarray;
    }
    String[] matches = new String[b.length];
    for (int j = 0; j < b.length; j++)
      matches[j] = Escape.escape(b[j], asAtoms);
    return matches;
  }

  private void connect(int index) throws ScriptException {

    final float[] distances = new float[2];
    BitSet[] atomSets = new BitSet[2];
    atomSets[0] = atomSets[1] = viewer.getSelectionSet(false);
    float radius = Float.NaN;
    int color = Integer.MIN_VALUE;
    int distanceCount = 0;
    int bondOrder = JmolEdge.BOND_ORDER_NULL;
    int bo;
    int operation = JmolConstants.CONNECT_MODIFY_OR_CREATE;
    boolean isDelete = false;
    boolean haveType = false;
    boolean haveOperation = false;
    String translucency = null;
    float translucentLevel = Float.MAX_VALUE;
    boolean isColorOrRadius = false;
    int nAtomSets = 0;
    int nDistances = 0;
    BitSet bsBonds = new BitSet();
    boolean isBonds = false;
    int expression2 = 0;
    int ptColor = 0;
    float energy = 0;
    /*
     * connect [<=2 distance parameters] [<=2 atom sets] [<=1 bond type] [<=1
     * operation]
     */

    if (statementLength == 1) {
      viewer.rebond();
      return;
    }

    for (int i = index; i < statementLength; ++i) {
      switch (getToken(i).tok) {
      case Token.on:
      case Token.off:
        checkLength(2);
        if (!isSyntaxCheck)
          viewer.rebond();
        return;
      case Token.integer:
      case Token.decimal:
        if (nAtomSets > 0) {
          if (haveType || isColorOrRadius)
            error(ERROR_invalidParameterOrder);
          bo = JmolConstants.getBondOrderFromFloat(floatParameter(i));
          if (bo == JmolEdge.BOND_ORDER_NULL)
            error(ERROR_invalidArgument);
          bondOrder = bo;
          haveType = true;
          break;
        }
        if (++nDistances > 2)
          error(ERROR_badArgumentCount);
        float dist = floatParameter(i);
        if (tokAt(i + 1) == Token.percent) {
          dist = -dist / 100f;
          i++;
        }
        distances[distanceCount++] = dist;
        break;
      case Token.bitset:
      case Token.expressionBegin:
        if (nAtomSets > 2 || isBonds && nAtomSets > 0)
          error(ERROR_badArgumentCount);
        if (haveType || isColorOrRadius)
          error(ERROR_invalidParameterOrder);
        atomSets[nAtomSets++] = atomExpression(i);
        isBonds = isBondSet;
        if (nAtomSets == 2) {
          int pt = iToken;
          for (int j = i; j < pt; j++)
            if (tokAt(j) == Token.identifier
                && parameterAsString(j).equals("_1")) {
              expression2 = i;
              break;
            }
          iToken = pt;
        }
        i = iToken;
        break;
      case Token.color:
        int tok = tokAt(i + 1);
        if (tok != Token.translucent && tok != Token.opaque)
          ptColor = i + 1;
        continue;
      case Token.translucent:
      case Token.opaque:
        if (translucency != null)
          error(ERROR_invalidArgument);
        isColorOrRadius = true;
        translucency = parameterAsString(i);
        if (theTok == Token.translucent && isFloatParameter(i + 1))
          translucentLevel = getTranslucentLevel(++i);
        ptColor = i + 1;
        break;
      case Token.pdb:
        boolean isAuto = (tokAt(2) == Token.auto);
        checkLength(isAuto ? 3 : 2);
        if (!isSyntaxCheck)
          viewer.setPdbConectBonding(isAuto);
        return;
      case Token.adjust:
      case Token.auto:
      case Token.create:
      case Token.modify:
      case Token.modifyorcreate:
        // must be an operation and must be last argument
        haveOperation = true;
        if (++i != statementLength)
          error(ERROR_invalidParameterOrder);
        operation = theTok;
        if (theTok == Token.auto
            && !(bondOrder == JmolEdge.BOND_ORDER_NULL
                || bondOrder == JmolEdge.BOND_H_REGULAR || bondOrder == JmolEdge.BOND_AROMATIC))
          error(ERROR_invalidArgument);
        break;
      case Token.struts:
        if (!isColorOrRadius) {
          color = 0xFFFFFF;
          translucency = "translucent";
          translucentLevel = 0.5f;
          radius = viewer.getStrutDefaultRadius();
          isColorOrRadius = true;
        }
        if (!haveOperation)
          operation = JmolConstants.CONNECT_MODIFY_OR_CREATE;
        haveOperation = true;
        // fall through
      case Token.identifier:
      case Token.aromatic:
      case Token.hbond:
        if (i > 0) {
          if (ptColor == i)
            break;
          // I know -- should have required the COLOR keyword
          if (isColorParam(i)) {
            ptColor = -i;
            break;
          }
        }
        String cmd = parameterAsString(i);
        if ((bo = JmolConstants.getBondOrderFromString(cmd)) == JmolEdge.BOND_ORDER_NULL) {
          error(ERROR_invalidArgument);
        }
        // must be bond type
        if (haveType)
          error(ERROR_incompatibleArguments);
        haveType = true;
        switch (bo) {
        case JmolEdge.BOND_PARTIAL01:
          switch (tokAt(i + 1)) {
          case Token.decimal:
            bo = JmolConstants
                .getPartialBondOrderFromInteger(statement[++i].intValue);
            break;
          case Token.integer:
            bo = (short) intParameter(++i);
            break;
          }
          break;
        case JmolEdge.BOND_H_REGULAR:
          if (tokAt(i + 1) == Token.integer) {
            bo = (short) (intParameter(++i) << JmolEdge.BOND_HBOND_SHIFT);
            energy = floatParameter(++i);
          }
          break;
        }
        bondOrder = bo;
        break;
      case Token.radius:
        radius = floatParameter(++i);
        isColorOrRadius = true;
        break;
      case Token.none:
      case Token.delete:
        if (++i != statementLength)
          error(ERROR_invalidParameterOrder);
        operation = JmolConstants.CONNECT_DELETE_BONDS;
        // if (isColorOrRadius) / for struts automatic color
        // error(ERROR_invalidArgument);
        isDelete = true;
        isColorOrRadius = false;
        break;
      default:
        ptColor = i;
        break;
      }
      // now check for color -- -i means we've already checked
      if (i > 0) {
        if (ptColor == -i || ptColor == i && isColorParam(i)) {
          color = getArgbParam(i);
          i = iToken;
          isColorOrRadius = true;
        } else if (ptColor == i) {
          error(ERROR_invalidArgument);
        }
      }
    }
    if (isSyntaxCheck)
      return;
    if (distanceCount < 2) {
      if (distanceCount == 0)
        distances[0] = JmolConstants.DEFAULT_MAX_CONNECT_DISTANCE;
      distances[1] = distances[0];
      distances[0] = JmolConstants.DEFAULT_MIN_CONNECT_DISTANCE;
    }
    if (translucency != null || !Float.isNaN(radius)
        || color != Integer.MIN_VALUE) {
      if (!haveType)
        bondOrder = JmolEdge.BOND_ORDER_ANY;
      if (!haveOperation)
        operation = JmolConstants.CONNECT_MODIFY_ONLY;
    }
    int nNew = 0;
    int nModified = 0;
    int[] result;
    if (expression2 > 0) {
      BitSet bs = new BitSet();
      definedAtomSets.put("_1", bs);
      BitSet bs0 = atomSets[0];
      for (int atom1 = bs0.nextSetBit(0); atom1 >= 0; atom1 = bs0
          .nextSetBit(atom1 + 1)) {
        bs.set(atom1);
        result = viewer.makeConnections(distances[0], distances[1], bondOrder,
            operation, bs, atomExpression(expression2), bsBonds, isBonds, 0);
        nNew += Math.abs(result[0]);
        nModified += result[1];
        bs.clear(atom1);
      }
    } else {
      result = viewer.makeConnections(distances[0], distances[1], bondOrder,
          operation, atomSets[0], atomSets[1], bsBonds, isBonds, energy);
      nNew += Math.abs(result[0]);
      nModified += result[1];
    }
    if (isDelete) {
      if (!(tQuiet || scriptLevel > scriptReportingLevel))
        scriptStatusOrBuffer(GT._("{0} connections deleted", nModified));
      return;
    }
    if (isColorOrRadius) {
      viewer.selectBonds(bsBonds);
      if (!Float.isNaN(radius))
        setShapeSize(JmolConstants.SHAPE_STICKS, (int) (radius * 2000), null);
      if (color != Integer.MIN_VALUE)
        setShapeProperty(JmolConstants.SHAPE_STICKS, "color",
            new Integer(color), bsBonds);
      if (translucency != null) {
        if (translucentLevel == Float.MAX_VALUE)
          translucentLevel = viewer.getDefaultTranslucent();
        setShapeProperty(JmolConstants.SHAPE_STICKS, "translucentLevel",
            new Float(translucentLevel));
        setShapeProperty(JmolConstants.SHAPE_STICKS, "translucency",
            translucency, bsBonds);
      }
      viewer.selectBonds(null);
    }
    if (!(tQuiet || scriptLevel > scriptReportingLevel))
      scriptStatusOrBuffer(GT._("{0} new bonds; {1} modified", new Object[] {
          new Integer(nNew), new Integer(nModified) }));
  }

  private float getTranslucentLevel(int i) throws ScriptException {
    float f = floatParameter(i);
    return (theTok == Token.integer && f > 0 && f < 9 ? f + 1 : f);
  }

  private void getProperty() throws ScriptException {
    if (isSyntaxCheck)
      return;
    String retValue = "";
    String property = optParameterAsString(1);
    String name = property;
    if (name.indexOf(".") >= 0)
      name = name.substring(0, name.indexOf("."));
    if (name.indexOf("[") >= 0)
      name = name.substring(0, name.indexOf("["));
    int propertyID = PropertyManager.getPropertyNumber(name);
    String param = optParameterAsString(2);
    int tok = tokAt(2);
    BitSet bs = (tok == Token.expressionBegin || tok == Token.bitset ? atomExpression(2)
        : null);
    if (property.length() > 0 && propertyID < 0) {
      // no such property
      property = ""; // produces a list from Property Manager
      param = "";
    } else if (propertyID >= 0 && statementLength < 3) {
      param = PropertyManager.getDefaultParam(propertyID);
      if (param.equals("(visible)")) {
        viewer.setModelVisibility();
        bs = viewer.getVisibleSet();
      }
    } else if (propertyID == PropertyManager.PROP_FILECONTENTS_PATH) {
      for (int i = 3; i < statementLength; i++)
        param += parameterAsString(i);
    }
    retValue = (String) viewer.getProperty("readable", property,
        (bs == null ? (Object) param : (Object) bs));
    showString(retValue);
  }

  private void background(int i) throws ScriptException {
    getToken(i);
    int argb;
    if (theTok == Token.image) {
      // background IMAGE "xxxx.jpg"
      String file = parameterAsString(checkLast(++i));
      if (isSyntaxCheck)
        return;
      Hashtable htParams = new Hashtable();
      Object image = null;
      if (!file.equalsIgnoreCase("none") && file.length() > 0)
        image = viewer.getFileAsImage(file, htParams);
      if (image instanceof String)
        evalError((String) image, null);
      viewer.setBackgroundImage((String) htParams.get("fullPathName"),
          (Image) image);
      return;
    }
    if (isColorParam(i) || theTok == Token.none) {
      argb = getArgbParamLast(i, true);
      if (isSyntaxCheck)
        return;
      setObjectArgb("background", argb);
      viewer.setBackgroundImage(null, null);
      return;
    }
    int iShape = getShapeType(theTok);
    colorShape(iShape, i + 1, true);
  }

  private void center(int i) throws ScriptException {
    // from center (atom) or from zoomTo under conditions of not
    // windowCentered()
    if (statementLength == 1) {
      viewer.setNewRotationCenter(null);
      return;
    }
    Point3f center = centerParameter(i);
    if (center == null)
      error(ERROR_invalidArgument);
    if (!isSyntaxCheck)
      viewer.setNewRotationCenter(center);
  }

  private String setObjectProperty() throws ScriptException {
    String s = "";
    String id = getShapeNameParameter(2);
    Object[] data = new Object[] { id, null };
    if (isSyntaxCheck)
      return "";
    int iTok = iToken;
    int tokCommand = tokAt(0);
    boolean isWild = TextFormat.isWild(id);
    for (int iShape = JmolConstants.SHAPE_DIPOLES;;) {
      if (iShape != JmolConstants.SHAPE_MO
          && getShapeProperty(iShape, "checkID", data)) {
        setShapeProperty(iShape, "thisID", id);
        switch (tokCommand) {
        case Token.delete:
          setShapeProperty(iShape, "delete", null);
          break;
        case Token.hide:
        case Token.display:
          setShapeProperty(iShape, "hidden",
              tokCommand == Token.display ? Boolean.FALSE : Boolean.TRUE);
          break;
        case Token.show:
          if (iShape == JmolConstants.SHAPE_ISOSURFACE && !isWild)
            return getIsosurfaceJvxl(false, JmolConstants.SHAPE_ISOSURFACE);
          else if (iShape == JmolConstants.SHAPE_PMESH && !isWild)
            return getIsosurfaceJvxl(true, JmolConstants.SHAPE_PMESH);
          s += (String) getShapeProperty(iShape, "command") + "\n";
        case Token.color:
          colorShape(iShape, iTok + 1, false);
          break;
        }
        if (!isWild)
          break;
      }
      if (iShape == JmolConstants.SHAPE_DIPOLES)
        iShape = JmolConstants.SHAPE_MAX_HAS_ID;
      if (--iShape < JmolConstants.SHAPE_MIN_HAS_ID)
        break;
    }
    return s;
  }

  private void color() throws ScriptException {
    int i = 1;
    if (isColorParam(1)) {
      theTok = Token.atoms;
    } else {
      int argb = 0;
      i = 2;
      int tok = getToken(1).tok;
      switch (tok) {
      case Token.dollarsign:
        setObjectProperty();
        return;
      case Token.altloc:
      case Token.amino:
      case Token.chain:
      case Token.fixedtemp:
      case Token.formalcharge:
      case Token.group:
      case Token.insertion:
      case Token.jmol:
      case Token.molecule:
      case Token.monomer:
      case Token.none:
      case Token.opaque:
      case Token.partialcharge:
      case Token.polymer:
      case Token.property:
      case Token.rasmol:
      case Token.spacefill:
      case Token.shapely:
      case Token.straightness:
      case Token.structure:
      case Token.surfacedistance:
      case Token.temperature:
      case Token.translucent:
      case Token.user:
      case Token.vanderwaals:
        theTok = Token.atoms;
        i = 1;
        break;
      case Token.string:
        String strColor = stringParameter(1);
        boolean isTranslucent = (tokAt(2) == Token.translucent);
        if (!isSyntaxCheck)
          viewer.setPropertyColorScheme(strColor, isTranslucent, true);
        i = (isTranslucent ? 3 : 2);
        if (tokAt(i) == Token.range || tokAt(i) == Token.absolute) {
          float min = floatParameter(++i);
          float max = floatParameter(++i);
          if (!isSyntaxCheck)
            viewer.setCurrentColorRange(min, max);
        }
        return;
      case Token.range:
      case Token.absolute:
        float min = floatParameter(2);
        float max = floatParameter(checkLast(3));
        if (!isSyntaxCheck)
          viewer.setCurrentColorRange(min, max);
        return;
      case Token.background:
        argb = getArgbParamLast(2, true);
        if (!isSyntaxCheck)
          setObjectArgb("background", argb);
        return;
      case Token.bitset:
      case Token.expressionBegin:
        i = -1;
        theTok = Token.atoms;
        break;
      case Token.rubberband:
        argb = getArgbParamLast(2, false);
        if (!isSyntaxCheck)
          viewer.setRubberbandArgb(argb);
        return;
      case Token.highlight:
      case Token.selectionhalos:
        i = 2;
        if (tokAt(2) == Token.opaque)
          i++;
        argb = getArgbParamLast(i, true);
        if (isSyntaxCheck)
          return;
        loadShape(JmolConstants.SHAPE_HALOS);
        setShapeProperty(
            JmolConstants.SHAPE_HALOS,
            (tok == Token.selectionhalos ? "argbSelection" : "argbHighlight"),
            new Integer(argb));
        return;
      case Token.axes:
      case Token.boundbox:
      case Token.unitcell:
      case Token.identifier:
      case Token.hydrogen:
        // color element
        String str = parameterAsString(1);
        if (checkToken(2)) {
          switch (getToken(2).tok) {
          case Token.rasmol:
            argb = Token.rasmol;
            break;
          case Token.none:
          case Token.jmol:
            argb = Token.jmol;
            break;
          default:
            argb = getArgbParam(2);
          }
        }
        if (argb == 0)
          error(ERROR_colorOrPaletteRequired);
        checkLast(iToken);
        if (str.equalsIgnoreCase("axes")
            || StateManager.getObjectIdFromName(str) >= 0) {
          setObjectArgb(str, argb);
          return;
        }
        if (changeElementColor(str, argb))
          return;
        error(ERROR_invalidArgument);
      case Token.isosurface:
        setShapeProperty(JmolConstants.SHAPE_ISOSURFACE, "thisID",
            JmolConstants.PREVIOUS_MESH_ID);
        break;
      }
    }
    colorShape(getShapeType(theTok), i, false);
  }

  private boolean changeElementColor(String str, int argb) {
    for (int i = Elements.elementNumberMax; --i >= 0;) {
      if (str.equalsIgnoreCase(Elements.elementNameFromNumber(i))) {
        if (!isSyntaxCheck)
          viewer.setElementArgb(i, argb);
        return true;
      }
    }
    for (int i = Elements.altElementMax; --i >= 0;) {
      if (str.equalsIgnoreCase(Elements.altElementNameFromIndex(i))) {
        if (!isSyntaxCheck)
          viewer.setElementArgb(Elements.altElementNumberFromIndex(i), argb);
        return true;
      }
    }
    if (str.charAt(0) != '_')
      return false;
    for (int i = Elements.elementNumberMax; --i >= 0;) {
      if (str.equalsIgnoreCase("_" + Elements.elementSymbolFromNumber(i))) {
        if (!isSyntaxCheck)
          viewer.setElementArgb(i, argb);
        return true;
      }
    }
    for (int i = Elements.altElementMax; --i >= JmolConstants.firstIsotope;) {
      if (str.equalsIgnoreCase("_" + Elements.altElementSymbolFromIndex(i))) {
        if (!isSyntaxCheck)
          viewer.setElementArgb(Elements.altElementNumberFromIndex(i), argb);
        return true;
      }
      if (str.equalsIgnoreCase("_" + Elements.altIsotopeSymbolFromIndex(i))) {
        if (!isSyntaxCheck)
          viewer.setElementArgb(Elements.altElementNumberFromIndex(i), argb);
        return true;
      }
    }
    return false;
  }

  private void colorShape(int shapeType, int index, boolean isBackground)
      throws ScriptException {
    String translucency = null;
    Object colorvalue = null;
    BitSet bs = null;
    String prefix = "";
    boolean isColor = false;
    boolean isIsosurface = (shapeType == JmolConstants.SHAPE_ISOSURFACE);
    int typeMask = 0;
    float translucentLevel = Float.MAX_VALUE;
    if (index < 0) {
      bs = atomExpression(-index);
      index = iToken + 1;
      if (isBondSet)
        shapeType = JmolConstants.SHAPE_STICKS;
    }
    if (isBackground)
      getToken(index);
    else if ((isBackground = (getToken(index).tok == Token.background)) == true)
      getToken(++index);
    if (isBackground)
      prefix = "bg";
    else if (isIsosurface && theTok == Token.mesh) {
      getToken(++index);
      prefix = "mesh";
    }
    if (!isSyntaxCheck && shapeType == JmolConstants.SHAPE_MO && !mo(true))
      return;
    boolean isTranslucent = (theTok == Token.translucent);
    if (isTranslucent || theTok == Token.opaque) {
      translucency = parameterAsString(index++);
      if (isTranslucent && isFloatParameter(index))
        translucentLevel = getTranslucentLevel(index++);
    }
    int tok = 0;
    if (index < statementLength && tokAt(index) != Token.on
        && tokAt(index) != Token.off) {
      isColor = true;
      tok = getToken(index).tok;
      if (isColorParam(index)) {
        int argb = getArgbParam(index, false);
        colorvalue = (argb == 0 ? null : new Integer(argb));
        if (translucency == null && tokAt(index = iToken + 1) != Token.nada) {
          getToken(index);
          isTranslucent = (theTok == Token.translucent);
          if (isTranslucent || theTok == Token.opaque) {
            translucency = parameterAsString(index);
            if (isTranslucent && isFloatParameter(index + 1))
              translucentLevel = getTranslucentLevel(++index);
          }
          // checkLength(index + 1);
          // iToken = index;
        }
      } else if (shapeType == JmolConstants.SHAPE_LCAOCARTOON) {
        iToken--; // back up one
      } else {
        // must not be a color, but rather a color SCHEME
        // this could be a problem for properties, which can't be
        // checked later -- they must be turned into a color NOW.

        // "cpk" value would be "spacefill"
        String name = parameterAsString(index).toLowerCase();
        boolean isByElement = (name.indexOf(ColorEncoder.BYELEMENT_PREFIX) == 0);
        boolean isColorIndex = (isByElement || name
            .indexOf(ColorEncoder.BYRESIDUE_PREFIX) == 0);
        byte pid = (isColorIndex || isIsosurface ? JmolConstants.PALETTE_PROPERTY
            : tok == Token.spacefill ? JmolConstants.PALETTE_CPK
                : JmolConstants.getPaletteID(name));
        // color atoms "cpkScheme"
        if (pid == JmolConstants.PALETTE_UNKNOWN
            || (pid == JmolConstants.PALETTE_TYPE || pid == JmolConstants.PALETTE_ENERGY)
            && shapeType != JmolConstants.SHAPE_HSTICKS)
          error(ERROR_invalidArgument);
        Object data = null;
        BitSet bsSelected = (pid != JmolConstants.PALETTE_PROPERTY
            && pid != JmolConstants.PALETTE_VARIABLE
            || !viewer.isRangeSelected() ? null : viewer
                .getSelectionSet(false));
        if (pid == JmolConstants.PALETTE_PROPERTY) {
          if (isColorIndex) {
            if (!isSyntaxCheck) {
              data = getBitsetPropertyFloat(bsSelected, (isByElement ? Token.elemno
                  : Token.groupid) | Token.allfloat, Float.NaN, Float.NaN);
            }
          } else {
            if (!isColorIndex && !isIsosurface)
              index++;
            if (name.equals("property")
                && Token.tokAttr((tok = getToken(index).tok),
                    Token.atomproperty)
                && !Token.tokAttr(tok, Token.strproperty)) {
              if (!isSyntaxCheck) {
                data = getBitsetPropertyFloat(bsSelected,
                    getToken(index++).tok | Token.allfloat, Float.NaN, Float.NaN);
              }
            }
          }
        } else if (pid == JmolConstants.PALETTE_VARIABLE) {
          index++;
          name = parameterAsString(index++);
          data = new float[viewer.getAtomCount()];
          Parser.parseStringInfestedFloatArray("" + getParameter(name, Token.string),
              null, (float[]) data);
          pid = JmolConstants.PALETTE_PROPERTY;
        }
        if (pid == JmolConstants.PALETTE_PROPERTY) {
          String scheme = (tokAt(index) == Token.string ? parameterAsString(
              index++).toLowerCase() : null);
          if (scheme != null && !isIsosurface) {
            setStringProperty("propertyColorScheme", (isTranslucent
                && translucentLevel == Float.MAX_VALUE ? "translucent " : "")
                + scheme);
            isColorIndex = (scheme.indexOf(ColorEncoder.BYELEMENT_PREFIX) == 0 || scheme
                .indexOf(ColorEncoder.BYRESIDUE_PREFIX) == 0);
          }
          float min = 0;
          float max = Float.MAX_VALUE;
          if (!isColorIndex
              && (tokAt(index) == Token.absolute || tokAt(index) == Token.range)) {
            min = floatParameter(index + 1);
            max = floatParameter(index + 2);
            index += 3;
            if (min == max && isIsosurface) {
              float[] range = (float[]) getShapeProperty(shapeType, "dataRange");
              if (range != null) {
                min = range[0];
                max = range[1];
              }
            } else if (min == max) {
              max = Float.MAX_VALUE;
            }
          }
          if (!isSyntaxCheck) {
            if (isIsosurface) {
            } else if (data == null) {
              viewer.setCurrentColorRange(name);
            } else {
              viewer.setCurrentColorRange((float[]) data, bsSelected);
            }
            if (isIsosurface) {
              checkLength(index);
              isColor = false;
              ColorEncoder ce = viewer.getColorEncoder(scheme);
              if (ce == null)
                return;
              ce.isTranslucent = (isTranslucent && translucentLevel == Float.MAX_VALUE);
              ce.setRange(min, max, min > max);
              if (max == Float.MAX_VALUE)
                ce.hi = max;
              setShapeProperty(shapeType, "remapColor", ce);
              showString(getIsosurfaceDataRange(shapeType, ""));
              if (translucentLevel == Float.MAX_VALUE)
                return;
            } else if (max != Float.MAX_VALUE) {
              viewer.setCurrentColorRange(min, max);
            }
          }
        } else {
          index++;
        }
        checkLength(index);
        colorvalue = new Byte(pid);
      }
     }
    if (isSyntaxCheck || shapeType < 0)
      return;
    switch (shapeType) {
    case JmolConstants.SHAPE_STRUTS:
      typeMask = JmolEdge.BOND_STRUT;
      break;
    case JmolConstants.SHAPE_HSTICKS:
      typeMask = JmolEdge.BOND_HYDROGEN_MASK;
      break;
    case JmolConstants.SHAPE_SSSTICKS:
      typeMask = JmolEdge.BOND_SULFUR_MASK;
      break;
    case JmolConstants.SHAPE_STICKS:
      typeMask = JmolEdge.BOND_COVALENT_MASK;
      break;
    default:
      typeMask = 0;
    }
    if (typeMask == 0) {
      loadShape(shapeType);
      if (shapeType == JmolConstants.SHAPE_LABELS)
        setShapeProperty(JmolConstants.SHAPE_LABELS, "setDefaults", viewer
            .getNoneSelected());
    } else {
      if (bs != null) {
        viewer.selectBonds(bs);
        bs = null;
      }
      shapeType = JmolConstants.SHAPE_STICKS;
      setShapeProperty(shapeType, "type", new Integer(typeMask));
    }
    if (isColor) {
      // ok, the following five options require precalculation.
      // the state must not save them as paletteIDs, only as pure
      // color values.
      switch (tok) {
      case Token.surfacedistance:
      case Token.straightness:
        viewer.autoCalculate(tok);
        break;
      case Token.temperature:
        if (viewer.isRangeSelected())
          viewer.clearBfactorRange();
        break;
      case Token.group:
        viewer.calcSelectedGroupsCount();
        break;
      case Token.polymer:
      case Token.monomer:
        viewer.calcSelectedMonomersCount();
        break;
      case Token.molecule:
        viewer.calcSelectedMoleculesCount();
        break;
      }
      if (bs == null)
        setShapeProperty(shapeType, prefix + "color", colorvalue);
      else
        setShapeProperty(shapeType, prefix + "color", colorvalue, bs);
    }
    if (translucency != null)
      setShapeTranslucency(shapeType, prefix, translucency, translucentLevel,
          bs);
    if (typeMask != 0)
      setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
          JmolEdge.BOND_COVALENT_MASK));
  }

  private void colorShape(int shapeType, int typeMask, int argb,
                          String translucency, float translucentLevel, BitSet bs) {

    if (typeMask != 0) {
      setShapeProperty(shapeType = JmolConstants.SHAPE_STICKS, "type",
          new Integer(typeMask));
    }
    setShapeProperty(shapeType, "color", new Integer(argb), bs);
    if (translucency != null)
      setShapeTranslucency(shapeType, "", translucency, translucentLevel, bs);
    if (typeMask != 0)
      setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
          JmolEdge.BOND_COVALENT_MASK));
  }

  private void setShapeTranslucency(int shapeType, String prefix,
                                    String translucency,
                                    float translucentLevel, BitSet bs) {
    if (translucentLevel == Float.MAX_VALUE)
      translucentLevel = viewer.getDefaultTranslucent();
    setShapeProperty(shapeType, "translucentLevel", new Float(translucentLevel));
    if (prefix == null)
      return;
    if (bs == null)
      setShapeProperty(shapeType, prefix + "translucency", translucency);
    else if (!isSyntaxCheck)
      setShapeProperty(shapeType, prefix + "translucency", translucency, bs);
  }

  private void cd() throws ScriptException {
    if (isSyntaxCheck)
      return;
    String dir = (statementLength == 1 ? null : parameterAsString(1));
    showString(viewer.cd(dir));
  }

  private Object[] data;

  private void mapProperty() throws ScriptException {
    // map {1.1}.straightness  {2.1}.property_x resno
    BitSet bsFrom, bsTo;
    String property1, property2, mapKey;
    int tokProp1 = 0;
    int tokProp2 = 0;
    int tokKey = 0;
    while (true) {
      if (tokAt(1) == Token.selected) {
        bsFrom = viewer.getSelectionSet(false);
        bsTo = atomExpression(2);
        property1 = property2 = "selected";
      } else {
        bsFrom = atomExpression(1);
        if (tokAt(++iToken) != Token.per
            || !Token.tokAttr(tokProp1 = tokAt(++iToken), Token.atomproperty))
          break;
        property1 = parameterAsString(iToken);
        bsTo = atomExpression(++iToken);
        if (tokAt(++iToken) != Token.per
            || !Token.tokAttr(tokProp2 = tokAt(++iToken), Token.settable))
          break;
        property2 = parameterAsString(iToken);
      }
      if (Token.tokAttr(tokKey = tokAt(iToken + 1), Token.atomproperty))
        mapKey = parameterAsString(++iToken);
      else
        mapKey = Token.nameOf(tokKey = Token.atomno);
      checkLast(iToken);
      if (isSyntaxCheck)
        return;
      BitSet bsOut = null;
      showString("mapping " + property1.toUpperCase() + " for "
          + bsFrom.cardinality() + " atoms to " + property2.toUpperCase()
          + " for " + bsTo.cardinality() + " atoms using "
          + mapKey.toUpperCase());
      if (Token.tokAttrOr(tokProp1, Token.intproperty, Token.floatproperty)
          && Token.tokAttrOr(tokProp2, Token.intproperty, Token.floatproperty)
          && Token.tokAttrOr(tokKey, Token.intproperty, Token.floatproperty)) {
        float[] data1 = getBitsetPropertyFloat(bsFrom, tokProp1 | Token.selectedfloat, Float.NaN,
            Float.NaN);
        float[] data2 = getBitsetPropertyFloat(bsFrom, tokKey | Token.selectedfloat, Float.NaN,
            Float.NaN);
        float[] data3 = getBitsetPropertyFloat(bsTo, tokKey | Token.selectedfloat, Float.NaN,
            Float.NaN);
        boolean isProperty = (tokProp2 == Token.property);
        float[] dataOut = new float[isProperty ? viewer.getAtomCount()
            : data3.length];
        bsOut = new BitSet();
        if (data1.length == data2.length) {
          Hashtable ht = new Hashtable();
          for (int i = 0; i < data1.length; i++)
            ht.put(new Float(data2[i]), new Float(data1[i]));
          int pt = -1;
          int nOut = 0;
          for (int i = 0; i < data3.length; i++) {
            pt = bsTo.nextSetBit(pt + 1);
            Float F = (Float) ht.get(new Float(data3[i]));
            if (F == null)
              continue;
            bsOut.set(pt);
            dataOut[(isProperty ? pt : nOut)] = F.floatValue();
            nOut++;
          }
          if (isProperty)
            viewer.setData(property2,
                new Object[] { property2, dataOut, bsOut }, viewer
                    .getAtomCount(), 0, 0, Integer.MAX_VALUE, 0);
          else
            viewer.setAtomProperty(bsOut, tokProp2, 0, 0, null, dataOut, null);
        }
      }
      if (bsOut == null) {
        String format = "{" + mapKey + "=%[" + mapKey + "]}." + property2
            + " = %[" + property1 + "]";
        String[] data = (String[]) getBitsetIdent(bsFrom, format, null, false,
            Integer.MAX_VALUE, false);
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < data.length; i++)
          if (data[i].indexOf("null") < 0)
            sb.append(data[i]).append('\n');
        if (Logger.debugging)
          Logger.info(sb.toString());
        BitSet bsSubset = BitSetUtil.copy(viewer.getSelectionSubset());
        viewer.setSelectionSubset(bsTo);
        try {
          runScript(sb.toString());
        } catch (Exception e) {
          viewer.setSelectionSubset(bsSubset);
          error(-1, "Error: " + e.getMessage());
        } catch (Error er) {
          viewer.setSelectionSubset(bsSubset);
          error(-1, "Error: " + er.getMessage());
        }
        viewer.setSelectionSubset(bsSubset);
      }
      showString("DONE");
      return;
    }
    error(ERROR_invalidArgument);
  }

  private void data() throws ScriptException {
    String dataString = null;
    String dataLabel = null;
    boolean isOneValue = false;
    int i;
    switch (iToken = statementLength) {
    case 5:
      // parameters 3 and 4 are just for the ride: [end] and ["key"]
      dataString = parameterAsString(2);
      // fall through
    case 4:
    case 2:
      dataLabel = parameterAsString(1);
      if (dataLabel.equalsIgnoreCase("clear")) {
        if (!isSyntaxCheck)
          viewer.setData(null, null, 0, 0, 0, 0, 0);
        return;
      }
      if ((i = dataLabel.indexOf("@")) >= 0) {
        dataString = "" + getParameter(dataLabel.substring(i + 1), Token.string);
        dataLabel = dataLabel.substring(0, i).trim();
      } else if (dataString == null && (i = dataLabel.indexOf(" ")) >= 0) {
        dataString = dataLabel.substring(i + 1).trim();
        dataLabel = dataLabel.substring(0, i).trim();
        isOneValue = true;
      }
      break;
    default:
      error(ERROR_badArgumentCount);
    }
    String dataType = dataLabel + " ";
    dataType = dataType.substring(0, dataType.indexOf(" ")).toLowerCase();
    if (dataType.equals("model") || dataType.equals("append")) {
      load();
      return;
    }
    if (isSyntaxCheck)
      return;
    boolean isDefault = (dataLabel.toLowerCase().indexOf("(default)") >= 0);
    data = new Object[3];
    if (dataType.equals("element_vdw")) {
      // vdw for now
      data[0] = dataType;
      data[1] = dataString.replace(';', '\n');
      int n = Elements.elementNumberMax;
      int[] eArray = new int[n + 1];
      for (int ie = 1; ie <= n; ie++)
        eArray[ie] = ie;
      data[2] = eArray;
      viewer.setData("element_vdw", data, n, 0, 0, 0, 0);
      return;
    }
    if (dataType.equals("connect_atoms")) {
      viewer.connect(Parser.parseFloatArray2d(dataString));
      return;
    }
    if (dataType.indexOf("data2d_") == 0) {
      // data2d_someName
      data[0] = dataLabel;
      data[1] = Parser.parseFloatArray2d(dataString);
      viewer.setData(dataLabel, data, 0, 0, 0, 0, 0);
      return;
    }
    if (dataType.indexOf("data3d_") == 0) {
      // data3d_someName
      data[0] = dataLabel;
      data[1] = Parser.parseFloatArray3d(dataString);
      viewer.setData(dataLabel, data, 0, 0, 0, 0, 0);
      return;
    }
    String[] tokens = Parser.getTokens(dataLabel);
    if (dataType.indexOf("property_") == 0
        && !(tokens.length == 2 && tokens[1].equals("set"))) {
      BitSet bs = viewer.getSelectionSet(false);
      data[0] = dataType;
      int atomNumberField = (isOneValue ? 0 : ((Integer) viewer
          .getParameter("propertyAtomNumberField")).intValue());
      int atomNumberFieldColumnCount = (isOneValue ? 0 : ((Integer) viewer
          .getParameter("propertyAtomNumberColumnCount")).intValue());
      int propertyField = (isOneValue ? Integer.MIN_VALUE : ((Integer) viewer
          .getParameter("propertyDataField")).intValue());
      int propertyFieldColumnCount = (isOneValue ? 0 : ((Integer) viewer
          .getParameter("propertyDataColumnCount")).intValue());
      if (!isOneValue && dataLabel.indexOf(" ") >= 0) {
        if (tokens.length == 3) {
          // DATA "property_whatever [atomField] [propertyField]"
          dataLabel = tokens[0];
          atomNumberField = Parser.parseInt(tokens[1]);
          propertyField = Parser.parseInt(tokens[2]);
        }
        if (tokens.length == 5) {
          // DATA
          // "property_whatever [atomField] [atomFieldColumnCount] [propertyField] [propertyDataColumnCount]"
          dataLabel = tokens[0];
          atomNumberField = Parser.parseInt(tokens[1]);
          atomNumberFieldColumnCount = Parser.parseInt(tokens[2]);
          propertyField = Parser.parseInt(tokens[3]);
          propertyFieldColumnCount = Parser.parseInt(tokens[4]);
        }
      }
      if (atomNumberField < 0)
        atomNumberField = 0;
      if (propertyField < 0)
        propertyField = 0;
      int atomCount = viewer.getAtomCount();
      int[] atomMap = null;
      BitSet bsTemp = new BitSet(atomCount);
      if (atomNumberField > 0) {
        atomMap = new int[atomCount + 2];
        for (int j = 0; j <= atomCount; j++)
          atomMap[j] = -1;
        for (int j = bs.nextSetBit(0); j >= 0; j = bs.nextSetBit(j + 1)) {
          int atomNo = viewer.getAtomNumber(j);
          if (atomNo > atomCount + 1 || atomNo < 0 || bsTemp.get(atomNo))
            continue;
          bsTemp.set(atomNo);
          atomMap[atomNo] = j;
        }
        data[2] = atomMap;
      } else {
        data[2] = BitSetUtil.copy(bs);
      }
      data[1] = dataString;
      viewer.setData(dataType, data, atomCount, atomNumberField,
          atomNumberFieldColumnCount, propertyField, propertyFieldColumnCount);
      return;
    }
    int userType = AtomCollection.getUserSettableType(dataType);
    if (userType >= 0) {
      // this is a known settable type or "property_xxxx"
      viewer.setAtomData(userType, dataType, dataString, isDefault);
      return;
    }
    // this is just information to be stored.
    data[0] = dataLabel;
    data[1] = dataString;
    viewer.setData(dataType, data, 0, 0, 0, 0, 0);
  }

  private void define() throws ScriptException {
    // note that the standard definition depends upon the
    // current state. Once defined, a setName is the set
    // of atoms that matches the definition at that time.
    // adding DYMAMIC_ to the beginning of the definition
    // allows one to create definitions that are recalculated
    // whenever they are used. When used, "DYNAMIC_" is dropped
    // so, for example:
    // define DYNAMIC_what selected and visible
    // and then
    // select what
    // will return different things at different times depending
    // upon what is selected and what is visible
    // but
    // define what selected and visible
    // will evaluate the moment it is defined and then represent
    // that set of atoms forever.

    String setName = ((String) getToken(1).value).toLowerCase();
    if (isSyntaxCheck)
      return;
    boolean isSite = setName.startsWith("site_");
    boolean isDynamic = (setName.indexOf("dynamic_") == 0);
    if (isDynamic || isSite) {
      Token[] code = new Token[statementLength];
      for (int i = statementLength; --i >= 0;)
        code[i] = statement[i];
      definedAtomSets
          .put("!" + (isSite ? setName : setName.substring(8)), code);
      //if (!isSite)
        //viewer.addStateScript(thisCommand, false, true); removed for 12.0.19
    } else {
      BitSet bs = atomExpression(2);
      definedAtomSets.put(setName, bs);
      setStringProperty("@" + setName, Escape.escape(bs));
    }
  }

  private void echo(int index, boolean isImage) throws ScriptException {
    if (isSyntaxCheck)
      return;
    String text = optParameterAsString(index);
    if (viewer.getEchoStateActive()) {
      if (isImage) {
        Hashtable htParams = new Hashtable();
        Object image = viewer.getFileAsImage(text, htParams);
        if (image instanceof String) {
          text = (String) image;
        } else {
          setShapeProperty(JmolConstants.SHAPE_ECHO, "text", htParams
              .get("fullPathName"));
          setShapeProperty(JmolConstants.SHAPE_ECHO, "image", image);
          text = null;
        }
      } else if (text.startsWith("\1")) {
        // no reporting, just screen echo, from mouseManager key press
        text = text.substring(1);
        isImage = true;
      }
      if (text != null)
        setShapeProperty(JmolConstants.SHAPE_ECHO, "text", text);
    }
    if (!isImage && viewer.getRefreshing())
      showString(viewer.formatText(text));
  }

  private void message() throws ScriptException {
    String text = parameterAsString(checkLast(1));
    if (isSyntaxCheck)
      return;
    String s = viewer.formatText(text);
    if (outputBuffer == null)
      viewer.showMessage(s);
    if (!s.startsWith("_"))
      scriptStatusOrBuffer(s);
  }

  private void log() throws ScriptException {
    if (statementLength == 1)
      error(ERROR_badArgumentCount);
    if (isSyntaxCheck)
      return;
    String s = parameterExpressionString(1, 0);
    if (tokAt(1) == Token.off)
      setStringProperty("logFile", "");
    else
      viewer.log(s);
  }

  private void label(int index) throws ScriptException {
    if (isSyntaxCheck)
      return;
    loadShape(JmolConstants.SHAPE_LABELS);
    String strLabel = null;
    switch (getToken(index).tok) {
    case Token.on:
      strLabel = viewer.getStandardLabelFormat();
      break;
    case Token.off:
      break;
    case Token.hide:
    case Token.display:
      setShapeProperty(JmolConstants.SHAPE_LABELS, "display",
          theTok == Token.display ? Boolean.TRUE : Boolean.FALSE);
      return;
    default:
      strLabel = parameterAsString(index);
    }
    shapeManager.setLabel(strLabel, viewer.getSelectionSet(false));
  }

  private void hover() throws ScriptException {
    if (isSyntaxCheck)
      return;
    String strLabel = parameterAsString(1);
    if (strLabel.equalsIgnoreCase("on"))
      strLabel = "%U";
    else if (strLabel.equalsIgnoreCase("off"))
      strLabel = null;
    viewer.setHoverLabel(strLabel);
  }

  private void load() throws ScriptException {
    boolean doLoadFiles = (!isSyntaxCheck || isCmdLine_C_Option);
    boolean isAppend = false;
    boolean isInline = false;
    boolean isSmiles = false;
    boolean isData = false;
    boolean isAuto = false;
    BitSet bsModels;
    int i = (tokAt(0) == Token.data ? 0 : 1);
    boolean appendNew = viewer.getAppendNew();
    String filter = null;
    List firstLastSteps = null;
    int modelCount = viewer.getModelCount()
        - (viewer.getFileName().equals("zapped") ? 1 : 0);
    StringBuffer loadScript = new StringBuffer("load");
    int nFiles = 1;
    Hashtable htParams = new Hashtable();
    // ignore optional file format
    String modelName = null;
    String filename = null;
    String[] filenames = null;
    String[] tempFileInfo = null;
    String errMsg = null;
    String sOptions = "";
    int tokType = 0;
    int tok;

    // check for special parameters

    if (statementLength == 1) {
      i = 0;
    } else {
      modelName = parameterAsString(i);
      switch (tok = tokAt(i)) {
      case Token.menu:
        String m = parameterAsString(checkLast(2));
        if (!isSyntaxCheck)
          viewer.setMenu(m, true);
        return;
      case Token.data:
        isData = true;
        loadScript.append(" data");
        String key = stringParameter(++i).toLowerCase();
        loadScript.append(" ").append(Escape.escape(key));
        isAppend = key.startsWith("append");
        String strModel = parameterAsString(++i);
        strModel = viewer.fixInlineString(strModel, viewer.getInlineChar());
        htParams.put("fileData", strModel);
        htParams.put("isData", Boolean.TRUE);
        //note: ScriptCompiler will remove an initial \n if present
        loadScript.append('\n');
        loadScript.append(strModel);
        loadScript.append(" end ").append(Escape.escape(key));
        i += 2; // skip END "key"
        break;
      case Token.append:
        isAppend = true;
        loadScript.append(" " + modelName);
        filename = optParameterAsString(++i);
        tok = Token.getTokFromName(filename);
        break;
      case Token.identifier:
        i++;
        loadScript.append(" " + modelName);
        tokType = (tok == Token.identifier
            && Parser.isOneOf(modelName.toLowerCase(),
                JmolConstants.LOAD_ATOM_DATA_TYPES) ? Token
            .getTokFromName(modelName.toLowerCase()) : 0);
        if (tokType > 0) {
          // loading just some data here
          // xyz vxyz vibration temperature occupancy partialcharge
          htParams.put("atomDataOnly", Boolean.TRUE);
          htParams.put("modelNumber", Integer.valueOf(1));
          if (tokType == Token.vibration)
            tokType = Token.vibxyz;
          tempFileInfo = viewer.getFileInfo();
          isAppend = true;
        }
      }
      switch (tok) {
      case Token.file:
      case Token.inline:
      case Token.smiles:
        isInline = (tok == Token.inline);
        isSmiles = (tok == Token.smiles);
        modelName = filename;
        i++;
        loadScript.append(" " + modelName);
        break;
      case Token.trajectory:
      case Token.model:
        modelName = filename;
        i++;
        loadScript.append(" " + modelName);
        if (tok == Token.trajectory)
          htParams.put("isTrajectory", Boolean.TRUE);
        if (isPoint3f(i)) {
          Point3f pt = getPoint3f(i, false);
          i = iToken + 1;
          // first last stride
          htParams.put("firstLastStep", new int[] { (int) pt.x, (int) pt.y,
              (int) pt.z });
          loadScript.append(" " + Escape.escape(pt));
        } else if (tokAt(i) == Token.bitset) {
          bsModels = (BitSet) getToken(i++).value;
          htParams.put("bsModels", bsModels);
          loadScript.append(" " + Escape.escape(bsModels));
        } else {
          htParams.put("firstLastStep", new int[] { 0, -1, 1 });
        }
        break;
      case Token.identifier:
        break;
      default:
        modelName = "fileset";
      }
      if (getToken(i).tok != Token.string)
        error(ERROR_filenameExpected);
    }
    // long timeBegin = System.currentTimeMillis();

    // file name is next

    int filePt = i;
    String localName = null;
    if (tokAt(filePt + 1) == Token.as)
      localName = stringParameter(i = i + 2);
    if (statementLength == i + 1) {
      if (i == 0 || (filename = parameterAsString(filePt)).length() == 0)
        filename = viewer.getFullPathName();
      if (filename == null) {
        zap(false);
        return;
      }
      if (isSmiles) {
        filename = "$" + filename;
      } else if (!isInline) {
        if (filename.indexOf("[]") >= 0)
          return;
        if (filename.indexOf("[") == 0) {
          filenames = Escape.unescapeStringArray(filename);
          if (filenames != null) {
            if (i == 1)
              loadScript.append(" files");
            if (loadScript.indexOf(" files") < 0)
              error(ERROR_invalidArgument);
            for (int j = 0; j < filenames.length; j++)
              loadScript.append(" /*file*/")
                  .append(Escape.escape(filenames[j]));
          }
        }
      }
    } else if (getToken(i + 1).tok == Token.leftbrace
        || theTok == Token.point3f || theTok == Token.integer
        || theTok == Token.range || theTok == Token.manifest
        || theTok == Token.packed || theTok == Token.filter
        && tokAt(i + 3) != Token.coord || theTok == Token.identifier
        && tokAt(i + 3) != Token.coord) {
      if ((filename = parameterAsString(filePt)).length() == 0)
        filename = viewer.getFullPathName();
      if (filePt == i)
        i++;
      if (filename == null) {
        zap(false);
        return;
      }
      if (filename.indexOf("[]") >= 0)
        return;
      if ((tok = tokAt(i)) == Token.manifest) {
        String manifest = stringParameter(++i);
        htParams.put("manifest", manifest);
        sOptions += " MANIFEST " + Escape.escape(manifest);
        tok = tokAt(++i);
      }
      if (tok == Token.integer) {
        int n = intParameter(i);
        sOptions += " " + n;
        if (n < 0)
          htParams.put("vibrationNumber", new Integer(-n));
        else
          htParams.put("modelNumber", new Integer(n));
        tok = tokAt(++i);
      }
      Point3f lattice = null;
      if (tok == Token.leftbrace || tok == Token.point3f) {
        lattice = getPoint3f(i, false);
        i = iToken + 1;
        tok = tokAt(i);
      }
      boolean isPacked = false;
      if (tok == Token.packed) {
        if (lattice == null)
          lattice = new Point3f(555, 555, -1);
        isPacked = true;
        iToken++;
        i++;
      }
      if (lattice != null) {
        i = iToken + 1;
        htParams.put("lattice", lattice);
        sOptions += " {" + (int) lattice.x + " " + (int) lattice.y + " "
            + (int) lattice.z + "}";
        if (isPacked) {
          htParams.put("packed", Boolean.TRUE);
          sOptions += " PACKED";
        }
        float distance = 0;
        /*
         * # Jmol 11.3.9 introduces the capability of visualizing the close
         * contacts around a crystalline protein (or any other cyrstal
         * structure) that are to atoms that are in proteins in adjacent unit
         * cells or adjacent to the protein itself. The option RANGE x, where x
         * is a distance in angstroms, placed right after the braces containing
         * the set of unit cells to load does this. The distance, if a positive
         * number, is the maximum distance away from the closest atom in the {1
         * 1 1} set. If the distance x is a negative number, then -x is the
         * maximum distance from the {not symmetry} set. The difference is that
         * in the first case the primary unit cell (555) is first filled as
         * usual, using symmetry operators, and close contacts to this set are
         * found. In the second case, only the file-based atoms ( Jones-Faithful
         * operator x,y,z) are initially included, then close contacts to that
         * set are found. Depending upon the application, one or the other of
         * these options may be desirable.
         */
        if (tokAt(i) == Token.range) {
          i++;
          distance = floatParameter(i++);
          sOptions += " range " + distance;
        }
        htParams.put("symmetryRange", new Float(distance));
        String spacegroup = null;
        float[] fparams = null;
        int iGroup = Integer.MIN_VALUE;
        if (tokAt(i) == Token.spacegroup) {
          ++i;
          spacegroup = TextFormat.simpleReplace(parameterAsString(i++), "''",
              "\"");
          sOptions += " spacegroup " + Escape.escape(spacegroup);
        }
        if (tokAt(i) == Token.unitcell) {
          ++i;
          fparams = floatParameterSet(i, 6, 6);
          i = iToken;
          sOptions += " unitcell {";
          for (int j = 0; j < 6; j++)
            sOptions += (j == 0 ? "" : " ") + fparams[j];
          sOptions += "}";
          htParams.put("unitcell", fparams);
        }
        if (spacegroup != null) {
          if (spacegroup.equalsIgnoreCase("ignoreOperators")) {
            iGroup = -999;
          } else {
            if (spacegroup.indexOf(",") >= 0) // Jones Faithful
              if ((lattice.x < 9 && lattice.y < 9 && lattice.z == 0))
                spacegroup += "#doNormalize=0";
            iGroup = -2;
            htParams.put("spaceGroupName", spacegroup);
          }
        }
        if (fparams != null && iGroup == Integer.MIN_VALUE)
          iGroup = -1;
        if (iGroup != Integer.MIN_VALUE)
          htParams.put("spaceGroupIndex", new Integer(iGroup));
      }
      if (tokAt(i) == Token.filter)
        filter = stringParameter(++i);
    } else {
      if (i == 1) {
        i++;
        loadScript.append(" " + modelName);
      }
      Point3f pt = null;
      BitSet bs = null;
      List fNames = new ArrayList();
      while (i < statementLength) {
        switch (tokAt(i)) {
        case Token.filter:
          filter = stringParameter(++i);
          ++i;
          continue;
        case Token.coord:
          htParams.remove("isTrajectory");
          if (firstLastSteps == null) {
            firstLastSteps = new ArrayList();
            pt = new Point3f(0, -1, 1);
          }
          if (isPoint3f(++i)) {
            pt = getPoint3f(i, false);
            i = iToken + 1;
          } else if (tokAt(i) == Token.bitset) {
            bs = (BitSet) getToken(i).value;
            pt = null;
            i = iToken + 1;
          }
          break;
        case Token.identifier:
          error(ERROR_invalidArgument);
        }
        fNames.add(filename = parameterAsString(i++));
        if (pt != null) {
          firstLastSteps.add(new int[] { (int) pt.x, (int) pt.y, (int) pt.z });
          loadScript.append(" COORD " + Escape.escape(pt));
        } else if (bs != null) {
          firstLastSteps.add(bs);
          loadScript.append(" COORD " + Escape.escape(bs));
        }
        loadScript.append(" /*file*/$FILENAME" + fNames.size() + "$");
      }
      if (firstLastSteps != null)
        htParams.put("firstLastSteps", firstLastSteps);
      nFiles = fNames.size();
      filenames = new String[nFiles];
      for (int j = 0; j < nFiles; j++)
        filenames[j] = (String) fNames.get(j);
      filename = modelName;
    }
    if (!doLoadFiles)
      return;
    if (filter == null)
      filter = viewer.getDefaultLoadFilter();
    if (filter.length() > 0) {
      htParams.put("filter", filter);
      if (filter.equalsIgnoreCase("2d"))
        filter = "2D-noMin";
      sOptions += " FILTER " + Escape.escape(filter);
    }
    boolean isVariable = false;
    if (filenames == null) {
      // standard file loading here
      if (isInline) {
        htParams.put("fileData", filename);
      } else if (filename.startsWith("@") && filename.length() > 1) {
        isVariable = true;
        String s = getStringParameter(filename.substring(1), false);
        htParams.put("fileData", s);
        loadScript = new StringBuffer("{\n    var " + filename.substring(1)
            + " = " + Escape.escape(s) + ";\n    " + loadScript);
      }
    }

    // OK, we are ready to load the data and create the model set

    OutputStream os = null;
    if (localName != null) {
      if (localName.equals("."))
        localName = viewer.getFilePath(filename, true);
      if (localName.length() == 0
          || viewer.getFilePath(localName, false).equalsIgnoreCase(
              viewer.getFilePath(filename, false)))
        error(ERROR_invalidArgument);
      String[] fullPath = new String[] { localName };
      os = viewer.getOutputStream(localName, fullPath);
      if (os == null)
        Logger.error("Could not create output stream for " + fullPath[0]);
      else
        htParams.put("OutputStream", os);
    }

    if (filenames == null && tokType == 0) {
      // a single file or string -- complete the loadScript
      loadScript.append(" ");
      if (isVariable || isInline) {
        loadScript.append(Escape.escape(filename));
        if (isVariable)
          sOptions += "\n  }";
      } else if (!isData) {
        if (!filename.equals("string") && !filename.equals("string[]"))
          loadScript.append("/*file*/");
        if (localName != null)
          localName = viewer.getFilePath(localName, false);
        loadScript.append((localName != null ? Escape.escape(localName)
            : "$FILENAME$"));
      }
      loadScript.append(sOptions);
      htParams.put("loadScript", loadScript);
    }
    setCursorWait(true);
    errMsg = viewer.loadModelFromFile(null, filename, filenames, null,
        isAppend, htParams, loadScript, tokType);
    if (os != null)
      try {
        viewer.setFileInfo(new String[] { localName, localName, localName });
        Logger.info(GT._("file {0} created", localName));
        showString(viewer.getFilePath(localName, false) + " created");
        os.close();
      } catch (IOException e) {
        Logger.error("error closing file " + e.getMessage());
      }
    if (tokType > 0) {
      // we are just loading an atom property
      // reset the file info in FileManager, check for errors, and return
      viewer.setFileInfo(tempFileInfo);
      if (errMsg != null && !isCmdLine_c_or_C_Option)
        evalError(errMsg, null);
      return;
    }
    // with "@t" we do not save the load command but instead the data statement
    // but there could state problems here because then we don't have the
    // option to save load options with that... Hmm.
    if (errMsg != null && !isCmdLine_c_or_C_Option) {
      if (errMsg.indexOf("NOTE: file recognized as a script file:") == 0) {
        filename = errMsg.substring(errMsg.indexOf("file:") + 5).trim();
        script(0, filename);
        return;
      }
      if (isAuto) {
        String surfaceType = (errMsg.indexOf("java.io.FileNotFound") >= 0 ? null
            : SurfaceFileTyper.determineSurfaceFileType(viewer
                .getBufferedInputStream(filename)));
        if (surfaceType != null) {
          runScript("isosurface " + Escape.escape(filename));
          return;
        }
      }
      evalError(errMsg, null);
    }
    if (isAppend && (appendNew || nFiles > 1)) {
      viewer.setAnimationRange(-1, -1);
      viewer.setCurrentModelIndex(modelCount);
    }
    if (logMessages)
      scriptStatusOrBuffer("Successfully loaded:"
          + (filenames == null ? htParams.get("fullPathName") : modelName));
    String script = viewer.getDefaultLoadScript();
    String msg = "";
    if (script.length() > 0)
      msg += "\nUsing defaultLoadScript: " + script;
    if (viewer.getAllowEmbeddedScripts()) {
      String embeddedScript = (String) viewer.getModelSetAuxiliaryInfo()
          .remove("jmolscript");
      if (embeddedScript != null && embeddedScript.length() > 0) {
        msg += "\nAdding embedded #jmolscript: " + embeddedScript;
        script += ";" + embeddedScript;
        setStringProperty("_loadScript", script);
        script = "allowEmbeddedScripts = false;try{" + script
            + "} allowEmbeddedScripts = true;";
      }
    }
    logLoadInfo(msg);

    String siteScript = (String) viewer.getModelSetAuxiliaryInfo().remove(
        "sitescript");
    if (siteScript != null)
      script = siteScript + ";" + script;
    if (script.length() > 0 && !isCmdLine_c_or_C_Option)
      // NOT checking embedded scripts in some cases
      runScript(script);
  }

  private void logLoadInfo(String msg) {
    if (msg.length() > 0)
      Logger.info(msg);
    StringBuffer sb = new StringBuffer();
    int modelCount = viewer.getModelCount();
    if (modelCount > 1)
      sb.append(modelCount).append(" models\n");
    for (int i = 0; i < modelCount; i++) {
      Hashtable moData = (Hashtable) viewer.getModelAuxiliaryInfo(i, "moData");
      if (moData == null)
        continue;
      sb.append(((List) moData.get("mos")).size())
      .append(" molecular orbitals in model ")
      .append(viewer.getModelNumberDotted(i)).append("\n");
    }
    if (sb.length() > 0)
      showString(sb.toString());
  }

  private String getFullPathName() throws ScriptException {
    String filename = (!isSyntaxCheck || isCmdLine_C_Option ? viewer
        .getFullPathName() : "test.xyz");
    if (filename == null)
      error(ERROR_invalidArgument);
    return filename;
  }

  private void measure() throws ScriptException {
    if (tokAt(1) == Token.search) {
      String smarts = stringParameter(statementLength == 3 ? 2 : 4);
      if (isSyntaxCheck)
        return;
      Atom[] atoms = viewer.getModelSet().atoms;
      int atomCount = viewer.getAtomCount();
      int[][] maps = viewer.getSmilesMatcher().getCorrelationMaps(smarts,
          atoms, atomCount, viewer.getSelectionSet(false), true, false);
      if (maps == null)
        return;
      setShapeProperty(JmolConstants.SHAPE_MEASURES, "maps", maps);
      return;
    }
    switch (statementLength) {
    case 1:
    case 2:
      switch (getToken(1).tok) {
      case Token.nada:
      case Token.on:
        setShapeProperty(JmolConstants.SHAPE_MEASURES, "hideAll", Boolean.FALSE);
        return;
      case Token.off:
        setShapeProperty(JmolConstants.SHAPE_MEASURES, "hideAll", Boolean.TRUE);
        return;
      case Token.list:
        if (!isSyntaxCheck)
          showString(viewer.getMeasurementInfoAsString(), false);
        return;
      case Token.delete:
        if (!isSyntaxCheck)
          viewer.clearAllMeasurements();
        return;
      case Token.string:
        setShapeProperty(JmolConstants.SHAPE_MEASURES, "setFormats",
            stringParameter(1));
        return;
      }
      error(ERROR_keywordExpected, "ON, OFF, DELETE");
      break;
    case 3: // measure delete N
      // search "smartsString"
      switch (getToken(1).tok) {
      case Token.delete:
        if (getToken(2).tok == Token.all) {
          if (!isSyntaxCheck)
            viewer.clearAllMeasurements();
        } else {
          int i = intParameter(2) - 1;
          if (!isSyntaxCheck)
            viewer.deleteMeasurement(i);
        }
        return;
      }
    }

    int nAtoms = 0;
    int expressionCount = 0;
    int modelIndex = -1;
    int atomIndex = -1;
    int ptFloat = -1;
    int[] countPlusIndexes = new int[5];
    float[] rangeMinMax = new float[] { Float.MAX_VALUE, Float.MAX_VALUE };
    boolean isAll = false;
    boolean isAllConnected = false;
    boolean isNotConnected = false;
    boolean isRange = true;
    int tokAction = Token.opToggle;
    String strFormat = null;
    List points = new ArrayList();
    BitSet bs = new BitSet();
    Object value = null;
    TickInfo tickInfo = null;
    for (int i = 1; i < statementLength; ++i) {
      switch (getToken(i).tok) {
      case Token.identifier:
        error(ERROR_keywordExpected, "ALL, ALLCONNECTED, DELETE");
      default:
        error(ERROR_expressionOrIntegerExpected);
      case Token.opNot:
        if (tokAt(i + 1) != Token.connected)
          error(ERROR_invalidArgument);
        i++;
        isNotConnected = true;
        break;
      case Token.connected:
      case Token.allconnected:
      case Token.all:
        isAllConnected = (theTok == Token.allconnected);
        atomIndex = -1;
        isAll = true;
        if (isAllConnected && isNotConnected)
          error(ERROR_invalidArgument);
        break;
      case Token.decimal:
        isAll = true;
        isRange = true;
        ptFloat = (ptFloat + 1) % 2;
        rangeMinMax[ptFloat] = floatParameter(i);
        break;
      case Token.delete:
        if (tokAction != Token.opToggle)
          error(ERROR_invalidArgument);
        tokAction = Token.delete;
        break;
      case Token.integer:
        int iParam = intParameter(i);
        if (isAll) {
          isRange = true; // irrelevant if just four integers
          ptFloat = (ptFloat + 1) % 2;
          rangeMinMax[ptFloat] = iParam;
        } else {
          atomIndex = viewer.getAtomIndexFromAtomNumber(iParam);
          if (!isSyntaxCheck && atomIndex < 0)
            return;
          if (value != null)
            error(ERROR_invalidArgument);
          if ((countPlusIndexes[0] = ++nAtoms) > 4)
            error(ERROR_badArgumentCount);
          countPlusIndexes[nAtoms] = atomIndex;
        }
        break;
      case Token.modelindex:
        modelIndex = intParameter(++i);
        break;
      case Token.off:
        if (tokAction != Token.opToggle)
          error(ERROR_invalidArgument);
        tokAction = Token.off;
        break;
      case Token.on:
        if (tokAction != Token.opToggle)
          error(ERROR_invalidArgument);
        tokAction = Token.on;
        break;
      case Token.range:
        isAll = true;
        isRange = true; // unnecessary
        atomIndex = -1;
        break;
      case Token.string:
        // measures "%a1 %a2 %v %u"
        strFormat = stringParameter(i);
        break;
      case Token.ticks:
        tickInfo = checkTicks(i, false, true, true);
        i = iToken;
        tokAction = Token.define;
        break;
      case Token.bitset:
      case Token.expressionBegin:
      case Token.leftbrace:
      case Token.point3f:
      case Token.dollarsign:
        if (atomIndex >= 0)
          error(ERROR_invalidArgument);
        expressionResult = Boolean.FALSE;
        value = centerParameter(i);
        if (expressionResult instanceof BitSet) {
          value = bs = (BitSet) expressionResult;
          if (!isSyntaxCheck && bs.length() == 0)
            return;
        }
        if (value instanceof Point3f) {
          Point3fi v = new Point3fi();
          v.set((Point3f) value);
          v.modelIndex = (short) modelIndex;
          value = v;
        }
        if ((nAtoms = ++expressionCount) > 4)
          error(ERROR_badArgumentCount);
        points.add(value);
        i = iToken;
        break;
      }
    }
    if (nAtoms < 2 && (tickInfo == null || nAtoms == 1))
      error(ERROR_badArgumentCount);
    if (strFormat != null && strFormat.indexOf(nAtoms + ":") != 0)
      strFormat = nAtoms + ":" + strFormat;
    if (isRange && rangeMinMax[1] < rangeMinMax[0]) {
      rangeMinMax[1] = rangeMinMax[0];
      rangeMinMax[0] = (rangeMinMax[1] == Float.MAX_VALUE ? Float.MAX_VALUE
          : -200F);
    }
    if (isSyntaxCheck)
      return;
    if (value != null || tickInfo != null) {
      if (value == null)
        tickInfo.id = "default";
      setShapeProperty(JmolConstants.SHAPE_MEASURES, "measure",
          new MeasurementData(points, tokAction, rangeMinMax, strFormat, null,
              tickInfo, isAllConnected, isNotConnected, isAll));
      return;
    }
    switch (tokAction) {
    case Token.delete:
      setShapeProperty(JmolConstants.SHAPE_MEASURES, "delete", countPlusIndexes);
      break;
    case Token.on:
      setShapeProperty(JmolConstants.SHAPE_MEASURES, "show", countPlusIndexes);
      break;
    case Token.off:
      setShapeProperty(JmolConstants.SHAPE_MEASURES, "hide", countPlusIndexes);
    default:
      setShapeProperty(JmolConstants.SHAPE_MEASURES,
          (strFormat == null ? "toggle" : "toggleOn"), countPlusIndexes);
      if (strFormat != null)
        setShapeProperty(JmolConstants.SHAPE_MEASURES, "setFormats", strFormat);
    }
  }

  private String plot(Token[] args) throws ScriptException {
    // also used for draw [quaternion, helix, ramachandran]
    // and write quaternion, ramachandran, plot, ....
    // and plot property propertyX, propertyY, propertyZ //
    int modelIndex = viewer.getCurrentModelIndex();
    if (modelIndex < 0)
      error(ERROR_multipleModelsDisplayedNotOK, "plot");
    modelIndex = viewer.getJmolDataSourceFrame(modelIndex);
    int pt = args.length - 1;
    boolean isReturnOnly = (args != statement);
    Token[] statementSave = statement;
    if (isReturnOnly)
      statement = args;
    int tokCmd = (isReturnOnly ? Token.show : args[0].tok);
    int pt0 = (isReturnOnly || tokCmd == Token.quaternion || tokCmd == Token.ramachandran ? 0
        : 1);
    String filename = null;
    boolean makeNewFrame = true;
    boolean isDraw = false;
    switch (tokCmd) {
    case Token.plot:
    case Token.quaternion:
    case Token.ramachandran:
      break;
    case Token.draw:
      makeNewFrame = false;
      isDraw = true;
      break;
    case Token.show:
      makeNewFrame = false;
      break;
    case Token.write:
      makeNewFrame = false;
      if (tokAt(pt, args) == Token.string) {
        filename = stringParameter(pt--);
      } else if (tokAt(pt - 1, args) == Token.per) {
        filename = parameterAsString(pt - 2) + "." + parameterAsString(pt);
        pt -= 3;
      } else {
        statement = statementSave;
        iToken = statement.length;
        error(ERROR_endOfStatementUnexpected);
      }
      break;
    }
    String qFrame = "";
    Object[] parameters = null;
    String stateScript = "";
    boolean isQuaternion = false;
    boolean isDerivative = false;
    boolean isSecondDerivative = false;
    boolean isRamachandranRelative = false;
    int propertyX = 0, propertyY = 0, propertyZ = 0;
    BitSet bs = BitSetUtil.copy(viewer.getSelectionSet(false));
    String preSelected = "; select " + Escape.escape(bs) + ";\n ";
    String type = optParameterAsString(pt).toLowerCase();
    Point3f minXYZ = null;
    Point3f maxXYZ = null;
    int plotType = 0;
    int tok = tokAt(pt0, args);
    if (tok == Token.string)
      tok = Token.getTokFromName((String)args[pt0].value);
    switch (tok) {
    case Token.quaternion:
    case Token.helix:
      plotType = JmolConstants.JMOL_DATA_QUATERNION;
      break;
    case Token.ramachandran:
      plotType = JmolConstants.JMOL_DATA_RAMACHANDRAN;
      break;
    case Token.property:
      plotType = JmolConstants.JMOL_DATA_OTHER;
      iToken = pt0 + 1;
      break;
    default:
      iToken = 1;
      error(ERROR_invalidArgument);
    }
    switch (plotType) {
    case JmolConstants.JMOL_DATA_OTHER:
      if (!Token.tokAttr(propertyX = tokAt(iToken++), Token.atomproperty)
          || !Token.tokAttr(propertyY = tokAt(iToken++), Token.atomproperty))
        error(ERROR_invalidArgument);
      if (Token.tokAttr(propertyZ = tokAt(iToken), Token.atomproperty))
        iToken++;
      else
        propertyZ = 0;
      if (tokAt(iToken) == Token.min) {
        minXYZ = getPoint3f(++iToken, false);
        iToken++;
      }
      if (tokAt(iToken) == Token.max) {
        maxXYZ = getPoint3f(++iToken, false);
        iToken++;
      }
      type = "property " + Token.nameOf(propertyX) + " "
          + Token.nameOf(propertyY)
          + (propertyZ == 0 ? "" : " " + Token.nameOf(propertyZ));
      if (bs.nextSetBit(0) < 0)
        bs = viewer.getModelUndeletedAtomsBitSet(modelIndex);
      stateScript = "select " + Escape.escape(bs) + ";\n ";
      break;
    case JmolConstants.JMOL_DATA_RAMACHANDRAN:
      if (type.equalsIgnoreCase("draw")) {
        isDraw = true;
        type = optParameterAsString(--pt).toLowerCase();
      }
      isRamachandranRelative = (pt > pt0 && type.startsWith("r"));
      type = "ramachandran" + (isRamachandranRelative ? " r" : "")
          + (tokCmd == Token.draw ? " draw" : "");
      break;
    case JmolConstants.JMOL_DATA_QUATERNION:
      qFrame = " \"" + viewer.getQuaternionFrame() + "\"";
      stateScript = "set quaternionFrame" + qFrame + ";\n  ";
      isQuaternion = true;
      // working backward this time:
      if (type.equalsIgnoreCase("draw")) {
        isDraw = true;
        type = optParameterAsString(--pt).toLowerCase();
      }
      isDerivative = (type.startsWith("deriv") || type.startsWith("diff"));
      isSecondDerivative = (isDerivative && type.indexOf("2") > 0);
      if (isDerivative)
        pt--;
      if (type.equalsIgnoreCase("helix") || type.equalsIgnoreCase("axis")) {
        isDraw = true;
        isDerivative = true;
        pt = -1;
      }
      type = ((pt <= pt0 ? "" : optParameterAsString(pt)) + "w")
          .substring(0, 1);
      if (type.equals("a") || type.equals("r"))
        isDerivative = true;
      if (!Parser.isOneOf(type, "w;x;y;z;r;a")) // a absolute; r relative
        evalError("QUATERNION [w,x,y,z,a,r] [difference][2]", null);
      type = "quaternion " + type + (isDerivative ? " difference" : "")
          + (isSecondDerivative ? "2" : "") + (isDraw ? " draw" : "");
      break;
    }
    statement = statementSave;
    if (isSyntaxCheck) // just in case we later add parameter options to this
      return "";

    // if not just drawing check to see if there is already a plot of this type

    if (makeNewFrame) {
      stateScript += "plot " + type;
      int ptDataFrame = viewer.getJmolDataFrameIndex(modelIndex, stateScript);
      if (ptDataFrame > 0 && tokCmd != Token.write && tokCmd != Token.show) {
        // no -- this is that way we switch frames. viewer.deleteAtoms(viewer.getModelUndeletedAtomsBitSet(ptDataFrame), true);
        // data frame can't be 0.
        viewer.setCurrentModelIndex(ptDataFrame, true);
        // BitSet bs2 = viewer.getModelAtomBitSet(ptDataFrame);
        // bs2.and(bs);
        // need to be able to set data directly as well.
        // viewer.display(BitSetUtil.setAll(viewer.getAtomCount()), bs2, tQuiet);
        return "";
      }
    }

    // prepare data for property plotting

    float[] dataX = null, dataY = null, dataZ = null;
    Point3f factors = new Point3f(1, 1, 1);
    if (plotType == JmolConstants.JMOL_DATA_OTHER) {
      dataX = getBitsetPropertyFloat(bs, propertyX | Token.selectedfloat, (minXYZ == null ? Float.NaN
          : minXYZ.x), (maxXYZ == null ? Float.NaN : maxXYZ.x));
      dataY = getBitsetPropertyFloat(bs, propertyY | Token.selectedfloat, (minXYZ == null ? Float.NaN
          : minXYZ.y), (maxXYZ == null ? Float.NaN : maxXYZ.y));
      if (propertyZ != 0)
        dataZ = getBitsetPropertyFloat(bs, propertyZ | Token.selectedfloat,
            (minXYZ == null ? Float.NaN : minXYZ.z),
            (maxXYZ == null ? Float.NaN : maxXYZ.z));
      if (minXYZ == null)
        minXYZ = new Point3f(getMinMax(dataX, false, propertyX), getMinMax(
            dataY, false, propertyY), getMinMax(dataZ, false, propertyZ));
      if (maxXYZ == null)
        maxXYZ = new Point3f(getMinMax(dataX, true, propertyX), getMinMax(
            dataY, true, propertyY), getMinMax(dataZ, true, propertyZ));
      Logger.info("plot min/max: " + minXYZ + " " + maxXYZ);
      Point3f center = new Point3f(maxXYZ);
      center.add(minXYZ);
      center.scale(0.5f);
      factors.set(maxXYZ);
      factors.sub(minXYZ);
      factors.set(factors.x / 200, factors.y / 200, factors.z / 200);
      if (Token.tokAttr(propertyX, Token.intproperty)) {
        factors.x = 1;
        center.x = 0;
      } else if (factors.x > 0.1 && factors.x <= 10) {
        factors.x = 1;
      }
      if (Token.tokAttr(propertyY, Token.intproperty)) {
        factors.y = 1;
        center.y = 0;
      } else if (factors.y > 0.1 && factors.y <= 10) {
        factors.y = 1;
      }
      if (Token.tokAttr(propertyZ, Token.intproperty)) {
        factors.z = 1;
        center.z = 0;
      } else if (factors.z > 0.1 && factors.z <= 10) {
        factors.z = 1;
      }
      if (propertyZ == 0)
        center.z = minXYZ.z = maxXYZ.z = factors.z = 0;
      for (int i = 0; i < dataX.length; i++)
        dataX[i] = (dataX[i] - center.x) / factors.x;
      for (int i = 0; i < dataY.length; i++)
        dataY[i] = (dataY[i] - center.y) / factors.y;
      if (propertyZ != 0)
        for (int i = 0; i < dataZ.length; i++)
          dataZ[i] = (dataZ[i] - center.z) / factors.z;
      parameters = new Object[] { bs, dataX, dataY, dataZ, minXYZ, maxXYZ,
          factors, center };
    }

    // all set...

    if (tokCmd == Token.write)
      return viewer.streamFileData(filename, "PLOT", type, modelIndex,
          parameters);
    String data = viewer.getPdbData(modelIndex, type, parameters);
    if (tokCmd == Token.show)
      return data;

    if (Logger.debugging)
      Logger.info(data);

    if (tokCmd == Token.draw) {
      runScript(data);
      return "";
    }

    // create the new model

    String[] savedFileInfo = viewer.getFileInfo();
    boolean oldAppendNew = viewer.getAppendNew();
    viewer.setAppendNew(true);
    boolean isOK = (data != null && viewer.loadInline(data, true) == null);
    viewer.setAppendNew(oldAppendNew);
    viewer.setFileInfo(savedFileInfo);
    if (!isOK)
      return "";
    int modelCount = viewer.getModelCount();
    viewer.setJmolDataFrame(stateScript, modelIndex, modelCount - 1);
    if (plotType != JmolConstants.JMOL_DATA_OTHER)
      stateScript += ";\n" + preSelected;
    StateScript ss = viewer.addStateScript(stateScript, true, false);

    // get post-processing script

    String script;
    switch (plotType) {
    case -1:
      viewer.setFrameTitle(modelCount - 1, type + " plot for model "
          + viewer.getModelNumberDotted(modelIndex));
      float f = 3;
      script = "frame 0.0; frame last; reset;" + "select visible; spacefill "
          + f + "; wireframe 0;" + "draw plotAxisX" + modelCount
          + " {100 -100 -100} {-100 -100 -100} \"" + Token.nameOf(propertyX)
          + "\";" + "draw plotAxisY" + modelCount
          + " {-100 100 -100} {-100 -100 -100} \"" + Token.nameOf(propertyY)
          + "\";";
      if (propertyZ != 0)
        script += "draw plotAxisZ" + modelCount
            + " {-100 -100 100} {-100 -100 -100} \"" + Token.nameOf(propertyZ)
            + "\";";
      break;
    case JmolConstants.JMOL_DATA_RAMACHANDRAN:
    default:
      viewer.setFrameTitle(modelCount - 1, "ramachandran plot for model "
          + viewer.getModelNumberDotted(modelIndex));
      script = "frame 0.0; frame last; reset;"
          + "select visible; color structure; spacefill 3.0; wireframe 0;"
          + "draw ramaAxisX" + modelCount + " {100 0 0} {-100 0 0} \"phi\";"
          + "draw ramaAxisY" + modelCount + " {0 100 0} {0 -100 0} \"psi\";";
      break;
    case JmolConstants.JMOL_DATA_QUATERNION:
      viewer.setFrameTitle(modelCount - 1, type.replace('w', ' ') + qFrame
          + " for model " + viewer.getModelNumberDotted(modelIndex));
      String color = (Graphics3D
          .getHexCode(viewer.getColixBackgroundContrast()));
      script = "frame 0.0; frame last; reset;"
          + "select visible; wireframe 0; spacefill 3.0; "
          + "isosurface quatSphere" + modelCount + " color " + color
          + " sphere 100.0 mesh nofill frontonly translucent 0.8;"
          + "draw quatAxis" + modelCount
          + "X {100 0 0} {-100 0 0} color red \"x\";" + "draw quatAxis"
          + modelCount + "Y {0 100 0} {0 -100 0} color green \"y\";"
          + "draw quatAxis" + modelCount
          + "Z {0 0 100} {0 0 -100} color blue \"z\";" + "color structure;"
          + "draw quatCenter" + modelCount + "{0 0 0} scale 0.02;";
      break;
    }

    // run the post-processing script and set rotation radius and display frame title
    runScript(script + preSelected);
    ss.setModelIndex(viewer.getCurrentModelIndex());
    viewer.setRotationRadius(150f, true);
    loadShape(JmolConstants.SHAPE_ECHO);
    showString("frame " + viewer.getModelNumberDotted(modelCount - 1)
        + " created: " + type + (isQuaternion ? qFrame : ""));
    return "";
  }

  private static float getMinMax(float[] data, boolean isMax, int tok) {
    if (data == null)
      return 0;
    switch (tok) {
    case Token.omega:
    case Token.phi:
    case Token.psi:
      return (isMax ? 180 : -180);
    case Token.eta:
    case Token.theta:
      return (isMax ? 360 : 0);
    case Token.straightness:
      return (isMax ? 1 : -1);
    }
    float fmax = (isMax ? -1E10f : 1E10f);
    for (int i = data.length; --i >= 0;) {
      float f = data[i];
      if (Float.isNaN(f))
        continue;
      if (isMax == (f > fmax))
        fmax = f;
    }
    return fmax;
  }

  private boolean pause() throws ScriptException {
    if (isSyntaxCheck)
      return false;
    String msg = optParameterAsString(1);
    if (!viewer.getBooleanProperty("_useCommandThread")) {
      // showString("Cannot pause thread when _useCommandThread = FALSE: " +
      // msg);
      // return;
    }
    if (viewer.autoExit || !viewer.haveDisplay)
      return false;
    if (scriptLevel == 0 && pc == aatoken.length - 1) {
      viewer.scriptStatus("nothing to pause: " + msg);
      return false;
    }
    msg = (msg.length() == 0 ? ": RESUME to continue." : ": "
        + viewer.formatText(msg));
    pauseExecution(true);
    viewer.scriptStatus("script execution paused" + msg,
        "script paused for RESUME");
    return true;
  }

  private void print() throws ScriptException {
    if (statementLength == 1)
      error(ERROR_badArgumentCount);
    showString(parameterExpressionString(1, 0), true);
  }

  private void prompt() throws ScriptException {
    String msg = null;
    if (statementLength == 1) {
      if (!isSyntaxCheck)
        msg = getScriptContext().getContextTrace(null, true).toString();
    } else {
        msg = parameterExpressionString(1, 0);
    }
    if (!isSyntaxCheck)
      viewer.prompt(msg, "OK", null, true);
  }

  private void refresh() {
    if (isSyntaxCheck)
      return;
    viewer.setTainted(true);
    viewer.requestRepaintAndWait();
  }

  private void reset() throws ScriptException {
    checkLength(-2);
    if (isSyntaxCheck)
      return;
    if (statementLength == 1) {
      viewer.reset(false);
      return;
    }
    // possibly "all"
    switch (tokAt(1)) {
    case Token.error:
      viewer.resetError();
      return;
    case Token.function:
      viewer.clearFunctions();
      return;
    case Token.vanderwaals:
      viewer.setData("element_vdw", new Object[] { null, "" }, 0, 0, 0, 0, 0);
      return;
    case Token.aromatic:
      viewer.resetAromatic();
      return;
    case Token.spin:
      viewer.reset(true);
      return;
   }
    String var = parameterAsString(1);
    if (var.charAt(0) == '_')
      error(ERROR_invalidArgument);
    viewer.unsetProperty(var);
  }

  private void restrict() throws ScriptException {
    boolean isBond = (tokAt(1) == Token.bonds);
    select(isBond ? 2 : 1);
    restrictSelected(isBond, true);
  }

  private void restrictSelected(boolean isBond, boolean doInvert) {
    if (isSyntaxCheck)
      return;
    BitSet bsSelected = BitSetUtil.copy(viewer.getSelectionSet(true));
    if (doInvert) {
      viewer.invertSelection();
      BitSet bsSubset = viewer.getSelectionSubset();
      if (bsSubset != null) {
        bsSelected = BitSetUtil.copy(viewer.getSelectionSet(true));
        bsSelected.and(bsSubset);
        viewer.select(bsSelected, true);
        BitSetUtil.invertInPlace(bsSelected, viewer.getAtomCount());
        bsSelected.and(bsSubset);
      }
    }
    BitSetUtil.andNot(bsSelected, viewer.getDeletedAtoms());
    boolean bondmode = viewer.getBondSelectionModeOr();

    if (!isBond)
      setBooleanProperty("bondModeOr", true);
    setShapeSize(JmolConstants.SHAPE_STICKS, 0, null);
    // wireframe will not operate on STRUTS even though they are
    // a form of bond order (see BondIteratoSelected)
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_STRUT));
    setShapeSize(JmolConstants.SHAPE_STICKS, 0, null);
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_COVALENT_MASK));
    // also need to turn off backbones, ribbons, strands, cartoons
    for (int shapeType = JmolConstants.SHAPE_MAX_SIZE_ZERO_ON_RESTRICT; --shapeType >= 0;)
      if (shapeType != JmolConstants.SHAPE_MEASURES)
        setShapeSize(shapeType, 0, null);
    setShapeProperty(JmolConstants.SHAPE_POLYHEDRA, "delete", null);
    shapeManager.setLabel(null, viewer.getSelectionSet(true));

    if (!isBond)
      setBooleanProperty("bondModeOr", bondmode);
    viewer.select(bsSelected, true);
  }

  private void rotate(boolean isSpin, boolean isSelected)
      throws ScriptException {

    // rotate is a full replacement for spin
    // spin is DEPRECATED

    /*
     * The Chime spin method:
     *
     * set spin x 10;set spin y 30; set spin z 10; spin | spin ON spin OFF
     *
     * Jmol does these "first x, then y, then z" I don't know what Chime does.
     *
     * spin and rotate are now consolidated here.
     *
     * far simpler is
     *
     * spin x 10 spin y 10
     *
     * these are pure x or y spins or
     *
     * spin axisangle {1 1 0} 10
     *
     * this is the same as the old "spin x 10; spin y 10" -- or is it? anyway,
     * it's better!
     *
     * note that there are many defaults
     *
     * spin # defaults to spin y 10 spin 10 # defaults to spin y 10 spin x #
     * defaults to spin x 10
     *
     * and several new options
     *
     * spin -x spin axisangle {1 1 0} 10 spin 10 (atomno=1)(atomno=2) spin 20 {0
     * 0 0} {1 1 1}
     *
     * spin MOLECULAR {0 0 0} 20
     *
     * The MOLECULAR keyword indicates that spins or rotations are to be carried
     * out in the internal molecular coordinate frame, not the fixed room frame.
     *
     * In the case of rotateSelected, all rotations are molecular and the
     * absense of the MOLECULAR keyword indicates to rotate about the geometric
     * center of the molecule, not {0 0 0}
     *
     * Fractional coordinates may be indicated:
     *
     * spin 20 {0 0 0/} {1 1 1/}
     *
     * In association with this, TransformManager and associated functions are
     * TOTALLY REWRITTEN and consolideated. It is VERY clean now - just two
     * methods here -- one fixed and one molecular, two in Viewer, and two in
     * TransformManager. All the centering stuff has been carefully inspected
     * are reorganized as well.
     *
     * Bob Hanson 5/21/06
     */

    if (statementLength == 2)
      switch (getToken(1).tok) {
      case Token.on:
        if (!isSyntaxCheck)
          viewer.setSpinOn(true);
        return;
      case Token.off:
        if (!isSyntaxCheck)
          viewer.setSpinOn(false);
        return;
      }

    BitSet bsAtoms = null;
    float degreesPerSecond = Float.MIN_VALUE;
    int nPoints = 0;
    float endDegrees = Float.MAX_VALUE;
    boolean isMolecular = false;
    boolean haveRotation = false;
    List ptsA = null;
    Point3f[] points = new Point3f[2];
    Vector3f rotAxis = new Vector3f(0, 1, 0);
    Vector3f translation = null;
    Matrix4f m4 = null;
    Matrix3f m3 = null;
    int direction = 1;
    int tok;
    Quaternion q = null;
    boolean helicalPath = false;
    List ptsB = null;
    BitSet bsCompare = null;
    Point3f invPoint = null;
    Point4f invPlane = null;
    boolean axesOrientationRasmol = viewer.getAxesOrientationRasmol();
    for (int i = 1; i < statementLength; ++i) {
      switch (tok = getToken(i).tok) {
      case Token.bitset:
      case Token.expressionBegin:
      case Token.leftbrace:
      case Token.point3f:
      case Token.dollarsign:
        if (tok == Token.bitset || tok == Token.expressionBegin) {
          if (translation != null || q != null || nPoints == 2) {
            bsAtoms = atomExpression(i);
            ptsB = null;
            isSelected = true;
            break;
          }
        }
        haveRotation = true;
        if (nPoints == 2)
          nPoints = 0;
        // {X, Y, Z}
        // $drawObject[n]
        Point3f pt1 = centerParameter(i, viewer.getCurrentModelIndex());
        if (!isSyntaxCheck && tok == Token.dollarsign
            && tokAt(i + 2) != Token.leftsquare) {
          // rotation about an axis such as $line1
          isMolecular = true;
          rotAxis = getDrawObjectAxis(objectNameParameter(++i), viewer
              .getCurrentModelIndex());
        }
        points[nPoints++] = pt1;
        break;
      case Token.spin:
        isSpin = true;
        continue;
      case Token.internal:
      case Token.molecular:
        isMolecular = true;
        continue;
      case Token.selected:
        isSelected = true;
        break;
      case Token.comma:
        continue;
      case Token.integer:
      case Token.decimal:
        if (endDegrees == Float.MAX_VALUE) {
          endDegrees = floatParameter(i);
        } else {
          degreesPerSecond = floatParameter(i);
          isSpin = (degreesPerSecond != 0);
        }
        continue;
      case Token.minus:
        direction = -1;
        continue;
      case Token.x:
        haveRotation = true;
        rotAxis.set(direction, 0, 0);
        continue;
      case Token.y:
        haveRotation = true;
        rotAxis.set(0, (axesOrientationRasmol && !isMolecular ? -direction
            : direction), 0);
        continue;
      case Token.z:
        haveRotation = true;
        rotAxis.set(0, 0, direction);
        continue;

        // 11.6 options

      case Token.point4f:
      case Token.quaternion:
        if (tok == Token.quaternion)
          i++;
        haveRotation = true;
        q = getQuaternionParameter(i);
        rotAxis.set(q.getNormal());
        endDegrees = q.getTheta();
        break;
      case Token.axisangle:
        haveRotation = true;
        if (isPoint3f(++i)) {
          rotAxis.set(centerParameter(i));
          break;
        }
        Point4f p4 = getPoint4f(i);
        rotAxis.set(p4.x, p4.y, p4.z);
        endDegrees = p4.w;
        q = new Quaternion(rotAxis, endDegrees);
        break;
      case Token.branch:
        haveRotation = true;
        int iAtom1 = atomExpression(++i).nextSetBit(0);
        int iAtom2 = atomExpression(++iToken).nextSetBit(0);
        if (iAtom1 < 0 || iAtom2 < 0)
          return;
        bsAtoms = viewer.getBranchBitSet(iAtom2, iAtom1);
        isSelected = true;
        isMolecular = true;
        points[0] = viewer.getAtomPoint3f(iAtom1);
        points[1] = viewer.getAtomPoint3f(iAtom2);
        nPoints = 2;
        break;

      // 12.0 options

      case Token.translate:
        translation = new Vector3f(centerParameter(++i));
        isMolecular = isSelected = true;
        break;
      case Token.helix:
        // screw motion, for quaternion-based operations
        helicalPath = true;
        continue;
      case Token.symop:
        int symop = intParameter(++i);
        if (isSyntaxCheck)
          continue;
        Hashtable info = viewer.getSpaceGroupInfo(null);
        Object[] op = (info == null ? null : (Object[]) info.get("operations"));
        if (symop == 0 || op == null || op.length < Math.abs(symop))
          error(ERROR_invalidArgument);
        op = (Object[]) op[Math.abs(symop) - 1];
        translation = (Vector3f) op[5];
        invPoint = (Point3f) op[6];
        points[0] = (Point3f) op[7];
        if (op[8] != null)
          rotAxis = (Vector3f) op[8];
        endDegrees = ((Integer) op[9]).intValue();
        if (symop < 0) {
          endDegrees = -endDegrees;
          if (translation != null)
            translation.scale(-1);
        }
        if (endDegrees == 0 && points[0] != null) {
          // glide plane
          invPlane = Measure.getPlaneThroughPoint(points[0], rotAxis);
        }
        q = new Quaternion(rotAxis, endDegrees);
        nPoints = (points[0] == null ? 0 : 1);
        isMolecular = true;
        haveRotation = true;
        isSelected = true;
        continue;
      case Token.compare:
      case Token.matrix4f:
      case Token.matrix3f:
        haveRotation = true;
        if (tok == Token.compare) {
          bsCompare = atomExpression(++i);
          ptsA = viewer.getAtomPointVector(bsCompare);
          if (ptsA == null)
            error(ERROR_invalidArgument, i);
          i = iToken;
          ptsB = getPointVector(getToken(++i), i);
          if (ptsB == null || ptsA.size() != ptsB.size())
            error(ERROR_invalidArgument, i);
          m4 = new Matrix4f();
          points[0] = new Point3f();
          nPoints = 1;
          float stddev = (isSyntaxCheck ? 0 : Measure.getTransformMatrix4(ptsA,
              ptsB, m4, points[0]));
          // if the standard deviation is very small, we leave ptsB
          // because it will be used to set the absolute final positions
          if (stddev > 0.001)
            ptsB = null;
        } else if (tok == Token.matrix4f) {
          m4 = (Matrix4f) theToken.value;
        }
        m3 = new Matrix3f();
        if (m4 != null) {
          translation = new Vector3f();
          m4.get(translation);
          m4.get(m3);
        } else {
          m3 = (Matrix3f) theToken.value;
        }
        q = (isSyntaxCheck ? new Quaternion() : new Quaternion(m3));
        rotAxis.set(q.getNormal());
        endDegrees = q.getTheta();
        isMolecular = true;
        break;
      default:
        error(ERROR_invalidArgument);
      }
      i = iToken;
    }
    if (isSyntaxCheck)
      return;
    if (isSelected && bsAtoms == null)
      bsAtoms = viewer.getSelectionSet(false);
    if (bsCompare != null) {
      isSelected = true;
      if (bsAtoms == null)
        bsAtoms = bsCompare;
    }
    float rate = (degreesPerSecond == Float.MIN_VALUE ? 10
        : degreesPerSecond < 0 ?
        // -n means number of seconds, not degreesPerSecond
        -endDegrees / degreesPerSecond
            : degreesPerSecond);
    if (q != null) {
      // only when there is a translation (4x4 matrix or TRANSLATE)
      // do we set the rotation to be the center of the selected atoms or model
      if (nPoints == 0 && translation != null)
        points[0] = viewer.getAtomSetCenter(bsAtoms != null ? bsAtoms
            : isSelected ? viewer.getSelectionSet(false) : viewer
                .getModelUndeletedAtomsBitSet(-1));
      if (helicalPath && translation != null) {
        points[1] = new Point3f(points[0]);
        points[1].add(translation);
        Object[] ret = (Object[]) Measure.computeHelicalAxis(null, Token.array,
            points[0], points[1], q);
        points[0] = (Point3f) ret[0];
        float theta = ((Point3f) ret[3]).x;
        if (theta != 0) {
          translation = (Vector3f) ret[1];
          rotAxis = new Vector3f(translation);
          if (theta < 0)
            rotAxis.scale(-1);
        }
        m4 = null;
      }
      if (isSpin && m4 == null)
        m4 = ScriptMathProcessor.getMatrix4f(q.getMatrix(), translation);
      if (points[0] != null)
        nPoints = 1;
    }
    if (invPoint != null) {
      viewer.invertAtomCoord(invPoint, bsAtoms);
      if (rotAxis == null)
        return;
    }
    if (invPlane != null) {
      viewer.invertAtomCoord(invPlane, bsAtoms);
      if (rotAxis == null)
        return;
    }
    if (nPoints < 2) {
      if (!isMolecular) {
        // fixed-frame rotation
        // rotate x 10 # Chime-like
        // rotate axisangle {0 1 0} 10
        // rotate x 10 (atoms) # point-centered
        // rotate x 10 $object # point-centered
        viewer.rotateAxisAngleAtCenter(points[0], rotAxis, rate, endDegrees,
            isSpin, bsAtoms);
        return;
      }
      if (nPoints == 0)
        points[0] = new Point3f();
      // rotate MOLECULAR
      // rotate MOLECULAR (atom1)
      // rotate MOLECULAR x 10 (atom1)
      // rotate axisangle MOLECULAR (atom1)
      points[1] = new Point3f(points[0]);
      points[1].add(rotAxis);
      nPoints = 2;
    }
    if (nPoints == 0)
      points[0] = new Point3f();
    if (nPoints < 2 || points[0].distance(points[1]) == 0) {
      points[1] = new Point3f(points[0]);
      points[1].y += 1.0;
    }
    if (endDegrees == Float.MAX_VALUE)
      endDegrees = 0;
    if (endDegrees != 0 && translation != null && !haveRotation)
      translation.scale(endDegrees / translation.length());
    if (isSpin && translation != null
        && (endDegrees == 0 || degreesPerSecond == 0)) {
      // need a token rotation
      endDegrees = 0.01f;
      rate = (degreesPerSecond == Float.MIN_VALUE ? 0.01f
          : degreesPerSecond < 0 ?
          // -n means number of seconds, not degreesPerSecond
          -endDegrees / degreesPerSecond
              : degreesPerSecond * 0.01f / translation.length());
      degreesPerSecond = 0.01f;
    }
    if (bsAtoms != null && isSpin && ptsB == null && m4 != null) {
      ptsA = viewer.getAtomPointVector(bsAtoms);
      ptsB = Measure.transformPoints(ptsA, m4, points[0]);
    }
    if (bsAtoms != null && !isSpin && ptsB != null)
      viewer.setAtomCoord(bsAtoms, Token.xyz, ptsB);
    else
      viewer.rotateAboutPointsInternal(points[0], points[1], rate, endDegrees,
          isSpin, bsAtoms, translation, ptsB);
  }

  private Quaternion getQuaternionParameter(int i) throws ScriptException {
    if (tokAt(i) == Token.varray) {
      List sv = ((ScriptVariable) getToken(i)).getList();
      Point4f p4 = null;
      if (sv.size() == 0
          || (p4 = ScriptVariable.pt4Value((ScriptVariable)sv.get(0))) == null)
        error(ERROR_invalidArgument);
      return new Quaternion(p4);
    }
    return new Quaternion(getPoint4f(i));
  }

  List getPointVector(Token t, int i) throws ScriptException {
    switch (t.tok) {
    case Token.bitset:
      return viewer.getAtomPointVector((BitSet) t.value);
    case Token.varray:
      List data = new ArrayList();
      Point3f pt;
      List pts = ((ScriptVariable) t).getList();
      for (int j = 0; j < pts.size(); j++)
        if ((pt = ScriptVariable.ptValue((ScriptVariable)pts.get(j))) != null)
          data.add(pt);
        else
          return null;
      return data;
    }
    if (i > 0)
      return viewer.getAtomPointVector(atomExpression(i));
    return null;
  }

  private Point3f getObjectCenter(String axisID, int index, int modelIndex) {
    Object[] data = new Object[] { axisID, new Integer(index),
        new Integer(modelIndex) };
    return (getShapeProperty(JmolConstants.SHAPE_DRAW, "getCenter", data)
        || getShapeProperty(JmolConstants.SHAPE_ISOSURFACE, "getCenter", data) ? (Point3f) data[2]
        : null);
  }

  private Point3f[] getObjectBoundingBox(String id) {
    Object[] data = new Object[] { id, null, null };
    return (getShapeProperty(JmolConstants.SHAPE_ISOSURFACE, "getBoundingBox",
        data) ? (Point3f[]) data[2] : null);
  }

  private Vector3f getDrawObjectAxis(String axisID, int index) {
    Object[] data = new Object[] { axisID, new Integer(index), null };
    return (getShapeProperty(JmolConstants.SHAPE_DRAW, "getSpinAxis", data) ? (Vector3f) data[2]
        : null);
  }

  private void script(int tok, String filename) throws ScriptException {
    boolean loadCheck = true;
    boolean isCheck = false;
    boolean doStep = false;
    int lineNumber = 0;
    int pc = 0;
    int lineEnd = 0;
    int pcEnd = 0;
    int i = 2;
    String theScript = null;
    String localPath = null;
    String remotePath = null;
    String scriptPath = null;
    if (tok == Token.javascript) {
      checkLength(2);
      if (!isSyntaxCheck)
        viewer.jsEval(parameterAsString(1));
      return;
    }
    if (filename == null) {
      tok = tokAt(1);
      if (tok != Token.string)
        error(ERROR_filenameExpected);
      filename = parameterAsString(1);
      if (filename.equalsIgnoreCase("applet")) {
        // script APPLET x "....."
        String appID = parameterAsString(2);
        theScript = parameterExpressionString(3, 0);
        checkLast(iToken);
        if (isSyntaxCheck)
          return;
        if (appID.length() == 0 || appID.equals("all"))
          appID = "*";
        if (!appID.equals(".")) {
          viewer.jsEval(appID + "\1" + theScript);
          if (!appID.equals("*"))
            return;
        }
      } else {
        tok = tokAt(statementLength - 1);
        doStep = (tok == Token.step);
        if (filename.equalsIgnoreCase("inline")) {
          theScript = parameterExpressionString(2,(doStep ? statementLength - 1 : 0));
          i = iToken + 1;
        }
        while (filename.equalsIgnoreCase("localPath")
            || filename.equalsIgnoreCase("remotePath")
            || filename.equalsIgnoreCase("scriptPath")) {
          if (filename.equalsIgnoreCase("localPath"))
            localPath = parameterAsString(i++);
          else if (filename.equalsIgnoreCase("scriptPath"))
            scriptPath = parameterAsString(i++);
          else
            remotePath = parameterAsString(i++);
          filename = parameterAsString(i++);
        }
        if ((tok = tokAt(i)) == Token.check) {
          isCheck = true;
          tok = tokAt(++i);
        }
        if (tok == Token.noload) {
          loadCheck = false;
          tok = tokAt(++i);
        }
        if (tok == Token.line || tok == Token.lines) {
          i++;
          lineEnd = lineNumber = Math.max(intParameter(i++), 0);
          if (checkToken(i)) {
            if (getToken(i).tok == Token.minus)
              lineEnd = (checkToken(++i) ? intParameter(i++) : 0);
            else
              lineEnd = -intParameter(i++);
            if (lineEnd <= 0)
              error(ERROR_invalidArgument);
          }
        } else if (tok == Token.command || tok == Token.commands) {
          i++;
          pc = Math.max(intParameter(i++) - 1, 0);
          pcEnd = pc + 1;
          if (checkToken(i)) {
            if (getToken(i).tok == Token.minus)
              pcEnd = (checkToken(++i) ? intParameter(i++) : 0);
            else
              pcEnd = -intParameter(i++);
            if (pcEnd <= 0)
              error(ERROR_invalidArgument);
          }
        }
        checkLength(doStep ? i + 1 : i);
      }
    }

    // processing

    if (isSyntaxCheck && !isCmdLine_c_or_C_Option)
      return;
    if (isCmdLine_c_or_C_Option)
      isCheck = true;
    boolean wasSyntaxCheck = isSyntaxCheck;
    boolean wasScriptCheck = isCmdLine_c_or_C_Option;
    if (isCheck)
      isSyntaxCheck = isCmdLine_c_or_C_Option = true;
    pushContext(null);
    contextPath += " >> " + filename;
    if (theScript == null ? compileScriptFileInternal(filename, localPath,
        remotePath, scriptPath) : compileScript(null, theScript, false)) {
      this.pcEnd = pcEnd;
      this.lineEnd = lineEnd;
      while (pc < lineNumbers.length && lineNumbers[pc] < lineNumber)
        pc++;
      this.pc = pc;
      boolean saveLoadCheck = isCmdLine_C_Option;
      isCmdLine_C_Option &= loadCheck;
      executionStepping |= doStep;
      instructionDispatchLoop(isCheck);
      if (debugScript && viewer.getMessageStyleChime())
        viewer.scriptStatus("script <exiting>");
      isCmdLine_C_Option = saveLoadCheck;
      popContext(false, false);
    } else {
      Logger.error(GT._("script ERROR: ") + errorMessage);
      popContext(false, false);
      if (wasScriptCheck) {
        setErrorMessage(null);
      } else {
        evalError(null, null);
      }
    }

    isSyntaxCheck = wasSyntaxCheck;
    isCmdLine_c_or_C_Option = wasScriptCheck;
  }

  private void function() throws ScriptException {
    if (isSyntaxCheck && !isCmdLine_c_or_C_Option)
      return;
    String name = (String) getToken(0).value;
    if (!viewer.isFunction(name))
      error(ERROR_commandExpected);
    List params = (statementLength == 1 || statementLength == 3
        && tokAt(1) == Token.leftparen && tokAt(2) == Token.rightparen ? null
        : parameterExpressionList(1, false));
    if (isSyntaxCheck)
      return;
    runFunction(null, name, params, null, false);
  }

  private void sync() throws ScriptException {
    // new 11.3.9
    checkLength(-3);
    String text = "";
    String applet = "";
    switch (statementLength) {
    case 1:
      applet = "*";
      text = "ON";
      break;
    case 2:
      applet = parameterAsString(1);
      if (applet.indexOf("jmolApplet") == 0 || Parser.isOneOf(applet, "*;.;^")) {
        text = "ON";
        if (!isSyntaxCheck)
          viewer.syncScript(text, applet);
        applet = ".";
        break;
      }
      text = applet;
      applet = "*";
      break;
    case 3:
      applet = parameterAsString(1);
      text = (tokAt(2) == Token.stereo ? Viewer.SYNC_GRAPHICS_MESSAGE
          : parameterAsString(2));
      break;
    }
    if (isSyntaxCheck)
      return;
    viewer.syncScript(text, applet);
  }

  private void history(int pt) throws ScriptException {
    // history or set history
    if (statementLength == 1) {
      // show it
      showString(viewer.getSetHistory(Integer.MAX_VALUE));
      return;
    }
    if (pt == 2) {
      // set history n; n' = -2 - n; if n=0, then set history OFF
      int n = intParameter(checkLast(2));
      if (n < 0)
        error(ERROR_invalidArgument);
      if (!isSyntaxCheck)
        viewer.getSetHistory(n == 0 ? 0 : -2 - n);
      return;
    }
    switch (getToken(checkLast(1)).tok) {
    // pt = 1 history ON/OFF/CLEAR
    case Token.on:
    case Token.clear:
      if (!isSyntaxCheck)
        viewer.getSetHistory(Integer.MIN_VALUE);
      return;
    case Token.off:
      if (!isSyntaxCheck)
        viewer.getSetHistory(0);
      break;
    default:
      error(ERROR_keywordExpected, "ON, OFF, CLEAR");
    }
  }

  private void display(boolean isDisplay) throws ScriptException {
    if (tokAt(1) == Token.dollarsign) {
      setObjectProperty();
      return;
    }
    BitSet bs = (statementLength == 1 ? null : atomExpression(1));
    if (isSyntaxCheck)
      return;
    if (isDisplay)
      viewer.display(bs, tQuiet);
    else
      viewer.hide(bs, tQuiet);
  }

  private void delete() throws ScriptException {
    if (statementLength == 1) {
      zap(true);
      return;
    }
    if (tokAt(1) == Token.dollarsign) {
      setObjectProperty();
      return;
    }
    BitSet bs = atomExpression(statement, 1, 0, true, false, true, false);
    if (isSyntaxCheck)
      return;
    int nDeleted = viewer.deleteAtoms(bs, false);
    if (!(tQuiet || scriptLevel > scriptReportingLevel))
      scriptStatusOrBuffer(GT._("{0} atoms deleted", nDeleted));
  }

  private void minimize() throws ScriptException {
    BitSet bsSelected = null;
    int steps = Integer.MAX_VALUE;
    float crit = 0;
    boolean addHydrogen = false;
    boolean isSilent = false;
    BitSet bsFixed = null;
    MinimizerInterface minimizer = viewer.getMinimizer(false);
    // may be null
    for (int i = 1; i < statementLength; i++)
      switch (getToken(i).tok) {
      case Token.addhydrogens:
        addHydrogen = true;
        continue;
      case Token.cancel:
      case Token.stop:
        checkLength(2);
        if (isSyntaxCheck || minimizer == null)
          return;
        minimizer.setProperty(parameterAsString(i), null);
        return;
      case Token.clear:
        checkLength(2);
        if (isSyntaxCheck || minimizer == null)
          return;
        minimizer.setProperty("clear", null);
        return;
      case Token.constraint:
        if (i != 1)
          error(ERROR_invalidArgument);
        int n = 0;
        float targetValue = 0;
        int[] aList = new int[5];
        if (tokAt(++i) == Token.clear) {
          checkLength(2);
        } else {
          while (n < 4 && !isFloatParameter(i)) {
            aList[++n] = atomExpression(i).nextSetBit(0);
            i = iToken + 1;
          }
          aList[0] = n;
          if (n == 1)
            error(ERROR_invalidArgument);
          targetValue = floatParameter(checkLast(i));
        }
        if (!isSyntaxCheck)
          viewer.getMinimizer(true).setProperty("constraint",
              new Object[] { aList, new int[n], new Float(targetValue) });
        return;
      case Token.criterion:
        crit = floatParameter(++i);
        continue;
      case Token.energy:
        steps = 0;
        continue;
      case Token.fixed:
        if (i != 1)
          error(ERROR_invalidArgument);
        bsFixed = atomExpression(++i);
        if (bsFixed.nextSetBit(0) < 0)
          bsFixed = null;
        i = iToken;
        if (!isSyntaxCheck)
          viewer.getMinimizer(true).setProperty("fixed", bsFixed);
        if (i + 1 == statementLength)
          return;
        continue;
      case Token.select:
        bsSelected = atomExpression(++i);
        i = iToken;
        continue;
      case Token.silent:
        isSilent = true;
        break;
      case Token.step:
      case Token.steps:
        steps = intParameter(++i);
        continue;
      default:
        error(ERROR_invalidArgument);
        break;
      }
    if (!isSyntaxCheck)
      viewer.minimize(steps, crit, bsSelected, bsFixed, 0, addHydrogen,
          isSilent, false);
  }

  private void select(int i) throws ScriptException {
    // NOTE this is called by restrict()
    if (statementLength == 1) {
      viewer.select(null, tQuiet || scriptLevel > scriptReportingLevel);
      return;
    }
    if (statementLength == 2 && tokAt(1) == Token.only)
      return; // coming from "cartoon only"
    // select beginexpr none endexpr
    viewer.setNoneSelected(statementLength == 4 && tokAt(2) == Token.none);
    // select beginexpr bonds ( {...} ) endex pr
    if (tokAt(2) == Token.bitset && getToken(2).value instanceof BondSet
        || getToken(2).tok == Token.bonds && getToken(3).tok == Token.bitset) {
      if (statementLength == iToken + 2) {
        if (!isSyntaxCheck)
          viewer.selectBonds((BitSet) theToken.value);
        return;
      }
      error(ERROR_invalidArgument);
    }
    if (getToken(2).tok == Token.measure) {
      if (statementLength == 5 && getToken(3).tok == Token.bitset) {
        if (!isSyntaxCheck)
          setShapeProperty(JmolConstants.SHAPE_MEASURES, "select",
              theToken.value);
        return;
      }
      error(ERROR_invalidArgument);
    }
    BitSet bs = null;
    if (getToken(1).intValue == 0) {
      Object v = parameterExpressionToken(0).value;
      if (!(v instanceof BitSet))
        error(ERROR_invalidArgument);
      checkLast(iToken);
      bs = (BitSet) v;
    } else {
      bs = atomExpression(i);
    }
    if (isSyntaxCheck)
      return;
    if (isBondSet) {
      viewer.selectBonds(bs);
    } else {
      if (bs.length() > viewer.getAtomCount()) {
        BitSet bs1 = viewer.getModelUndeletedAtomsBitSet(-1);
        bs1.and(bs);
        bs = bs1;
      }
      viewer.select(bs, tQuiet || scriptLevel > scriptReportingLevel);
    }
  }

  private void subset() throws ScriptException {
    BitSet bs = (statementLength == 1 ? null : atomExpression(-1));
    if (isSyntaxCheck)
      return;
    // There might have been a reason to have bsSubset being set BEFORE
    // checking syntax checking, but I can't remember why.
    // will leave it this way for now. Might cause some problems with script
    // checking.
    viewer.setSelectionSubset(bs);
    // I guess we do not want to select, because that could
    // throw off picking in a strange way
    // viewer.select(bsSubset, false);
  }

  private void invertSelected() throws ScriptException {
    // invertSelected POINT
    // invertSelected PLANE
    // invertSelected HKL
    // invertSelected STEREO {sp3Atom} {one or two groups)
    Point3f pt = null;
    Point4f plane = null;
    BitSet bs = null;
    int iAtom = Integer.MIN_VALUE;
    switch (tokAt(1)) {
    case Token.nada:
      if (isSyntaxCheck)
        return;
      bs = viewer.getSelectionSet(false);
      pt = viewer.getAtomSetCenter(bs);
      viewer.invertAtomCoord(pt, bs);
      return;
    case Token.stereo:
      iAtom = atomExpression(2).nextSetBit(0);
      // and only these:
      bs = atomExpression(iToken + 1);
      break;
    case Token.point:
      pt = centerParameter(2);
      break;
    case Token.plane:
      plane = planeParameter(2);
      break;
    case Token.hkl:
      plane = hklParameter(2);
      break;
    }
    checkLength(iToken + 1, 1);
    if (plane == null && pt == null && iAtom == Integer.MIN_VALUE)
      error(ERROR_invalidArgument);
    if (isSyntaxCheck)
      return;
    if (iAtom == -1)
      return;
    viewer.invertSelected(pt, plane, iAtom, bs);
  }

  private void translate(boolean isSelected) throws ScriptException {
    // translate [selected] X|Y|Z x.x [NM|ANGSTROMS]
    // translate [selected] X|Y x.x%
    // translate [selected] X|Y|Z x.x [NM|ANGSTROMS]
    // translate [selected] X|Y x.x%
    // translate {x y z} [{atomExpression}]
    BitSet bs = null;
    int i = 1;
    if (tokAt(1) == Token.selected) {
      isSelected = true;
      i++;
    }
    if (isPoint3f(i)) {
      Point3f pt = getPoint3f(i, true);
      bs = (!isSelected && iToken + 1 < statementLength ? atomExpression(++iToken)
          : null);
      checkLast(iToken);
      if (!isSyntaxCheck)
        viewer.setAtomCoordRelative(pt, bs);
      return;
    }
    int xyz = getToken(i).tok;
    if (xyz != Token.x && xyz != Token.y && xyz != Token.z)
      error(ERROR_axisExpected);
    float amount = floatParameter(++i);
    if (amount == 0)
      return;
    char type;
    switch (tokAt(++i)) {
    case Token.nada:
    case Token.bitset:
    case Token.expressionBegin:
      type = '\0';
      break;
    default:
      type = (optParameterAsString(i).toLowerCase() + '\0').charAt(0);
    }
    iToken = (type == '\0' ? 2 : 3);
    bs = (isSelected ? viewer.getSelectionSet(false)
        : iToken + 1 < statementLength ? atomExpression(++iToken) : null);
    checkLast(iToken);
    if (!isSyntaxCheck)
      viewer.translate(parameterAsString(1).charAt(0), amount, type, bs);
  }

  private void zap(boolean isZapCommand) throws ScriptException {
    if (statementLength == 1 || !isZapCommand) {
      viewer.zap(true, isZapCommand && !isStateScript, true);
      refresh();
      return;
    }
    BitSet bs = atomExpression(1);
    if (isSyntaxCheck)
      return;
    int nDeleted = viewer.deleteAtoms(bs, true);
    boolean isQuiet = (tQuiet || scriptLevel > scriptReportingLevel);
    if (!isQuiet)
      scriptStatusOrBuffer(GT._("{0} atoms deleted", nDeleted));
    viewer.select(null, isQuiet);
  }

  private void zoom(boolean isZoomTo) throws ScriptException {
    if (!isZoomTo) {
      // zoom
      // zoom on|off
      int tok = (statementLength > 1 ? getToken(1).tok : Token.on);
      switch (tok) {
      case Token.in:
      case Token.out:
        break;
      case Token.on:
      case Token.off:
        if (statementLength > 2)
          error(ERROR_badArgumentCount);
        if (!isSyntaxCheck)
          setBooleanProperty("zoomEnabled", tok == Token.on);
        return;
      }
    }
    Point3f center = null;
    //Point3f currentCenter = viewer.getRotationCenter();
    int i = 1;
    // zoomTo time-sec
    float time = (isZoomTo ? (isFloatParameter(i) ? floatParameter(i++) : 2f)
        : 0f);
    if (time < 0) {
      // zoom -10
      i--;
      time = 0;
    }
    // zoom {x y z} or (atomno=3)
    int ptCenter = 0;
    BitSet bsCenter = null;
    if (isCenterParameter(i)) {
      ptCenter = i;
      center = centerParameter(i);
      if (expressionResult instanceof BitSet)
        bsCenter = (BitSet) expressionResult;
      i = iToken + 1;
    }

    // disabled sameAtom stuff -- just too weird
    boolean isSameAtom = false;// && (center != null && currentCenter.distance(center) < 0.1);
    // zoom/zoomTo [0|n|+n|-n|*n|/n|IN|OUT]
    // zoom/zoomTo percent|-factor|+factor|*factor|/factor | 0
    float zoom = viewer.getZoomSetting();
    float newZoom = getZoom(i, bsCenter, zoom);
    i = iToken + 1;
    float xTrans = Float.NaN;
    float yTrans = Float.NaN;
    if (i != statementLength) {
      xTrans = floatParameter(i++);
      yTrans = floatParameter(i++);
    }
    if (i != statementLength)
      error(ERROR_invalidArgument);
    if (newZoom < 0) {
      newZoom = -newZoom; // currentFactor
      if (isZoomTo) {
        // no factor -- check for no center (zoom out) or same center (zoom in)
        if (statementLength == 1 || isSameAtom)
          newZoom *= 2;
        else if (center == null)
          newZoom /= 2;
      }
    }
    float max = viewer.getMaxZoomPercent();
    if (newZoom < 5 || newZoom > max)
      numberOutOfRange(5, max);
    if (!viewer.isWindowCentered()) {
      // do a smooth zoom only if not windowCentered
      if (center != null) {
        BitSet bs = atomExpression(ptCenter);
        if (!isSyntaxCheck)
          viewer.setCenterBitSet(bs, false);
      }
      center = viewer.getRotationCenter();
      if (Float.isNaN(xTrans))
        xTrans = viewer.getTranslationXPercent();
      if (Float.isNaN(yTrans))
        yTrans = viewer.getTranslationYPercent();
    }
    if (isSyntaxCheck)
      return;
    if (Float.isNaN(xTrans))
      xTrans = 0;
    if (Float.isNaN(yTrans))
      yTrans = 0;
    if (isSameAtom && Math.abs(zoom - newZoom) < 1)
      time = 0;
    viewer.moveTo(time, center, JmolConstants.center, Float.NaN, null, newZoom,
        xTrans, yTrans, Float.NaN, null, Float.NaN, Float.NaN, Float.NaN);
  }

  private float getZoom(int i, BitSet bs, float currentZoom)
      throws ScriptException {
    // where [zoom factor] is [0|n|+n|-n|*n|/n|IN|OUT]

    float zoom = (isFloatParameter(i) ? floatParameter(i++) : Float.NaN);
    if (zoom == 0 || currentZoom == 0) {
      // moveTo/zoom/zoomTo {center} 0
      if (bs == null)
        error(ERROR_invalidArgument);
      float r = viewer.calcRotationRadius(bs);
      currentZoom = viewer.getRotationRadius() / r * 100;
      zoom = Float.NaN;
    }
    if (zoom < 0) {
      // moveTo/zoom/zoomTo -factor
      zoom += currentZoom;
    } else if (Float.isNaN(zoom)) {
      // moveTo/zoom/zoomTo [optional {center}] percent|+factor|*factor|/factor
      // moveTo/zoom/zoomTo {center} 0 [optional
      // -factor|+factor|*factor|/factor]
      int tok = tokAt(i);
      switch (tok) {
      case Token.out:
      case Token.in:
        zoom = currentZoom * (tok == Token.out ? 0.5f : 2f);
        i++;
        break;
      case Token.divide:
      case Token.times:
      case Token.plus:
        float value = floatParameter(++i);
        i++;
        switch (tok) {
        case Token.divide:
          zoom = currentZoom / value;
          break;
        case Token.times:
          zoom = currentZoom * value;
          break;
        case Token.plus:
          zoom = currentZoom + value;
          break;
        }
        break;
      default:
        // indicate no factor indicated
        zoom = (bs == null ? -currentZoom : currentZoom);
      }
    }
    iToken = i - 1;
    return zoom;
  }

  private void delay() throws ScriptException {
    long millis = 0;
    switch (getToken(1).tok) {
    case Token.on: // this is auto-provided as a default
      millis = 1;
      break;
    case Token.integer:
      millis = intParameter(1) * 1000;
      break;
    case Token.decimal:
      millis = (long) (floatParameter(1) * 1000);
      break;
    default:
      error(ERROR_numberExpected);
    }
    if (!isSyntaxCheck)
      delay(millis);
  }

  private void delay(long millis) {
    long timeBegin = System.currentTimeMillis();
    refresh();
    int delayMax;
    if (millis < 0)
      millis = -millis;
    else if ((delayMax = viewer.getDelayMaximum()) > 0 && millis > delayMax)
      millis = delayMax;
    millis -= System.currentTimeMillis() - timeBegin;
    int seconds = (int) millis / 1000;
    millis -= seconds * 1000;
    if (millis <= 0)
      millis = 1;
    while (seconds >= 0 && millis > 0 && !interruptExecution
        && currentThread == Thread.currentThread()) {
      viewer.popHoldRepaint("delay");
      try {
        Thread.sleep((seconds--) > 0 ? 1000 : millis);
      } catch (InterruptedException e) {
      }
      viewer.pushHoldRepaint("delay");
    }
  }

  private void slab(boolean isDepth) throws ScriptException {
    boolean TF = false;
    Point4f plane = null;
    String str;
    if (isCenterParameter(1) || tokAt(1) == Token.point4f)
      plane = planeParameter(1);
    else
      switch (getToken(1).tok) {
      case Token.integer:
        int percent = intParameter(checkLast(1));
        if (!isSyntaxCheck)
          if (isDepth)
            viewer.depthToPercent(percent);
          else
            viewer.slabToPercent(percent);
        return;
      case Token.on:
        checkLength(2);
        TF = true;
        // fall through
      case Token.off:
        checkLength(2);
        setBooleanProperty("slabEnabled", TF);
        return;
      case Token.reset:
        checkLength(2);
        if (isSyntaxCheck)
          return;
        viewer.slabReset();
        setBooleanProperty("slabEnabled", true);
        return;
      case Token.set:
        checkLength(2);
        if (isSyntaxCheck)
          return;
        viewer.setSlabDepthInternal(isDepth);
        setBooleanProperty("slabEnabled", true);
        return;
      case Token.minus:
        str = parameterAsString(2);
        if (str.equalsIgnoreCase("hkl"))
          plane = hklParameter(3);
        else if (str.equalsIgnoreCase("plane"))
          plane = planeParameter(3);
        if (plane == null)
          error(ERROR_invalidArgument);
        plane.scale(-1);
        break;
      case Token.plane:
        switch (getToken(2).tok) {
        case Token.none:
          break;
        default:
          plane = planeParameter(2);
        }
        break;
      case Token.hkl:
        plane = (getToken(2).tok == Token.none ? null : hklParameter(2));
        break;
      case Token.reference:
        // only in 11.2; deprecated
        return;
      default:
        error(ERROR_invalidArgument);
      }
    if (!isSyntaxCheck)
      viewer.slabInternal(plane, isDepth);
  }

  private void ellipsoid() throws ScriptException {
    int mad = 0;
    int i = 1;
    switch (getToken(1).tok) {
    case Token.on:
      mad = 50;
      break;
    case Token.off:
      break;
    case Token.integer:
      mad = intParameter(1);
      break;
    case Token.id:
    case Token.times:
    case Token.identifier:
      loadShape(JmolConstants.SHAPE_ELLIPSOIDS);
      if (theTok == Token.id)
        i++;
      setShapeId(JmolConstants.SHAPE_ELLIPSOIDS, i, false);
      i = iToken;
      while (++i < statementLength) {
        String key = parameterAsString(i);
        Object value = null;
        switch (tokAt(i)) {
        case Token.axes:
          Vector3f[] axes = new Vector3f[3];
          for (int j = 0; j < 3; j++) {
            axes[j] = new Vector3f();
            axes[j].set(centerParameter(++i));
            i = iToken;
          }
          value = axes;
          break;
        case Token.center:
          value = centerParameter(++i);
          i = iToken;
          break;
        case Token.color:
          float translucentLevel = Float.NaN;
          i++;
          if ((theTok = tokAt(i)) == Token.translucent) {
            value = "translucent";
            if (isFloatParameter(++i))
              translucentLevel = getTranslucentLevel(i++);
            else
              translucentLevel = viewer.getDefaultTranslucent();
          } else if (theTok == Token.opaque) {
            value = "opaque";
            i++;
          }
          if (isColorParam(i)) {
            setShapeProperty(JmolConstants.SHAPE_ELLIPSOIDS, "color",
                new Integer(getArgbParam(i)));
            i = iToken;
          }
          if (value == null)
            continue;
          if (!Float.isNaN(translucentLevel))
            setShapeProperty(JmolConstants.SHAPE_ELLIPSOIDS,
                "translucentLevel", new Float(translucentLevel));
          key = "translucency";
          break;
        case Token.delete:
          value = Boolean.TRUE;
          checkLength(3);
          break;
        case Token.modelindex:
          value = new Integer(intParameter(++i));
          break;
        case Token.on:
          value = Boolean.TRUE;
          break;
        case Token.off:
          key = "on";
          value = Boolean.FALSE;
          break;
        case Token.scale:
          value = new Float(floatParameter(++i));
          break;
        }
        if (value == null)
          error(ERROR_invalidArgument);
        setShapeProperty(JmolConstants.SHAPE_ELLIPSOIDS, key.toLowerCase(),
            value);
      }
      setShapeProperty(JmolConstants.SHAPE_ELLIPSOIDS, "thisID", null);
      return;
    default:
      error(ERROR_invalidArgument);
    }
    setShapeSize(JmolConstants.SHAPE_ELLIPSOIDS, mad, null);
  }

  private String getShapeNameParameter(int i) throws ScriptException {
    String id = parameterAsString(i);
    boolean isWild = id.equals("*");
    if (id.length() == 0)
      error(ERROR_invalidArgument);
    if (isWild) {
      switch (tokAt(i + 1)) {
      case Token.nada:
      case Token.on:
      case Token.off:
      case Token.displayed:
      case Token.hidden:
      case Token.color:
      case Token.delete:
        break;
      default:
        if (setMeshDisplayProperty(-1, 0, tokAt(i + 1)))
          break;
        id += optParameterAsString(++i);
      }
    }
    if (tokAt(i + 1) == Token.times)
      id += parameterAsString(++i);
    iToken = i;
    return id;
  }

  private String setShapeId(int iShape, int i, boolean idSeen)
      throws ScriptException {
    if (idSeen)
      error(ERROR_invalidArgument);
    String name = getShapeNameParameter(i).toLowerCase();
    setShapeProperty(iShape, "thisID", name);
    return name;
  }

  private void setAtomShapeSize(int shape, float scale) throws ScriptException {
    // halo star spacefill
    RadiusData rd = null;
    int tok = tokAt(1);
    boolean isOnly = false;
    switch (tok) {
    case Token.only:
      restrictSelected(false, false);
      break;
    case Token.on:
      break;
    case Token.off:
      scale = 0;
      break;
    case Token.decimal:
      isOnly = (floatParameter(1) < 0);
      // fall through
    case Token.integer:
    default:
      rd = encodeRadiusParameter(1, isOnly);
      if (Float.isNaN(rd.value))
        error(ERROR_invalidArgument);
    }
    if (rd == null)
      rd = new RadiusData(scale, RadiusData.TYPE_FACTOR, JmolConstants.VDW_AUTO);
    if (isOnly)
      restrictSelected(false, false);
    setShapeSize(shape, rd);
  }

  /*
   * Based on the form of the parameters, returns and encoded radius as follows:
   *
   * script meaning range
   *
   * +1.2 offset [0 - 10]
   * -1.2 offset 0)
   * 1.2 absolute (0 - 10]
   * -30% 70% (-100 - 0)
   * +30% 130% (0
   * 80% percent (0
   */

  private RadiusData encodeRadiusParameter(int index, boolean isOnly)
      throws ScriptException {

    float value = Float.NaN;
    int type = RadiusData.TYPE_ABSOLUTE;
    int vdwType = 0;

    int tok = getToken(index).tok;
    switch (tok) {
    case Token.adpmax:
    case Token.adpmin:
    case Token.ionic:
    case Token.temperature:
    case Token.vanderwaals:
      value = 1;
      type = RadiusData.TYPE_FACTOR;
      vdwType = (tok == Token.vanderwaals ? 0 : tok);
      tok = tokAt(++index);
      break;
    }
    switch (tok) {
    case Token.reset:
      return viewer.getDefaultRadiusData();
    case Token.auto:
    case Token.rasmol:
    case Token.babel:
    case Token.babel21:
    case Token.jmol:
      value = 1;
      type = RadiusData.TYPE_FACTOR;
      iToken = index - 1;
      break;
    case Token.plus:
    case Token.decimal:
      if (tok == Token.plus) {
        index++;
        type = RadiusData.TYPE_OFFSET;
      } else {
        type = RadiusData.TYPE_ABSOLUTE;
        vdwType = Integer.MAX_VALUE;
      }
      value = floatParameter(index, (isOnly ? -Atom.RADIUS_MAX : 0),
          Atom.RADIUS_MAX);
      if (isOnly)
        value = -value;
      break;
    case Token.integer:
      value = intParameter(index);
      if (tokAt(index + 1) == Token.percent) {
        iToken = ++index;
        type = RadiusData.TYPE_FACTOR;
        if (value < 0 || value > 200)
          integerOutOfRange(0, 200);
        value /= 100;
        break;
      }
      // rasmol 250-scale if positive or percent (again), if negative
      // (deprecated)
      if (value > 749 || value < -200)
        integerOutOfRange(-200, 749);
      if (value > 0) {
        value /= 250;
        type = RadiusData.TYPE_ABSOLUTE;
      } else {
        value /= -100;
        type = RadiusData.TYPE_FACTOR;
      }
      break;
    default:
      if (value == 1)
        index--;
    }
    if (vdwType == 0) {
      vdwType = JmolConstants.getVdwType(optParameterAsString(++iToken));
      if (vdwType < 0) {
        iToken = index;
        vdwType = JmolConstants.getVdwType("auto");
      }
    }
    return new RadiusData(value, type, vdwType);
  }

  private void structure() throws ScriptException {
    byte iType = JmolConstants.getProteinStructureType(parameterAsString(1));
    if (iType < 0)
      error(ERROR_invalidArgument);
    BitSet bs = null;
    switch (tokAt(2)) {
    case Token.bitset:
    case Token.expressionBegin:
      bs = atomExpression(2);
      checkLast(iToken);
      break;
    default:
      checkLength(2);
    }
    if (isSyntaxCheck)
      return;
    clearDefinedVariableAtomSets();
    viewer.setProteinType(iType, bs);
  }

  private void wireframe() throws ScriptException {
    int mad = Integer.MIN_VALUE;
    if (tokAt(1) == Token.reset)
      checkLast(1);
    else
      mad = getMadParameter();
    if (isSyntaxCheck)
      return;
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_COVALENT_MASK));
    setShapeSize(
        JmolConstants.SHAPE_STICKS,
        mad == Integer.MIN_VALUE ? 2 * JmolConstants.DEFAULT_BOND_MILLIANGSTROM_RADIUS
            : mad, null);
  }

  private void ssbond() throws ScriptException {
    int mad = getMadParameter();
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_SULFUR_MASK));
    setShapeSize(JmolConstants.SHAPE_STICKS, mad, null);
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_COVALENT_MASK));
  }

  private void struts() throws ScriptException {
    boolean defOn = (tokAt(1) == Token.only || tokAt(1) == Token.on || statementLength == 1);
    int mad = getMadParameter();
    if (defOn)
      mad = (int) (viewer.getStrutDefaultRadius() * 2000f);
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_STRUT));
    setShapeSize(JmolConstants.SHAPE_STICKS, mad, null);
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_COVALENT_MASK));
  }

  private void hbond(boolean isCommand) throws ScriptException {
    if (statementLength == 2 && getToken(1).tok == Token.calculate) {
      if (isSyntaxCheck)
        return;
      int n = viewer.autoHbond(null, null);
      scriptStatusOrBuffer(GT._("{0} hydrogen bonds", Math.abs(n)));
      return;
    }
    if (statementLength == 2 && getToken(1).tok == Token.delete) {
      if (isSyntaxCheck)
        return;
      connect(0);
      return;
    }
    int mad = getMadParameter();
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_HYDROGEN_MASK));
    setShapeSize(JmolConstants.SHAPE_STICKS, mad, null);
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_COVALENT_MASK));
  }

  private void configuration() throws ScriptException {
    // if (!isSyntaxCheck && viewer.getDisplayModelIndex() <= -2)
    // error(ERROR_backgroundModelError, "\"CONFIGURATION\"");
    BitSet bsAtoms;
    if (statementLength == 1) {
      bsAtoms = viewer.setConformation();
      viewer.addStateScript("select", null, viewer.getSelectionSet(false), null,
          "configuration", true, false);
    } else {
      int n = intParameter(checkLast(1));
      if (isSyntaxCheck)
        return;
      bsAtoms = viewer.getConformation(viewer.getCurrentModelIndex(), n - 1,
          true);
      viewer.addStateScript("configuration " + n + ";", true, false);
    }
    if (isSyntaxCheck)
      return;
    boolean addHbonds = viewer.hasCalculatedHBonds(bsAtoms);
    setShapeProperty(JmolConstants.SHAPE_STICKS, "type", new Integer(
        JmolEdge.BOND_HYDROGEN_MASK));
    setShapeSize(JmolConstants.SHAPE_STICKS, 0, bsAtoms);
    if (addHbonds)
      viewer.autoHbond(bsAtoms, bsAtoms);
    viewer.select(bsAtoms, tQuiet);
  }

  private void vector() throws ScriptException {
    int type = RadiusData.TYPE_SCREEN;
    float value = 1;
    checkLength(-3);
    switch (iToken = statementLength) {
    case 1:
      break;
    case 2:
      switch (getToken(1).tok) {
      case Token.on:
        break;
      case Token.off:
        value = 0;
        break;
      case Token.integer:
        // diameter Pixels
        type = RadiusData.TYPE_SCREEN;
        value = intParameter(1, 0, 19);
        break;
      case Token.decimal:
        // radius angstroms
        type = RadiusData.TYPE_ABSOLUTE;
        value = floatParameter(1, 0, 3);
        break;
      default:
        error(ERROR_booleanOrNumberExpected);
      }
      break;
    case 3:
      if (tokAt(1) == Token.scale) {
        setFloatProperty("vectorScale", floatParameter(2, -100, 100));
        return;
      }
    }
    setShapeSize(JmolConstants.SHAPE_VECTORS, new RadiusData(value, type, 0));
  }

  private void dipole() throws ScriptException {
    // dipole intWidth floatMagnitude OFFSET floatOffset {atom1} {atom2}
    String propertyName = null;
    Object propertyValue = null;
    boolean iHaveAtoms = false;
    boolean iHaveCoord = false;
    boolean idSeen = false;

    loadShape(JmolConstants.SHAPE_DIPOLES);
    if (tokAt(1) == Token.list && listIsosurface(JmolConstants.SHAPE_DIPOLES))
      return;
    setShapeProperty(JmolConstants.SHAPE_DIPOLES, "init", null);
    if (statementLength == 1) {
      setShapeProperty(JmolConstants.SHAPE_DIPOLES, "thisID", null);
      return;
    }
    for (int i = 1; i < statementLength; ++i) {
      propertyName = null;
      propertyValue = null;
      switch (getToken(i).tok) {
      case Token.on:
        propertyName = "on";
        break;
      case Token.off:
        propertyName = "off";
        break;
      case Token.delete:
        propertyName = "delete";
        break;
      case Token.integer:
      case Token.decimal:
        propertyName = "value";
        propertyValue = new Float(floatParameter(i));
        break;
      case Token.bitset:
        propertyName = "atomBitset";
        // fall through
      case Token.expressionBegin:
        if (propertyName == null)
          propertyName = (iHaveAtoms || iHaveCoord ? "endSet" : "startSet");
        propertyValue = atomExpression(i);
        i = iToken;
        iHaveAtoms = true;
        break;
      case Token.leftbrace:
      case Token.point3f:
        // {X, Y, Z}
        Point3f pt = getPoint3f(i, true);
        i = iToken;
        propertyName = (iHaveAtoms || iHaveCoord ? "endCoord" : "startCoord");
        propertyValue = pt;
        iHaveCoord = true;
        break;
      case Token.bonds:
        propertyName = "bonds";
        break;
      case Token.calculate:
        propertyName = "calculate";
        break;
      case Token.id:
        setShapeId(JmolConstants.SHAPE_DIPOLES, ++i, idSeen);
        i = iToken;
        break;
      case Token.cross:
        propertyName = "cross";
        propertyValue = Boolean.TRUE;
        break;
      case Token.nocross:
        propertyName = "cross";
        propertyValue = Boolean.FALSE;
        break;
      case Token.offset:
        float v = floatParameter(++i);
        if (theTok == Token.integer) {
          propertyName = "offsetPercent";
          propertyValue = new Integer((int) v);
        } else {
          propertyName = "offset";
          propertyValue = new Float(v);
        }
        break;
      case Token.offsetside:
        propertyName = "offsetSide";
        propertyValue = new Float(floatParameter(++i));
        break;

      case Token.val:
        propertyName = "value";
        propertyValue = new Float(floatParameter(++i));
        break;
      case Token.width:
        propertyName = "width";
        propertyValue = new Float(floatParameter(++i));
        break;
      default:
        if (theTok == Token.times || Token.tokAttr(theTok, Token.identifier)) {
          setShapeId(JmolConstants.SHAPE_DIPOLES, i, idSeen);
          i = iToken;
          break;
        }
        error(ERROR_invalidArgument);
      }
      idSeen = (theTok != Token.delete && theTok != Token.calculate);
      if (propertyName != null)
        setShapeProperty(JmolConstants.SHAPE_DIPOLES, propertyName,
            propertyValue);
    }
    if (iHaveCoord || iHaveAtoms)
      setShapeProperty(JmolConstants.SHAPE_DIPOLES, "set", null);
  }

  private void animationMode() throws ScriptException {
    float startDelay = 1, endDelay = 1;
    if (statementLength > 5)
      error(ERROR_badArgumentCount);
    int animationMode = JmolConstants.ANIMATION_ONCE;
    switch (getToken(2).tok) {
    case Token.loop:
      animationMode = JmolConstants.ANIMATION_LOOP;
      break;
    case Token.once:
      startDelay = endDelay = 0;
      break;
    case Token.palindrome:
      animationMode = JmolConstants.ANIMATION_PALINDROME;
      break;
    case Token.identifier:
      error(ERROR_invalidArgument);
    }
    if (statementLength >= 4) {
      startDelay = endDelay = floatParameter(3);
      if (statementLength == 5)
        endDelay = floatParameter(4);
    }
    if (!isSyntaxCheck)
      viewer.setAnimationReplayMode(animationMode, startDelay, endDelay);
  }

  private void vibration() throws ScriptException {
    checkLength(-3);
    float period = 0;
    switch (getToken(1).tok) {
    case Token.on:
      checkLength(2);
      period = viewer.getVibrationPeriod();
      break;
    case Token.off:
      checkLength(2);
      period = 0;
      break;
    case Token.integer:
    case Token.decimal:
      checkLength(2);
      period = floatParameter(1);
      break;
    case Token.scale:
      setFloatProperty("vibrationScale", floatParameter(2, -10, 10));
      return;
    case Token.period:
      setFloatProperty("vibrationPeriod", floatParameter(2));
      return;
    case Token.identifier:
      error(ERROR_invalidArgument);
    default:
      period = -1;
    }
    if (period < 0)
      error(ERROR_invalidArgument);
    if (isSyntaxCheck)
      return;
    if (period == 0) {
      viewer.setVibrationOff();
      return;
    }
    viewer.setVibrationPeriod(-period);
  }

  private void animationDirection() throws ScriptException {
    int i = 2;
    int direction = 0;
    switch (tokAt(i)) {
    case Token.minus:
      direction = -intParameter(++i);
      break;
    case Token.plus:
      direction = intParameter(++i);
      break;
    case Token.integer:
      direction = intParameter(i);
      if (direction > 0)
        direction = 0;
      break;
    default:
      error(ERROR_invalidArgument);
    }
    checkLength(++i);
    if (direction != 1 && direction != -1)
      error(ERROR_numberMustBe, "-1", "1");
    if (!isSyntaxCheck)
      viewer.setAnimationDirection(direction);
  }

  private void calculate() throws ScriptException {
    boolean isSurface = false;
    boolean asDSSP = false;
    BitSet bs;
    BitSet bs2 = null;
    int n = Integer.MIN_VALUE;
    if ((iToken = statementLength) >= 2) {
      clearDefinedVariableAtomSets();
      switch (getToken(1).tok) {
      case Token.straightness:
        if (!isSyntaxCheck) {
          viewer.calculateStraightness();
          viewer.addStateScript(thisCommand, false, true);
        }
        return;
      case Token.hydrogen:
        bs = (statementLength == 2 ? null : atomExpression(2));
        checkLast(iToken);
        if (!isSyntaxCheck)
          viewer.addHydrogens(bs, false, false);
        return;
      case Token.pointgroup:
        pointGroup();
        return;
      case Token.surface:
        isSurface = true;
        // deprecated
        // fall through
      case Token.surfacedistance:
        /*
         * preferred:
         *
         * calculate surfaceDistance FROM {...} calculate surfaceDistance WITHIN
         * {...}
         */
        boolean isFrom = false;
        switch (tokAt(2)) {
        case Token.within:
          iToken++;
          break;
        case Token.nada:
          isFrom = !isSurface;
          break;
        case Token.from:
          isFrom = true;
          iToken++;
          break;
        default:
          isFrom = true;
        }
        bs = (iToken + 1 < statementLength ? atomExpression(++iToken) : viewer
            .getSelectionSet(false));
        checkLength(++iToken);
        if (isSyntaxCheck)
          return;
        viewer.calculateSurface(bs, (isFrom ? Float.MAX_VALUE : -1));
        return;
      case Token.struts:
        bs = (iToken + 1 < statementLength ? atomExpression(++iToken) : null);
        bs2 = (iToken + 1 < statementLength ? atomExpression(++iToken) : null);
        checkLength(++iToken);
        if (isSyntaxCheck)
          return;
        n = viewer.calculateStruts(bs, bs2);
        if (n > 0)
          colorShape(JmolConstants.SHAPE_STRUTS, JmolEdge.BOND_STRUT,
              0x0FFFFFF, "translucent", 0.5f, null);
        showString(GT._("{0} struts added", n));
        return;
      case Token.volume:
        if (!isSyntaxCheck) {
          float val = viewer.getVolume(null, null);
          showString("" + Math.round(val * 10) / 10f + " A^3; "
              + Math.round(val * 6.02) / 10f + " cm^3/mole (VDW "
              + viewer.getDefaultVdwTypeNameOrData(Integer.MIN_VALUE) + ")");
          return;
        }
        break;
      case Token.aromatic:
        checkLength(2);
        if (!isSyntaxCheck)
          viewer.assignAromaticBonds();
        return;
      case Token.identifier:
        checkLength(2);
        break;
      case Token.hbond:
        if (statementLength == 2) {
          if (!isSyntaxCheck) {
            n = viewer.autoHbond(null, null);
            break;
          }
          return;
        }
        BitSet bs1 = null;
        // calculate hbonds STRUCTURE -- only the DSSP structurally-defining H bonds
        asDSSP = (tokAt(++iToken) == Token.structure);
        if (asDSSP)
          bs1 = viewer.getSelectionSet(false);
        else
          bs1 = atomExpression(iToken);
        if (!asDSSP && !(asDSSP = (tokAt(++iToken) == Token.structure)))
          bs2 = atomExpression(iToken);
        if (!isSyntaxCheck) {
          n = viewer.autoHbond(bs1, bs2);
          break;
        }
        return;
      case Token.structure:
        bs = (statementLength < 4 ? null : atomExpression(2));
        switch (tokAt(++iToken)) {
        case Token.ramachandran:
          break;
        case Token.dssp:
          asDSSP = true;
          break;
        case Token.nada:
          asDSSP = viewer.getDefaultStructureDSSP();
          break;
        default:
          error(ERROR_invalidArgument);
        }
        if (!isSyntaxCheck)
          showString(viewer.calculateStructures(bs, asDSSP, true));
        return;
      }
      if (n != Integer.MIN_VALUE) {
        scriptStatusOrBuffer(GT._("{0} hydrogen bonds", Math.abs(n)));
        return;
      }
    }
    error(
        ERROR_what,
        "CALCULATE",
        "aromatic? hbonds? polymers? straightness? structure? strut? surfaceDistance FROM? surfaceDistance WITHIN? volume?");
  }

  private void pointGroup() throws ScriptException {
    switch (tokAt(0)) {
    case Token.calculate:
      if (!isSyntaxCheck)
        showString(viewer.calculatePointGroup());
      return;
    case Token.show:
      if (!isSyntaxCheck)
        showString(viewer.getPointGroupAsString(false, null, 0, 0));
      return;
    }
    // draw pointgroup [C2|C3|Cs|Ci|etc.] [n] [scale x]
    int pt = 2;
    String type = (tokAt(pt) == Token.scale ? "" : optParameterAsString(pt));
    float scale = 1;
    int index = 0;
    if (type.length() > 0) {
      if (isFloatParameter(++pt))
        index = intParameter(pt++);
    }
    if (tokAt(pt) == Token.scale)
      scale = floatParameter(++pt);
    if (!isSyntaxCheck)
      runScript(viewer.getPointGroupAsString(true, type, index, scale));
  }

  private void dots(int iShape) throws ScriptException {
    if (!isSyntaxCheck)
      loadShape(iShape);
    setShapeProperty(iShape, "init", null);
    float value = Float.NaN;
    int type = 0;
    int ipt = 1;
    switch (getToken(ipt).tok) {
    case Token.only:
      restrictSelected(false, false);
      value = 1;
      type = RadiusData.TYPE_FACTOR;
      break;
    case Token.on:
      value = 1;
      type = RadiusData.TYPE_FACTOR;
      break;
    case Token.off:
      value = 0;
      break;
    case Token.integer:
      int dotsParam = intParameter(ipt);
      if (tokAt(ipt + 1) == Token.radius) {
        ipt++;
        setShapeProperty(iShape, "atom", new Integer(dotsParam));
        setShapeProperty(iShape, "radius", new Float(floatParameter(++ipt)));
        if (tokAt(++ipt) == Token.color) {
          setShapeProperty(iShape, "colorRGB", new Integer(getArgbParam(++ipt)));
          ipt++;
        }
        if (getToken(ipt).tok != Token.bitset)
          error(ERROR_invalidArgument);
        setShapeProperty(iShape, "dots", statement[ipt].value);
        return;
      }
      break;
    }
    RadiusData rd = (Float.isNaN(value) ? encodeRadiusParameter(ipt, false)
        : new RadiusData(value, type, 0));
    if (Float.isNaN(rd.value))
      error(ERROR_invalidArgument);
    setShapeSize(iShape, rd);
  }

  private void proteinShape(int shapeType) throws ScriptException {
    int mad = 0;
    // token has ondefault1
    switch (getToken(1).tok) {
    case Token.only:
      if (isSyntaxCheck)
        return;
      restrictSelected(false, false);
      mad = -1;
      break;
    case Token.on:
      mad = -1; // means take default
      break;
    case Token.off:
      break;
    case Token.structure:
      mad = -2;
      break;
    case Token.temperature:
    case Token.displacement:
      mad = -4;
      break;
    case Token.integer:
      mad = (intParameter(1, 0, 1000) * 8);
      break;
    case Token.decimal:
      mad = (int) (floatParameter(1, -Shape.RADIUS_MAX, Shape.RADIUS_MAX) * 2000);
      if (mad < 0) {
        restrictSelected(false, false);
        mad = -mad;
      }
      break;
    case Token.bitset:
      if (!isSyntaxCheck)
        loadShape(shapeType);
      setShapeProperty(shapeType, "bitset", theToken.value);
      return;
    default:
      error(ERROR_booleanOrNumberExpected);
    }
    setShapeSize(shapeType, mad, null);
  }

  private void animation() throws ScriptException {
    boolean animate = false;
    switch (getToken(1).tok) {
    case Token.on:
      animate = true;
      // fall through
    case Token.off:
      if (!isSyntaxCheck)
        viewer.setAnimationOn(animate);
      break;
    case Token.frame:
      frame(2);
      break;
    case Token.mode:
      animationMode();
      break;
    case Token.direction:
      animationDirection();
      break;
    case Token.fps:
      setIntProperty("animationFps", intParameter(checkLast(2)));
      break;
    default:
      frameControl(1, true);
    }
  }

  private void assign() throws ScriptException {
    int atomsOrBonds = tokAt(1);
    int index = atomExpression(2).nextSetBit(0);
    int index2 = -1;
    String type = null;
    if (index < 0)
      error(ERROR_invalidArgument);
    if (atomsOrBonds == Token.connect) {
      index2 = atomExpression(++iToken).nextSetBit(0);
    } else {
      type = parameterAsString(++iToken);
    }
    Point3f pt = (++iToken < statementLength ? centerParameter(iToken) : null);
    if (isSyntaxCheck)
      return;
    switch (atomsOrBonds) {
    case Token.atoms:
      viewer.assignAtom(index, pt, type);
      break;
    case Token.bonds:
      viewer.assignBond(index, (type + "p").charAt(0));
      break;
    case Token.connect:
      viewer.assignConnect(index, index2);
    }
  }

  private void file() throws ScriptException {
    int file = intParameter(checkLast(1));
    if (isSyntaxCheck)
      return;
    int modelIndex = viewer.getModelNumberIndex(file * 1000000 + 1, false,
        false);
    int modelIndex2 = -1;
    if (modelIndex >= 0) {
      modelIndex2 = viewer.getModelNumberIndex((file + 1) * 1000000 + 1, false,
          false);
      if (modelIndex2 < 0)
        modelIndex2 = viewer.getModelCount();
      modelIndex2--;
    }
    viewer.setAnimationOn(false);
    viewer.setAnimationDirection(1);
    viewer.setAnimationRange(modelIndex, modelIndex2);
    viewer.setCurrentModelIndex(-1);
  }

  private void fixed() throws ScriptException {
    BitSet bs = (statementLength == 1 ? null : atomExpression(1));
    if (isSyntaxCheck)
      return;
    viewer.setMotionFixedAtoms(bs);
  }

  private void frame(int offset) throws ScriptException {
    boolean useModelNumber = true;
    // for now -- as before -- remove to implement
    // frame/model difference
    if (statementLength == 1 && offset == 1) {
      int modelIndex = viewer.getCurrentModelIndex();
      int m;
      if (!isSyntaxCheck && modelIndex >= 0
          && (m = viewer.getJmolDataSourceFrame(modelIndex)) >= 0)
        viewer.setCurrentModelIndex(m == modelIndex ? Integer.MIN_VALUE : m);
      return;
    }
    switch (tokAt(1)) {
    case Token.title:
      if (checkLength23() > 0)
        if (!isSyntaxCheck)
          viewer.setFrameTitle(statementLength == 2 ? "@{_modelName}"
              : parameterAsString(2));
      return;
    case Token.align:
      BitSet bs = (statementLength == 2 || tokAt(2) == Token.none ? null
          : atomExpression(2));
      if (!isSyntaxCheck)
        viewer.setFrameOffsets(bs);
      return;
    }
    if (getToken(offset).tok == Token.minus) {
      ++offset;
      if (getToken(checkLast(offset)).tok != Token.integer
          || intParameter(offset) != 1)
        error(ERROR_invalidArgument);
      if (!isSyntaxCheck)
        viewer.setAnimation(Token.prev);
      return;
    }
    boolean isPlay = false;
    boolean isRange = false;
    boolean isAll = false;
    boolean isHyphen = false;
    int[] frameList = new int[] { -1, -1 };
    int nFrames = 0;

    for (int i = offset; i < statementLength; i++) {
      switch (getToken(i).tok) {
      case Token.all:
      case Token.times:
        checkLength(offset + (isRange ? 2 : 1));
        isAll = true;
        break;
      case Token.minus: // ignore
        if (nFrames != 1)
          error(ERROR_invalidArgument);
        isHyphen = true;
        break;
      case Token.none:
        checkLength(offset + 1);
        break;
      case Token.decimal:
        useModelNumber = false;
        if (floatParameter(i) < 0)
          isHyphen = true;
        // fall through
      case Token.integer:
      case Token.string:
        if (nFrames == 2)
          error(ERROR_invalidArgument);
        int iFrame = (theTok == Token.string ? JmolConstants
            .modelValue((String) theToken.value) : theToken.intValue);
        if (iFrame == Integer.MAX_VALUE)
          iFrame = 0; // frame 0.0
        if (iFrame == -1) {
          checkLength(offset + 1);
          if (!isSyntaxCheck)
            viewer.setAnimation(Token.prev);
          return;
        }
        if (iFrame >= 1000 && iFrame < 1000000 && viewer.haveFileSet())
          iFrame = (iFrame / 1000) * 1000000 + (iFrame % 1000); // initial way
        if (!useModelNumber && iFrame == 0)
          isAll = true; // 0.0 means ALL; 0 means "all in this range
        if (iFrame >= 1000000)
          useModelNumber = false;
        frameList[nFrames++] = iFrame;
        break;
      case Token.play:
        isPlay = true;
        break;
      case Token.range:
        isRange = true;
        break;
      default:
        frameControl(offset, false);
        return;
      }
    }
    boolean haveFileSet = viewer.haveFileSet();
    if (isRange && nFrames == 0)
      isAll = true;
    if (isSyntaxCheck)
      return;
    if (isAll) {
      viewer.setAnimationOn(false);
      viewer.setAnimationRange(-1, -1);
      if (!isRange) {
        viewer.setCurrentModelIndex(-1);
      }
      return;
    }
    if (nFrames == 2 && !isRange)
      isHyphen = true;
    if (haveFileSet)
      useModelNumber = false;
    else if (useModelNumber)
      for (int i = 0; i < nFrames; i++)
        if (frameList[i] >= 0)
          frameList[i] %= 1000000;
    int modelIndex = viewer.getModelNumberIndex(frameList[0], useModelNumber,
        false);
    int modelIndex2 = -1;
    if (haveFileSet && nFrames == 1 && modelIndex < 0 && frameList[0] != 0) {
      // may have frame 2.0 or frame 2 meaning the range of models in file 2
      if (frameList[0] < 1000000)
        frameList[0] *= 1000000;
      if (frameList[0] % 1000000 == 0) {
        frameList[0]++;
        modelIndex = viewer.getModelNumberIndex(frameList[0], false, false);
        if (modelIndex >= 0) {
          modelIndex2 = viewer.getModelNumberIndex(frameList[0] + 1000000,
              false, false);
          if (modelIndex2 < 0)
            modelIndex2 = viewer.getModelCount();
          modelIndex2--;
          if (isRange)
            nFrames = 2;
          else if (!isHyphen && modelIndex2 != modelIndex)
            isHyphen = true;
          isRange = isRange || modelIndex == modelIndex2;// (isRange ||
          // !isHyphen &&
          // modelIndex2 !=
          // modelIndex);
        }
      } else {
        // must have been a bad frame number. Just return.
        return;
      }
    }

    if (!isPlay && !isRange || modelIndex >= 0)
      viewer.setCurrentModelIndex(modelIndex, false);
    if (isPlay && nFrames == 2 || isRange || isHyphen) {
      if (modelIndex2 < 0)
        modelIndex2 = viewer.getModelNumberIndex(frameList[1], useModelNumber,
            false);
      viewer.setAnimationOn(false);
      viewer.setAnimationDirection(1);
      viewer.setAnimationRange(modelIndex, modelIndex2);
      viewer.setCurrentModelIndex(isHyphen && !isRange ? -1
          : modelIndex >= 0 ? modelIndex : 0, false);
    }
    if (isPlay)
      viewer.setAnimation(Token.resume);
  }

  BitSet bitSetForModelFileNumber(int m) {
    // where */1.0 or */1.1 or just 1.1 is processed
    BitSet bs = new BitSet(viewer.getAtomCount());
    if (isSyntaxCheck)
      return bs;
    int modelCount = viewer.getModelCount();
    boolean haveFileSet = viewer.haveFileSet();
    if (m < 1000000 && haveFileSet)
      m *= 1000000;
    int pt = m % 1000000;
    if (pt == 0) {
      int model1 = viewer.getModelNumberIndex(m + 1, false, false);
      if (model1 < 0)
        return bs;
      int model2 = (m == 0 ? modelCount : viewer.getModelNumberIndex(
          m + 1000001, false, false));
      if (model1 < 0)
        model1 = 0;
      if (model2 < 0)
        model2 = modelCount;
      if (viewer.isTrajectory(model1))
        model2 = model1 + 1;
      for (int j = model1; j < model2; j++)
        bs.or(viewer.getModelUndeletedAtomsBitSet(j));
    } else {
      int modelIndex = viewer.getModelNumberIndex(m, false, true);
      if (modelIndex >= 0)
        bs.or(viewer.getModelUndeletedAtomsBitSet(modelIndex));
    }
    return bs;
  }

  private void frameControl(int i, boolean isSubCmd) throws ScriptException {
    switch (getToken(checkLast(i)).tok) {
    case Token.playrev:
    case Token.play:
    case Token.resume:
    case Token.pause:
    case Token.next:
    case Token.prev:
    case Token.rewind:
    case Token.first:
    case Token.last:
      if (!isSyntaxCheck)
        viewer.setAnimation(theTok);
      return;
    }
    error(ERROR_invalidArgument);
  }

  private int getShapeType(int tok) throws ScriptException {
    int iShape = JmolConstants.shapeTokenIndex(tok);
    if (iShape < 0)
      error(ERROR_unrecognizedObject);
    return iShape;
  }

  private void font(int shapeType, float fontsize) throws ScriptException {
    String fontface = "SansSerif";
    String fontstyle = "Plain";
    int sizeAdjust = 0;
    float scaleAngstromsPerPixel = -1;
    switch (iToken = statementLength) {
    case 6:
      scaleAngstromsPerPixel = floatParameter(5);
      if (scaleAngstromsPerPixel >= 5) // actually a zoom value
        scaleAngstromsPerPixel = viewer.getZoomSetting()
            / scaleAngstromsPerPixel / viewer.getScalePixelsPerAngstrom(false);
      // fall through
    case 5:
      if (getToken(4).tok != Token.identifier)
        error(ERROR_invalidArgument);
      fontstyle = parameterAsString(4);
      // fall through
    case 4:
      if (getToken(3).tok != Token.identifier)
        error(ERROR_invalidArgument);
      fontface = parameterAsString(3);
      if (!isFloatParameter(2))
        error(ERROR_numberExpected);
      fontsize = floatParameter(2);
      shapeType = getShapeType(getToken(1).tok);
      break;
    case 3:
      if (!isFloatParameter(2))
        error(ERROR_numberExpected);
      if (shapeType == -1) {
        shapeType = getShapeType(getToken(1).tok);
        fontsize = floatParameter(2);
      } else {// labels --- old set fontsize N
        if (fontsize >= 1)
          fontsize += (sizeAdjust = 5);
      }
      break;
    case 2:
    default:
      if (shapeType == JmolConstants.SHAPE_LABELS) {
        // set fontsize
        fontsize = JmolConstants.LABEL_DEFAULT_FONTSIZE;
        break;
      }
      error(ERROR_badArgumentCount);
    }
    if (shapeType == JmolConstants.SHAPE_LABELS) {
      if (fontsize < 0
          || fontsize >= 1
          && (fontsize < JmolConstants.LABEL_MINIMUM_FONTSIZE || fontsize > JmolConstants.LABEL_MAXIMUM_FONTSIZE))
        integerOutOfRange(JmolConstants.LABEL_MINIMUM_FONTSIZE - sizeAdjust,
            JmolConstants.LABEL_MAXIMUM_FONTSIZE - sizeAdjust);
      setShapeProperty(JmolConstants.SHAPE_LABELS, "setDefaults", viewer
          .getNoneSelected());
    }
    if (isSyntaxCheck)
      return;
    if (Graphics3D.getFontStyleID(fontface) >= 0) {
      fontstyle = fontface;
      fontface = "SansSerif";
    }     
    Font3D font3d = viewer.getFont3D(fontface, fontstyle, fontsize);
    loadShape(shapeType);
    setShapeProperty(shapeType, "font", font3d);
    if (scaleAngstromsPerPixel >= 0)
      setShapeProperty(shapeType, "scalereference", new Float(
          scaleAngstromsPerPixel));
  }

  private void set() throws ScriptException {
    /*
     * The SET command now allows only the following:
     *
     * SET SET xxx? SET [valid Jmol Token.setparam keyword] SET labelxxxx SET
     * xxxxCallback
     *
     * All other variables must be assigned using
     *
     * x = ....
     *
     * The processing goes as follows:
     *
     * check for SET check for SET xx? check for SET xxxx where xxxx is a
     * command --- deprecated (all other settings may alternatively start with x
     * = y) check for SET xxxx where xxxx requires special checking (all other
     * settings may alternatively start with x = (math expression) check for
     * context variables var x = ... check for deprecated SET words such as
     * "radius"
     */
    if (statementLength == 1) {
      showString(viewer.getAllSettings(null));
      return;
    }
    boolean isJmolSet = (parameterAsString(0).equals("set"));
    String key = optParameterAsString(1);
    if (isJmolSet && statementLength == 2 && key.indexOf("?") >= 0) {
      showString(viewer.getAllSettings(key.substring(0, key.indexOf("?"))));
      return;
    }
    int tok = getToken(1).tok;
    int newTok = 0;
    String sval;
    int ival = Integer.MAX_VALUE;

    boolean showing = (!isSyntaxCheck && !tQuiet
        && scriptLevel <= scriptReportingLevel && !((String) statement[0].value)
        .equals("var"));

    // THESE FIRST ARE DEPRECATED AND HAVE THEIR OWN COMMAND
    // anything in this block MUST RETURN

    switch (tok) {
    case Token.axes:
      axes(2);
      return;
    case Token.background:
      background(2);
      return;
    case Token.boundbox:
      boundbox(2);
      return;
    case Token.frank:
      frank(2);
      return;
    case Token.history:
      history(2);
      return;
    case Token.label:
      label(2);
      return;
    case Token.unitcell:
      unitcell(2);
      return;
    case Token.highlight:
      loadShape(JmolConstants.SHAPE_HALOS);
      setShapeProperty(JmolConstants.SHAPE_HALOS, "highlight",
          (tokAt(2) == Token.off ? null : atomExpression(2)));
      return;
    case Token.display:// deprecated
    case Token.selectionhalos:
      selectionHalo(2);
      return;
    case Token.timeout:
      timeout(2);
      return;
    }

    // THESE HAVE MULTIPLE CONTEXTS AND
    // SO DO NOT ALLOW CALCULATIONS xxx = a + b...
    // and are thus "setparam" only

    // anything in this block MUST RETURN

    switch (tok) {
    case Token.axescolor:
      ival = getArgbParam(2);
      if (!isSyntaxCheck)
        setObjectArgb("axes", ival);
      return;
    case Token.bondmode:
      setBondmode();
      return;
    case Token.debug:
      if (isSyntaxCheck)
        return;
      int iLevel = (tokAt(2) == Token.off || tokAt(2) == Token.integer
          && intParameter(2) == 0 ? 4 : 5);
      Logger.setLogLevel(iLevel);
      setIntProperty("logLevel", iLevel);
      if (iLevel == 4) {
        viewer.setDebugScript(false);
        if (showing)
          viewer.showParameter("debugScript", true, 80);
      }
      setDebugging();
      if (showing)
        viewer.showParameter("logLevel", true, 80);
      return;
    case Token.echo:
      setEcho();
      return;
    case Token.fontsize:
      font(JmolConstants.SHAPE_LABELS, checkLength23() == 2 ? 0
          : floatParameter(2));
      return;
    case Token.hbond:
      setHbond();
      return;
    case Token.measure:
    case Token.measurements:
      setMonitor();
      return;
    case Token.ssbond: // ssBondsBackbone
      setSsbond();
      return;
    case Token.togglelabel:
      setLabel("toggle");
      return;
    case Token.usercolorscheme:
      setUserColors();
      return;
    }

    // these next may report a value
    // require special checks
    // math expressions are allowed in most cases.

    boolean justShow = true;

    switch (tok) {
    case Token.axesscale:
      setFloatProperty("axesScale", floatSetting(2, -100, 100));
      break;
    case Token.backgroundmodel:
      if (statementLength > 2) {
        String modelDotted = stringSetting(2, false);
        int modelNumber;
        boolean useModelNumber = false;
        if (modelDotted.indexOf(".") < 0) {
          modelNumber = Parser.parseInt(modelDotted);
          useModelNumber = true;
        } else {
          modelNumber = JmolConstants.modelValue(modelDotted);
        }
        if (isSyntaxCheck)
          return;
        int modelIndex = viewer.getModelNumberIndex(modelNumber,
            useModelNumber, true);
        viewer.setBackgroundModelIndex(modelIndex);
        return;
      }
      break;
    case Token.defaultvdw:
      // allows unquoted string for known vdw type
      if (statementLength > 2) {
        sval = (statementLength == 3
            && JmolConstants.getVdwType(parameterAsString(2)) == JmolConstants.VDW_UNKNOWN ? stringSetting(
            2, false)
            : parameterAsString(2));
        if (JmolConstants.getVdwType(sval) < 0)
          error(ERROR_invalidArgument);
        setStringProperty(key, sval);
      }
      break;
    case Token.defaultlattice:
      if (statementLength > 2) {
        Point3f pt;
        ScriptVariable var = parameterExpressionToken(2);
        if (var.tok == Token.point3f)
          pt = (Point3f) var.value;
        else {
          int ijk = ScriptVariable.iValue(var);
          if (ijk < 555)
            pt = new Point3f();
          else
            pt = viewer.getSymmetry().ijkToPoint3f(ijk + 111);
        }
        if (!isSyntaxCheck)
          viewer.setDefaultLattice(pt);
      }
      break;
    case Token.defaults:
    case Token.defaultcolorscheme:
      // allows unquoted "jmol" or "rasmol"
      if (statementLength > 2) {
        if ((theTok = tokAt(2)) == Token.jmol || theTok == Token.rasmol) {
          sval = parameterAsString(checkLast(2)).toLowerCase();
        } else {
          sval = stringSetting(2, false).toLowerCase();
        }
        if (!sval.equals("jmol") && !sval.equals("rasmol"))
          error(ERROR_invalidArgument);
        setStringProperty(key, sval);
      }
      break;
    case Token.dipolescale:
      setFloatProperty("dipoleScale", floatSetting(2, -10, 10));
      break;
    case Token.formalcharge:
      ival = intSetting(2);
      if (ival == Integer.MIN_VALUE)
        error(ERROR_invalidArgument);
      if (!isSyntaxCheck)
        viewer.setFormalCharges(ival);
      return;
    case Token.historylevel:
      // save value locally as well
      ival = intSetting(2);
      if (!isSyntaxCheck) {
        if (ival != Integer.MIN_VALUE)
          commandHistoryLevelMax = ival;
        setIntProperty(key, ival);
      }
      break;
    case Token.language:
      // language can be used without quotes in a SET context
      // set language en
      if (statementLength > 2)
        setStringProperty(key, stringSetting(2, isJmolSet));
      break;
    case Token.measurementunits:
      if (statementLength > 2)
        setMeasurementUnits(stringSetting(2, isJmolSet));
      break;
    case Token.phongexponent:
      setIntProperty(key, intSetting(2, Integer.MAX_VALUE, 0, 1000));
      break;
    case Token.picking:
      if (statementLength > 2) {
        setPicking();
        return;
      }
      break;
    case Token.pickingstyle:
      if (statementLength > 2) {
        setPickingStyle();
        return;
      }
      break;
    case Token.property: // compiler may give different values to this token
      // set property_xxxx will be handled in setVariable
      break;
    case Token.scriptreportinglevel:
      // save value locally as well
      ival = intSetting(2);
      if (!isSyntaxCheck) {
        if (ival != Integer.MIN_VALUE)
          scriptReportingLevel = ival;
        setIntProperty(key, ival);
      }
      break;
    case Token.solventproberadius:
      setFloatProperty(key, floatSetting(2, 0, 10));
      break;
    case Token.specular:
    case Token.specularpercent:
    case Token.ambientpercent:
    case Token.diffusepercent:
      ival = intSetting(2);
      if (tok == Token.specular) {
        if (ival == Integer.MIN_VALUE || ival == 0 || ival == 1) {
          justShow = false;
          break;
        }
        tok = Token.specularpercent;
        key = "specularPercent";
      }
      setIntProperty(key, intSetting(2, ival, 0, 100));
      break;
    case Token.specularpower:
    case Token.specularexponent:
      ival = intSetting(2);
      if (tok == Token.specularpower) {
        if (ival >= 0) {
          justShow = false;
          break;
        }
        tok = Token.specularexponent;
        key = "specularExponent";
        if (ival < -10 || ival > -1)
          integerOutOfRange(-10, -1);
        ival = -ival;
      }
      setIntProperty(key, intSetting(2, ival, 0, 10));
      break;
    case Token.strands:
    case Token.strandcount:
    case Token.strandcountformeshribbon:
    case Token.strandcountforstrands:
      if (tok == Token.strands) {
        tok = Token.strandcount;
        key = "strandCount";
      }
      setIntProperty(key, intSetting(2, Integer.MAX_VALUE, 0, 20));
      break;
    default:
      justShow = false;
    }

    if (justShow && !showing)
      return;

    // var xxxx = xxx can supercede set xxxx

    boolean isContextVariable = (!justShow && !isJmolSet && getContextVariableAsVariable(key) != null);

    if (!justShow && !isContextVariable) {

      // THESE NEXT are deprecated:

      switch (tok) {
      case Token.bonds:
        newTok = Token.showmultiplebonds;
        break;
      case Token.hetero:
        newTok = Token.selecthetero;
        break;
      case Token.hydrogen:
        newTok = Token.selecthydrogen;
        break;
      case Token.measurementnumbers:
        newTok = Token.measurementlabels;
        break;
      case Token.radius:
        newTok = Token.solventproberadius;
        setFloatProperty("solventProbeRadius", floatSetting(2, 0, 10));
        justShow = true;
        break;
      case Token.scale3d:
        newTok = Token.scaleangstromsperinch;
        break;
      case Token.solvent:
        newTok = Token.solventprobe;
        break;
      case Token.color:
        newTok = Token.defaultcolorscheme;
        break;
      case Token.spin:
        sval = parameterAsString(2).toLowerCase();
        switch ("x;y;z;fps".indexOf(sval + ";")) {
        case 0:
          newTok = Token.spinx;
          break;
        case 2:
          newTok = Token.spiny;
          break;
        case 4:
          newTok = Token.spinz;
          break;
        case 6:
          newTok = Token.spinfps;
          break;
        default:
          error(ERROR_unrecognizedParameter, "set SPIN ", sval);
        }
        if (!isSyntaxCheck)
          viewer.setSpin(sval, (int) floatParameter(checkLast(3)));
        justShow = true;
        break;
      }
    }

    if (newTok != 0) {
      key = Token.nameOf(tok = newTok);
    } else if (!justShow && !isContextVariable) {
      // special cases must be checked
      if (key.length() == 0 || key.charAt(0) == '_') // these cannot be set by user
        error(ERROR_invalidArgument);

      // these next are not reported and do not allow calculation xxxx = a + b

      String lckey = key.toLowerCase();
      if (lckey.indexOf("label") == 0
          && Parser
              .isOneOf(key.substring(5).toLowerCase(),
                  "front;group;atom;offset;offsetexact;pointer;alignment;toggle;scalereference")) {
        if (setLabel(key.substring(5)))
          return;
      }
      if (lckey.indexOf("callback") >= 0)
        tok = Token.setparam;
    }

    if (isJmolSet && !Token.tokAttr(tok, Token.setparam)) {
      iToken = 1;
      if (!isStateScript)
        error(ERROR_unrecognizedParameter, "SET", key);
      warning(ERROR_unrecognizedParameterWarning, "SET", key);
    }

    if (!justShow && isJmolSet) {
      // simple cases
      switch (statementLength) {
      case 2:
        // set XXXX;
        // too bad we allow this...
        setBooleanProperty(key, true);
        justShow = true;
        break;
      case 3:
        // set XXXX val;
        // check for int and NONE just in case
        if (ival != Integer.MAX_VALUE) {
          // keep it simple
          setIntProperty(key, ival);
          justShow = true;
        }
        break;
      }
    }

    if (!justShow && !isJmolSet && tokAt(2) == Token.none) {
      if (!isSyntaxCheck)
        viewer.removeUserVariable(key);
      justShow = true;
    }

    if (!justShow) {
      int tok2 = (tokAt(1) == Token.expressionBegin ? 0 : tokAt(2));
      int setType = statement[0].intValue;
      // recasted by compiler:
      // var c.xxx =
      // c.xxx =
      // {...}[n].xxx =
      // not supported:
      // a[...][...].xxx =
      // var a[...][...].xxx =

      int pt = (tok2 == Token.opEQ ? 3
      // set x = ...
          : setType == '=' && !key.equals("return") && tok2 != Token.opEQ ? 0
          // {c}.xxx =
              // {...}.xxx =
              // {{...}[n]}.xxx =
              : 2
      // var a[...].xxx =
      // a[...].xxx =
      // var c = ...
      // var c = [
      // c = [
      // c = ...
      // set x ...
      // a[...] =
      );

      setVariable(pt, 0, key, setType);
      if (!isJmolSet)
        return;
    }
    if (showing)
      viewer.showParameter(key, true, 80);
  }


  private int intSetting(int pt, int val, int min, int max)
      throws ScriptException {
    if (val == Integer.MAX_VALUE)
      val = intSetting(pt);
    if (val != Integer.MIN_VALUE && val < min || val > max)
      integerOutOfRange(min, max);
    return val;
  }

  private int intSetting(int pt) throws ScriptException {
    if (pt == statementLength)
      return Integer.MIN_VALUE;
    return ScriptVariable.iValue(parameterExpressionToken(pt));
  }

  private float floatSetting(int pt, float min, float max)
      throws ScriptException {
    if (pt == statementLength)
      return Float.NaN;
    float val = ScriptVariable.fValue(parameterExpressionToken(pt));
    if (val < min || val > max)
      numberOutOfRange(min, max);
    return val;
  }

  private String stringSetting(int pt, boolean isJmolSet)
      throws ScriptException {
    if (isJmolSet && statementLength == pt + 1)
      return parameterAsString(pt);
    return ScriptVariable.sValue(parameterExpressionToken(pt));
  }

  private void setBondmode() throws ScriptException {
    boolean bondmodeOr = false;
    switch (getToken(checkLast(2)).tok) {
    case Token.opAnd:
      break;
    case Token.opOr:
      bondmodeOr = true;
      break;
    default:
      error(ERROR_invalidArgument);
    }
    setBooleanProperty("bondModeOr", bondmodeOr);
  }

  private void setEcho() throws ScriptException {
    String propertyName = "target";
    Object propertyValue = null;
    boolean echoShapeActive = true;
    // set echo xxx
    int len = 3;
    switch (getToken(2).tok) {
    case Token.off:
      checkLength(3);
      echoShapeActive = false;
      propertyName = "allOff";
      break;
    case Token.hide:
    case Token.hidden:
      propertyName = "hidden";
      propertyValue = Boolean.TRUE;
      break;
    case Token.on:
    case Token.display:
    case Token.displayed:
      propertyName = "hidden";
      propertyValue = Boolean.FALSE;
      break;
    case Token.none:
      echoShapeActive = false;
      // fall through
    case Token.all:
      checkLength(3);
      // fall through
    case Token.left:
    case Token.right:
    case Token.top:
    case Token.bottom:
    case Token.center:
    case Token.identifier:
      propertyValue = parameterAsString(2);
      break;
    case Token.model:
      int modelIndex = modelNumberParameter(3);
      if (isSyntaxCheck)
        return;
      if (modelIndex >= viewer.getModelCount())
        error(ERROR_invalidArgument);
      propertyName = "model";
      propertyValue = new Integer(modelIndex);
      len = 4;
      break;
    case Token.image:
      // set echo image "..."
      echo(3, true);
      return;
    case Token.depth:
      // set echo depth zzz
      propertyName = "%zpos";
      propertyValue = new Integer((int) floatParameter(3));
      len = 4;
      break;
    case Token.string:
      echo(2, false);
      return;
    default:
      if (!Token.tokAttr(theTok, Token.identifier))
        error(ERROR_invalidArgument);
      propertyValue = parameterAsString(2);
      break;
    }
    if (!isSyntaxCheck) {
      viewer.setEchoStateActive(echoShapeActive);
      loadShape(JmolConstants.SHAPE_ECHO);
      setShapeProperty(JmolConstants.SHAPE_ECHO, propertyName, propertyValue);
    }
    if (statementLength == len)
      return;
    propertyName = "align";
    propertyValue = null;
    // set echo name xxx
    if (statementLength == 4) {
      if (isCenterParameter(3)) {
        setShapeProperty(JmolConstants.SHAPE_ECHO, "xyz", centerParameter(3));
        return;
      }
      switch (getToken(3).tok) {
      case Token.off:
        propertyName = "off";
        break;
      case Token.hidden:
        propertyName = "hidden";
        propertyValue = Boolean.TRUE;
        break;
      case Token.displayed:
      case Token.on:
        propertyName = "hidden";
        propertyValue = Boolean.FALSE;
        break;
      case Token.model:
        int modelIndex = modelNumberParameter(4);
        if (isSyntaxCheck)
          return;
        if (modelIndex >= viewer.getModelCount())
          error(ERROR_invalidArgument);
        propertyName = "model";
        propertyValue = new Integer(modelIndex);
        break;
      case Token.left:
      case Token.right:
      case Token.top:
      case Token.bottom:
      case Token.center:
      case Token.identifier:
        propertyValue = parameterAsString(3);
        break;
      default:
        if (!Token.tokAttr(theTok, Token.identifier))
          error(ERROR_invalidArgument);
        propertyValue = parameterAsString(3);
        break;
      }
      setShapeProperty(JmolConstants.SHAPE_ECHO, propertyName, propertyValue);
      return;
    }
    // set echo name script "some script"
    // set echo name model x.y
    // set echo name depth nnnn
    // set echo name image "myimage.jpg"
    if (statementLength == 5) {
      switch (tokAt(3)) {
      case Token.script:
        propertyName = "script";
        propertyValue = parameterAsString(4);
        break;
      case Token.model:
        int modelIndex = modelNumberParameter(4);
        if (!isSyntaxCheck && modelIndex >= viewer.getModelCount())
          error(ERROR_invalidArgument);
        propertyName = "model";
        propertyValue = new Integer(modelIndex);
        break;
      case Token.image:
        // set echo name image "xxx"
        echo(4, true);
        return;
      case Token.depth:
        propertyName = "%zpos";
        propertyValue = new Integer((int) floatParameter(4));
        break;
      }
      if (propertyValue != null) {
        setShapeProperty(JmolConstants.SHAPE_ECHO, propertyName, propertyValue);
        return;
      }
    }
    // set echo name [x y] or set echo name [x y %]
    // set echo name x-pos y-pos

    getToken(4);
    int i = 3;
    // set echo name {x y z}
    if (isCenterParameter(i)) {
      if (!isSyntaxCheck)
        setShapeProperty(JmolConstants.SHAPE_ECHO, "xyz", centerParameter(i));
      return;
    }
    String type = "xypos";
    if ((propertyValue = xypParameter(i)) == null) {
      int pos = intParameter(i++);
      propertyValue = new Integer(pos);
      if (tokAt(i) == Token.percent) {
        type = "%xpos";
        i++;
      } else {
        type = "xpos";
      }
      setShapeProperty(JmolConstants.SHAPE_ECHO, type, propertyValue);
      pos = intParameter(i++);
      propertyValue = new Integer(pos);
      if (tokAt(i) == Token.percent) {
        type = "%ypos";
        i++;
      } else {
        type = "ypos";
      }
    }
    setShapeProperty(JmolConstants.SHAPE_ECHO, type, propertyValue);
  }

  private boolean setLabel(String str) throws ScriptException {
    loadShape(JmolConstants.SHAPE_LABELS);
    Object propertyValue = null;
    setShapeProperty(JmolConstants.SHAPE_LABELS, "setDefaults", viewer
        .getNoneSelected());
    while (true) {
      if (str.equals("scalereference")) {
        float scaleAngstromsPerPixel = floatParameter(2);
        if (scaleAngstromsPerPixel >= 5) // actually a zoom value
          scaleAngstromsPerPixel = viewer.getZoomSetting()
              / scaleAngstromsPerPixel
              / viewer.getScalePixelsPerAngstrom(false);
        propertyValue = new Float(scaleAngstromsPerPixel);
        break;
      }
      if (str.equals("offset") || str.equals("offsetexact")) {
        int xOffset = intParameter(2, -127, 127);
        int yOffset = intParameter(3, -127, 127);
        propertyValue = new Integer(Object2d.getOffset(xOffset, yOffset));
        break;
      }
      if (str.equals("alignment")) {
        switch (getToken(2).tok) {
        case Token.left:
        case Token.right:
        case Token.center:
          str = "align";
          propertyValue = theToken.value;
          break;
        default:
          error(ERROR_invalidArgument);
        }
        break;
      }
      if (str.equals("pointer")) {
        int flags = Object2d.POINTER_NONE;
        switch (getToken(2).tok) {
        case Token.off:
        case Token.none:
          break;
        case Token.background:
          flags |= Object2d.POINTER_BACKGROUND;
        case Token.on:
          flags |= Object2d.POINTER_ON;
          break;
        default:
          error(ERROR_invalidArgument);
        }
        propertyValue = new Integer(flags);
        break;
      }
      if (str.equals("toggle")) {
        iToken = 1;
        BitSet bs = (statementLength == 2 ? null : atomExpression(2));
        checkLast(iToken);
        if (!isSyntaxCheck)
          viewer.togglePickingLabel(bs);
        return true;
      }
      iToken = 1;
      boolean TF = (statementLength == 2 || getToken(2).tok == Token.on);
      if (str.equals("front") || str.equals("group")) {
        if (!TF && tokAt(2) != Token.off)
          error(ERROR_invalidArgument);
        if (!TF)
          str = "front";
        propertyValue = (TF ? Boolean.TRUE : Boolean.FALSE);
        break;
      }
      if (str.equals("atom")) {
        if (!TF && tokAt(2) != Token.off)
          error(ERROR_invalidArgument);
        str = "front";
        propertyValue = (TF ? Boolean.FALSE : Boolean.TRUE);
        break;
      }
      return false;
    }
    BitSet bs = (iToken + 1 < statementLength ? atomExpression(++iToken) : null);
    checkLast(iToken);
    if (isSyntaxCheck)
      return true;
    if (bs == null)
      setShapeProperty(JmolConstants.SHAPE_LABELS, str, propertyValue);
    else
      setShapeProperty(JmolConstants.SHAPE_LABELS, str, propertyValue, bs);
    return true;
  }

  private void setMonitor() throws ScriptException {
    // on off here incompatible with "monitor on/off" so this is just a SET
    // option.
    int tok = tokAt(checkLast(2));
    switch (tok) {
    case Token.on:
    case Token.off:
      setShapeProperty(JmolConstants.SHAPE_MEASURES, "showMeasurementNumbers",
          tok == Token.on ? Boolean.TRUE : Boolean.FALSE);
      return;
    case Token.dotted:
    case Token.integer:
    case Token.decimal:
      setShapeSize(JmolConstants.SHAPE_MEASURES, getSetAxesTypeMad(2), null);
      return;
    }
    setMeasurementUnits(parameterAsString(2));
  }

  private boolean setMeasurementUnits(String units) throws ScriptException {
    if (!StateManager.isMeasurementUnit(units))
      error(ERROR_unrecognizedParameter, "set measurementUnits ", units);
    if (!isSyntaxCheck)
      viewer.setMeasureDistanceUnits(units);
    return true;
  }

  /*
   * private void setProperty() throws ScriptException { // what possible good
   * is this? // set property foo bar is identical to // set foo bar if
   * (getToken(2).tok != Token.identifier) error(ERROR_propertyNameExpected);
   * String propertyName = parameterAsString(2); switch
   * (getToken(checkLast(3)).tok) { case Token.on:
   * setBooleanProperty(propertyName, true); break; case Token.off:
   * setBooleanProperty(propertyName, false); break; case Token.integer:
   * setIntProperty(propertyName, intParameter(3)); break; case Token.decimal:
   * setFloatProperty(propertyName, floatParameter(3)); break; case
   * Token.string: setStringProperty(propertyName, stringParameter(3)); break;
   * default: error(ERROR_unrecognizedParameter, "SET " +
   * propertyName.toUpperCase(), parameterAsString(3)); } }
   */

  private void setSsbond() throws ScriptException {
    boolean ssbondsBackbone = false;
    // loadShape(JmolConstants.SHAPE_SSSTICKS);
    switch (tokAt(checkLast(2))) {
    case Token.backbone:
      ssbondsBackbone = true;
      break;
    case Token.sidechain:
      break;
    default:
      error(ERROR_invalidArgument);
    }
    setBooleanProperty("ssbondsBackbone", ssbondsBackbone);
  }

  private void setHbond() throws ScriptException {
    boolean bool = false;
    switch (tokAt(checkLast(2))) {
    case Token.backbone:
      bool = true;
      // fall into
    case Token.sidechain:
      setBooleanProperty("hbondsBackbone", bool);
      break;
    case Token.solid:
      bool = true;
      // falll into
    case Token.dotted:
      setBooleanProperty("hbondsSolid", bool);
      break;
    default:
      error(ERROR_invalidArgument);
    }
  }

  private void setPicking() throws ScriptException {
    // set picking
    if (statementLength == 2) {
      setStringProperty("picking", "identify");
      return;
    }
    // set picking @{"xxx"} or some large length, ignored
    if (statementLength > 4 || tokAt(2) == Token.string) {
      setStringProperty("picking", stringSetting(2, false));
      return;
    }
    int i = 2;
    // set picking select ATOM|CHAIN|GROUP|MOLECULE|MODEL|SITE
    // set picking measure ANGLE|DISTANCE|TORSION
    // set picking spin fps
    String type = "SELECT";
    switch (getToken(2).tok) {
    case Token.select:
    case Token.measure:
    case Token.spin:
      if (checkLength34() == 4) {
        type = parameterAsString(2).toUpperCase();
        if (type.equals("SPIN"))
          setIntProperty("pickingSpinRate", intParameter(3));
        else
          i = 3;
      }
    case Token.delete:
      break;
    default:
      checkLength(3);
    }

    // set picking on
    // set picking normal
    // set picking identify
    // set picking off
    // set picking select
    // set picking bonds
    // set picking dragselected

    String str = parameterAsString(i);
    switch (getToken(i).tok) {
    case Token.on:
    case Token.normal:
      str = "identify";
      break;
    case Token.off:
    case Token.none:
      str = "off";
      break;
    case Token.select:
      str = "atom";
      break;
    case Token.label:
      str = "label";
      break;
    case Token.bonds: // not implemented
      str = "bond";
      break;
    case Token.delete:
      checkLength(4);
      if (tokAt(3) != Token.bonds)
        error(ERROR_invalidArgument);
      str = "deleteBond";
      break;
    }
    int mode = ((mode = str.indexOf("_")) >= 0 ? mode : str.length());
    mode = ActionManager.getPickingMode(str.substring(0, mode));
    if (mode < 0)
      error(ERROR_unrecognizedParameter, "SET PICKING " + type, str);
    setStringProperty("picking", str);
  }

  private void setPickingStyle() throws ScriptException {
    if (statementLength > 4 || tokAt(2) == Token.string) {
      setStringProperty("pickingStyle", stringSetting(2, false));
      return;
    }
    int i = 2;
    boolean isMeasure = false;
    String type = "SELECT";
    switch (getToken(2).tok) {
    case Token.measure:
      isMeasure = true;
      type = "MEASURE";
      // fall through
    case Token.select:
      if (checkLength34() == 4)
        i = 3;
      break;
    default:
      checkLength(3);
    }
    String str = parameterAsString(i);
    switch (getToken(i).tok) {
    case Token.none:
    case Token.off:
      str = (isMeasure ? "measureoff" : "toggle");
      break;
    case Token.on:
      if (isMeasure)
        str = "measure";
      break;
    }
    if (ActionManager.getPickingStyle(str) < 0)
      error(ERROR_unrecognizedParameter, "SET PICKINGSTYLE " + type, str);
    setStringProperty("pickingStyle", str);
  }

  private void timeout(int index) throws ScriptException {
    // timeout ID "mytimeout" mSec "script"
    // msec < 0 --> repeat indefinitely
    // timeout ID "mytimeout" 1000 // milliseconds
    // timeout ID "mytimeout" 0.1 // seconds
    // timeout ID "mytimeout" OFF
    // timeout OFF
    String name = null;
    String script = null;
    int mSec = 0;
    if (statementLength == index) {
      showString(viewer.showTimeout(null));
      return;
    }
    for (int i = index; i < statementLength; i++)
      switch (getToken(i).tok) {
      case Token.id:
        name = parameterAsString(++i);
        break;
      case Token.off:
        break;
      case Token.integer:
        mSec = intParameter(i);
        break;
      case Token.decimal:
        mSec = (int) (floatParameter(i) * 1000);
        break;
      default:
        if (name == null)
          name = parameterAsString(i);
        else if (script == null)
          script = parameterAsString(i);
        else
          error(ERROR_invalidArgument);
        break;
      }
    if (!isSyntaxCheck)
      viewer.setTimeout(name, mSec, script);
  }

  private void setUserColors() throws ScriptException {
    List v = new ArrayList();
    for (int i = 2; i < statementLength; i++) {
      int argb = getArgbParam(i);
      v.add(new Integer(argb));
      i = iToken;
    }
    if (isSyntaxCheck)
      return;
    int n = v.size();
    int[] scale = new int[n];
    for (int i = n; --i >= 0;)
      scale[i] = ((Integer) v.get(i)).intValue();
    viewer.setUserScale(scale);
  }

  private void setVariable(int pt, int ptMax, String key, int setType)
      throws ScriptException {
    BitSet bs = null;
    String propertyName = "";
    int tokProperty = Token.nada;
    boolean isArrayItem = (setType == '[');
    boolean settingProperty = false;
    boolean isExpression = false;
    boolean settingData = (key.startsWith("property_"));
    ScriptVariable t = (settingData ? null : getContextVariableAsVariable(key));
    boolean isUserVariable = (t != null);
    if (pt > 0 && tokAt(pt - 1) == Token.expressionBegin) {
      bs = atomExpression(pt - 1);
      pt = iToken + 1;
      isExpression = true;
    }
    if (tokAt(pt) == Token.per) {
      settingProperty = true;
      ScriptVariable token = getBitsetPropertySelector(++pt, true);
      if (token == null)
        error(ERROR_invalidArgument);
      if (tokAt(++pt) != Token.opEQ)
        error(ERROR_invalidArgument);
      pt++;
      tokProperty = token.intValue;
      propertyName = (String) token.value;
    }
    if (isExpression && !settingProperty)
      error(ERROR_invalidArgument);

    // get value

    List v = (List) parameterExpression(pt, ptMax, key, true, true, -1,
        isArrayItem, null, null);
    int nv = v.size();
    if (nv == 0 || !isArrayItem && nv > 1 || isArrayItem
        && (nv < 3 || nv % 2 != 1))
      error(ERROR_invalidArgument);
    if (isSyntaxCheck)
      return;
    // x[3][4] = ??
    ScriptVariable tv = (ScriptVariable) v.get(isArrayItem ? v.size() - 1 : 0);

    // create user variable if needed for list now, so we can do the copying

    boolean needVariable = (!isUserVariable && !isExpression && !settingData && (isArrayItem
        || settingProperty || !(tv.value instanceof String
        || tv.tok == Token.integer || tv.value instanceof Integer
        || tv.value instanceof Float || tv.value instanceof Boolean)));

    if (needVariable) {
      if (key.startsWith("_"))
        error(ERROR_invalidArgument, key);
      t = viewer.getOrSetNewVariable(key, true);
      isUserVariable = true;
    }

    if (isArrayItem) {
      ScriptVariable tnew = (new ScriptVariable()).set(tv, false);
      int nParam = v.size() / 2;
      for (int i = 0; i < nParam; i++) {
        boolean isLast = (i + 1 == nParam);
        ScriptVariable vv = (ScriptVariable) v.get(i * 2);
        // stack is selector [ selector [ selector [ ... VALUE
        if (t.tok == Token.bitset) {
          t.tok = Token.hash;
          t.value = new Hashtable();
        }
        if (t.tok == Token.hash) {
          String hkey = ScriptVariable.sValue(vv);
          Map tmap = (Map) t.value;
          if (isLast) {
            tmap.put(hkey, tnew);
            break;
          }
          t = (ScriptVariable) tmap.get(hkey);
        } else {
          int ipt = ScriptVariable.iValue(vv);
          switch (t.tok) {
          case Token.varray:
            List list = t.getList();
            if (ipt > list.size() || isLast)
              break;
            if (ipt <= 0)
              ipt = list.size() + ipt;
            if (--ipt < 0)
              ipt = 0;
            t = (ScriptVariable) list.get(ipt);
            continue;
          case Token.matrix3f:
          case Token.matrix4f:
            // check for row/column replacement
            int dim = (t.tok == Token.matrix3f ? 3 : 4);
            if (nParam == 1 && Math.abs(ipt) >= 1 && Math.abs(ipt) <= dim
                && tnew.tok == Token.varray && tnew.getList().size() == dim)
              break;
            if (nParam == 2) {
              int ipt2 = ScriptVariable.iValue((ScriptVariable) v.get(2));
              if (ipt2 >= 1 && ipt2 <= dim
                  && (tnew.tok == Token.integer || tnew.tok == Token.decimal)) {
                i++;
                ipt = ipt * 10 + ipt2;
                break;
              }
            }
            // change to an array and continue;
            t.toArray();
            --i;
            continue;
          }
          t.setSelectedValue(ipt, tnew);
          break;
        }
      }
      return;
    }
    if (settingProperty) {
      if (!isExpression) {
        bs = ScriptVariable.getBitSet(t, true);
        if (bs == null)
          error(ERROR_invalidArgument);
      }
      if (propertyName.startsWith("property_")) {
        viewer.setData(propertyName, new Object[] {
            propertyName,
            tv.tok == Token.varray ? ScriptVariable.flistValue(tv, viewer
                .getAtomCount()) : (Object) ScriptVariable.sValue(tv),
            BitSetUtil.copy(bs) }, viewer.getAtomCount(), 0, 0,
            tv.tok == Token.varray ? Integer.MAX_VALUE : Integer.MIN_VALUE, 0);
        return;
      }
      setBitsetProperty(bs, tokProperty, ScriptVariable.iValue(tv),
          ScriptVariable.fValue(tv), tv);
      return;
    }

    if (isUserVariable) {
      t.set(tv, false);
      return;
    }

    Object vv = ScriptVariable.oValue(tv);

    if (key.startsWith("property_")) {
      int n = viewer.getAtomCount();
      if (tv.tok == Token.varray)
        vv = ScriptVariable.sValue(tv);
      viewer.setData(key, new Object[] { key, "" + vv,
          BitSetUtil.copy(viewer.getSelectionSet(false)) }, n, 0, 0,
          Integer.MIN_VALUE, 0);
      return;
    }
    String str;
    if (vv instanceof Boolean) {
      setBooleanProperty(key, ((Boolean) vv).booleanValue());
    } else if (vv instanceof Integer) {
      setIntProperty(key, ((Integer) vv).intValue());
    } else if (vv instanceof Float) {
      setFloatProperty(key, ((Float) vv).floatValue());
    } else if (vv instanceof String) {
      setStringProperty(key, (String) vv);
    } else if (vv instanceof BondSet) {
      setStringProperty(key, Escape.escape((BitSet) vv, false));
    } else if (vv instanceof BitSet) {
      setStringProperty(key, Escape.escape((BitSet) vv));
    } else if (vv instanceof Point3f) {
      str = Escape.escape((Point3f) vv);
      setStringProperty(key, str);
    } else if (vv instanceof Point4f) {
      str = Escape.escape((Point4f) vv);
      setStringProperty(key, str);
    } else {
      Logger.error("ERROR -- return from propertyExpression was " + vv);
    }
  }

  private void axes(int index) throws ScriptException {
    // axes (index==1) or set axes (index==2)
    TickInfo tickInfo = checkTicks(index, true, true, false);
    index = iToken + 1;
    int tok = tokAt(index);
    String type = optParameterAsString(index).toLowerCase();
    if (statementLength == index + 1
        && Parser.isOneOf(type, "window;unitcell;molecular")) {
      setBooleanProperty("axes" + type, true);
      return;
    }
    switch (tok) {
    case Token.center:
      Point3f center = centerParameter(index + 1);
      setShapeProperty(JmolConstants.SHAPE_AXES, "origin", center);
      checkLast(iToken);
      return;
    case Token.scale:
      setFloatProperty("axesScale", floatParameter(checkLast(++index)));
      return;
    case Token.label:
      switch (tok = tokAt(index + 1)) {
      case Token.off:
      case Token.on:
        checkLength(index + 2);
        setShapeProperty(JmolConstants.SHAPE_AXES, "labels"
            + (tok == Token.on ? "On" : "Off"), null);
        return;
      }
      if (statementLength == index + 7) {
        // axes labels "X" "Y" "Z" "-X" "-Y" "-Z"
        setShapeProperty(JmolConstants.SHAPE_AXES, "labels", new String[] {
            parameterAsString(++index), parameterAsString(++index),
            parameterAsString(++index), parameterAsString(++index),
            parameterAsString(++index), parameterAsString(++index) });
      } else {
        checkLength(index + 4);
        setShapeProperty(JmolConstants.SHAPE_AXES, "labels", new String[] {
            parameterAsString(++index), parameterAsString(++index),
            parameterAsString(++index) });
      }
      return;
    }
    // axes position [x y %]
    if (type.equals("position")) {
      Point3f xyp;
      if (tokAt(++index) == Token.off) {
        xyp = new Point3f();
      } else {
        xyp = xypParameter(index);
        if (xyp == null)
          error(ERROR_invalidArgument);
        index = iToken;
      }
      setShapeProperty(JmolConstants.SHAPE_AXES, "position", xyp);
      return;
    }
    int mad = getSetAxesTypeMad(index);
    if (isSyntaxCheck)
      return;
    setObjectMad(JmolConstants.SHAPE_AXES, "axes", mad);
    if (tickInfo != null)
      setShapeProperty(JmolConstants.SHAPE_AXES, "tickInfo", tickInfo);
  }

  private void boundbox(int index) throws ScriptException {
    TickInfo tickInfo = checkTicks(index, false, true, false);
    index = iToken + 1;
    float scale = 1;
    if (tokAt(index) == Token.scale) {
      scale = floatParameter(++index);
      if (!isSyntaxCheck && scale == 0)
        error(ERROR_invalidArgument);
      index++;
      if (index == statementLength) {
        if (!isSyntaxCheck)
          viewer.setBoundBox(null, null, true, scale);
        return;
      }
    }
    boolean byCorner = (tokAt(index) == Token.corners);
    if (byCorner)
      index++;
    if (isCenterParameter(index)) {
      expressionResult = null;
      int index0 = index;
      Point3f pt1 = centerParameter(index);
      index = iToken + 1;
      if (byCorner || isCenterParameter(index)) {
        // boundbox CORNERS {expressionOrPoint1} {expressionOrPoint2}
        // boundbox {expressionOrPoint1} {vector}
        Point3f pt2 = (byCorner ? centerParameter(index) : getPoint3f(index,
            true));
        index = iToken + 1;
        if (!isSyntaxCheck)
          viewer.setBoundBox(pt1, pt2, byCorner, scale);
      } else if (expressionResult != null && expressionResult instanceof BitSet) {
        // boundbox {expression}
        if (!isSyntaxCheck)
          viewer.calcBoundBoxDimensions((BitSet) expressionResult, scale);
      } else if (expressionResult == null && tokAt(index0) == Token.dollarsign) {
        if (isSyntaxCheck)
          return;
        Point3f[] bbox = getObjectBoundingBox(objectNameParameter(++index0));
        if (bbox == null)
          error(ERROR_invalidArgument);
        viewer.setBoundBox(bbox[0], bbox[1], true, scale);
        index = iToken + 1;
      } else {
        error(ERROR_invalidArgument);
      }
      if (index == statementLength)
        return;
    }
    int mad = getSetAxesTypeMad(index);
    if (isSyntaxCheck)
      return;
    if (tickInfo != null)
      setShapeProperty(JmolConstants.SHAPE_BBCAGE, "tickInfo", tickInfo);
    setObjectMad(JmolConstants.SHAPE_BBCAGE, "boundbox", mad);
  }

  private TickInfo checkTicks(int index, boolean allowUnitCell,
                              boolean allowScale, boolean allowFirst)
      throws ScriptException {
    iToken = index - 1;
    if (tokAt(index) != Token.ticks)
      return null;
    TickInfo tickInfo;
    String str = " ";
    switch (tokAt(index + 1)) {
    case Token.x:
    case Token.y:
    case Token.z:
      str = parameterAsString(++index).toLowerCase();
      break;
    case Token.identifier:
      error(ERROR_invalidArgument);
    }
    if (tokAt(++index) == Token.none) {
      tickInfo = new TickInfo(null);
      tickInfo.type = str;
      iToken = index;
      return tickInfo;
    }
    tickInfo = new TickInfo((Point3f) getPointOrPlane(index, false, true,
        false, false, 3, 3));
    if (coordinatesAreFractional || tokAt(iToken + 1) == Token.unitcell) {
      tickInfo.scale = new Point3f(Float.NaN, Float.NaN, Float.NaN);
      allowScale = false;
    }
    if (tokAt(iToken + 1) == Token.unitcell)
      iToken++;
    tickInfo.type = str;
    if (tokAt(iToken + 1) == Token.format)
      tickInfo.tickLabelFormats = stringParameterSet(iToken + 2);
    if (!allowScale)
      return tickInfo;
    if (tokAt(iToken + 1) == Token.scale) {
      if (isFloatParameter(iToken + 2)) {
        float f = floatParameter(iToken + 2);
        tickInfo.scale = new Point3f(f, f, f);
      } else {
        tickInfo.scale = getPoint3f(iToken + 2, true);
      }
    }
    if (allowFirst)
      if (tokAt(iToken + 1) == Token.first)
        tickInfo.first = floatParameter(iToken + 2);
    // POINT {x,y,z} reference point not implemented
    //if (tokAt(iToken + 1) == Token.point)
    // tickInfo.reference = centerParameter(iToken + 2);
    return tickInfo;
  }

  private void unitcell(int index) throws ScriptException {
    int icell = Integer.MAX_VALUE;
    int mad = Integer.MAX_VALUE;
    Point3f pt = null;
    TickInfo tickInfo = checkTicks(index, true, false, false);
    index = iToken;
    if (statementLength == index + 2) {
      if (getToken(index + 1).tok == Token.integer
          && intParameter(index + 1) >= 111)
        icell = intParameter(++index);
    } else if (statementLength > index + 1) {
      pt = (Point3f) getPointOrPlane(++index, false, true, false, true, 3, 3);
      index = iToken;
    }
    mad = getSetAxesTypeMad(++index);
    checkLast(iToken);
    if (isSyntaxCheck)
      return;
    if (icell != Integer.MAX_VALUE)
      viewer.setCurrentUnitCellOffset(icell);
    setObjectMad(JmolConstants.SHAPE_UCCAGE, "unitCell", mad);
    if (pt != null)
      viewer.setCurrentUnitCellOffset(pt);
    if (tickInfo != null)
      setShapeProperty(JmolConstants.SHAPE_UCCAGE, "tickInfo", tickInfo);
  }

  private void frank(int index) throws ScriptException {
    setBooleanProperty("frank", booleanParameter(index));
  }

  private void selectionHalo(int pt) throws ScriptException {
    boolean showHalo = false;
    switch (pt == statementLength ? Token.on : getToken(pt).tok) {
    case Token.on:
    case Token.selected:
      showHalo = true;
    case Token.off:
    case Token.none:
    case Token.normal:
      setBooleanProperty("selectionHalos", showHalo);
      break;
    default:
      error(ERROR_invalidArgument);
    }
  }

  private void save() throws ScriptException {
    if (statementLength > 1) {
      String saveName = optParameterAsString(2);
      switch (tokAt(1)) {
      case Token.rotation:
        if (!isSyntaxCheck)
          viewer.saveOrientation(saveName);
        return;
      case Token.orientation:
        if (!isSyntaxCheck)
          viewer.saveOrientation(saveName);
        return;
      case Token.bonds:
        if (!isSyntaxCheck)
          viewer.saveBonds(saveName);
        return;
      case Token.state:
        if (!isSyntaxCheck)
          viewer.saveState(saveName);
        return;
      case Token.structure:
        if (!isSyntaxCheck)
          viewer.saveStructure(saveName);
        return;
      case Token.coord:
        if (!isSyntaxCheck)
          viewer.saveCoordinates(saveName, viewer.getSelectionSet(false));
        return;
      case Token.selection:
        if (!isSyntaxCheck)
          viewer.saveSelection(saveName);
        return;
      }
    }
    error(ERROR_what, "SAVE",
        "bonds? coordinates? orientation? selection? state? structure?");
  }

  private void restore() throws ScriptException {
    // restore orientation name time
    if (statementLength > 1) {
      String saveName = optParameterAsString(2);
      if (getToken(1).tok != Token.orientation)
        checkLength23();
      float timeSeconds;
      switch (getToken(1).tok) {
      case Token.rotation:
        timeSeconds = (statementLength > 3 ? floatParameter(3) : 0);
        if (timeSeconds < 0)
          error(ERROR_invalidArgument);
        if (!isSyntaxCheck)
          viewer.restoreRotation(saveName, timeSeconds);
        return;
      case Token.orientation:
        timeSeconds = (statementLength > 3 ? floatParameter(3) : 0);
        if (timeSeconds < 0)
          error(ERROR_invalidArgument);
        if (!isSyntaxCheck)
          viewer.restoreOrientation(saveName, timeSeconds);
        return;
      case Token.bonds:
        if (!isSyntaxCheck)
          viewer.restoreBonds(saveName);
        return;
      case Token.coord:
        if (isSyntaxCheck)
          return;
        String script = viewer.getSavedCoordinates(saveName);
        if (script == null)
          error(ERROR_invalidArgument);
        runScript(script);
        viewer.checkCoordinatesChanged();
        return;
      case Token.state:
        if (isSyntaxCheck)
          return;
        String state = viewer.getSavedState(saveName);
        if (state == null)
          error(ERROR_invalidArgument);
        runScript(state);
        return;
      case Token.structure:
        if (isSyntaxCheck)
          return;
        String shape = viewer.getSavedStructure(saveName);
        if (shape == null)
          error(ERROR_invalidArgument);
        runScript(shape);
        return;
      case Token.selection:
        if (!isSyntaxCheck)
          viewer.restoreSelection(saveName);
        return;
      }
    }
    error(ERROR_what, "RESTORE",
        "bonds? coords? orientation? selection? state? structure?");
  }

  String write(Token[] args) throws ScriptException {
    int pt = 0, pt0 = 0;
    boolean isCommand, isShow;
    if (args == null) {
      args = statement;
      pt = pt0 = 1;
      isCommand = true;
      isShow = (viewer.isApplet() && !viewer.isSignedApplet());
    } else {
      isCommand = false;
      isShow = true;
    }
    int argCount = (isCommand ? statementLength : args.length);
    int tok = (isCommand && args.length == 1 ? Token.clipboard
        : tokAt(pt, args));
    int len = 0;
    int width = -1;
    int height = -1;
    int quality = Integer.MIN_VALUE;
    String driverList = viewer.getExportDriverList();
    String type = "SPT";
    String data = "";
    String type2 = "";
    String fileName = null;
    String localPath = null;
    String remotePath = null;
    String val = null;
    String msg = null;
    String[] fullPath = new String[1];
    boolean isCoord = false;
    boolean isExport = false;
    boolean isImage = false;
    BitSet bsFrames = null;
    if (tok == Token.string) {
      Token t = Token.getTokenFromName(ScriptVariable.sValue(args[pt])
          .toLowerCase());
      if (t != null)
        tok = t.tok;
    }
    switch (tok) {
    case Token.quaternion:
    case Token.ramachandran:
    case Token.property:
      msg = plot(args);
      if (!isCommand)
        return msg;
      break;
    case Token.pointgroup:
      type = "PGRP";
      pt++;
      type2 = ScriptVariable.sValue(tokenAt(pt, args)).toLowerCase();
      if (type2.equals("draw"))
        pt++;
      break;
    case Token.function:
      type = "FUNCS";
      pt++;
      break;
    case Token.coord:
    case Token.data:
      type = ScriptVariable.sValue(tokenAt(++pt, args)).toLowerCase();
      type = "data";
      isCoord = true;
      break;
    case Token.state:
    case Token.script:
      val = ScriptVariable.sValue(tokenAt(++pt, args)).toLowerCase();
      while (val.equals("localpath") || val.equals("remotepath")) {
        if (val.equals("localpath"))
          localPath = ScriptVariable.sValue(tokenAt(++pt, args));
        else
          remotePath = ScriptVariable.sValue(tokenAt(++pt, args));
        val = ScriptVariable.sValue(tokenAt(++pt, args)).toLowerCase();
      }
      type = "SPT";
      break;
    case Token.mo:
      type = "MO";
      pt++;
      break;
    case Token.pmesh:
      type = "PMESH";
      pt++;
      break;
    case Token.mesh:
      type = "MESH";
      pt++;
      break;
    case Token.isosurface:
      type = "ISO";
      pt++;
      break;
    case Token.history:
      type = "HIS";
      pt++;
      break;
    case Token.var:
      type = "VAR";
      pt += 2;
      break;
    case Token.file:
      type = "FILE";
      pt++;
      break;
    case Token.jmol:
      type = "ZIPALL";
      pt++;
      break;
    case Token.image:
    case Token.identifier:
    case Token.string:
    case Token.frame:
      type = ScriptVariable.sValue(tokenAt(pt, args)).toLowerCase();
      if (tok == Token.image) {
        pt++;
      } else if (tok == Token.frame) {
        BitSet bsAtoms;
        if (pt + 1 < argCount && args[++pt].tok == Token.expressionBegin
            || args[pt].tok == Token.bitset) {
          bsAtoms = atomExpression(args, pt, 0, true, false, true, true);
          pt = iToken + 1;
        } else {
          bsAtoms = viewer.getModelUndeletedAtomsBitSet(-1);
        }
        if (!isSyntaxCheck)
          bsFrames = viewer.getModelBitSet(bsAtoms, true);
      } else if (Parser.isOneOf(type, driverList.toLowerCase())) {
        // povray, maya, vrml, idtf
        pt++;
        type = type.substring(0, 1).toUpperCase() + type.substring(1);
        isExport = true;
        if (isCommand)
          fileName = "Jmol." + type;
      } else if (type.equals("menu")) {
        pt++;
        type = "MENU";
      } else if (type.equals("zip")) {
        type = "ZIP";
        pt++;
      } else if (type.equals("zipall")) {
        type = "ZIPALL";
        pt++;
      } else {
        type = "(image)";
      }
      if (tokAt(pt, args) == Token.integer) {
        width = ScriptVariable.iValue(tokenAt(pt++, args));
        height = ScriptVariable.iValue(tokenAt(pt++, args));
      }
      break;
    }

    if (msg == null) {
      val = ScriptVariable.sValue(tokenAt(pt, args));
      if (val.equalsIgnoreCase("clipboard")) {
        if (isSyntaxCheck)
          return "";
        // if (isApplet)
        // evalError(GT._("The {0} command is not available for the applet.",
        // "WRITE CLIPBOARD"));
      } else if (Parser.isOneOf(val.toLowerCase(), "png;jpg;jpeg;jpg64;jpeg64")
          && tokAt(pt + 1, args) == Token.integer) {
        quality = ScriptVariable.iValue(tokenAt(++pt, args));
      } else if (Parser.isOneOf(val.toLowerCase(), "xyz;mol;sdf;v2000;v3000;pdb;cml")) {
        type = val.toUpperCase();
        if (pt + 1 == argCount)
          pt++;
      }

      // write [image|history|state] clipboard

      // write [optional image|history|state] [JPG quality|JPEG quality|JPG64
      // quality|PNG|PPM|SPT] "filename"
      // write script "filename"
      // write isosurface t.jvxl

      if (type.equals("(image)")
          && Parser.isOneOf(val.toUpperCase(),
              "GIF;JPG;JPG64;JPEG;JPEG64;PNG;PPM")) {
        type = val.toUpperCase();
        pt++;
      }

      if (pt + 2 == argCount) {
        data = ScriptVariable.sValue(tokenAt(++pt, args));
        if (data.length() > 0 && data.charAt(0) != '.')
          type = val.toUpperCase();
      }
      switch (tokAt(pt, args)) {
      case Token.nada:
      case Token.clipboard:
        break;
      case Token.identifier:
      case Token.string:
        fileName = ScriptVariable.sValue(tokenAt(pt, args));
        if (pt == argCount - 3 && tokAt(pt + 1, args) == Token.per) {
          // write filename.xxx gets separated as filename .spt
          // write isosurface filename.xxx also
          fileName += "." + ScriptVariable.sValue(tokenAt(pt + 2, args));
        }
        if (type != "VAR" && pt == pt0)
          type = "image";
        else if (fileName.length() > 0 && fileName.charAt(0) == '.'
            && (pt == pt0 + 1 || pt == pt0 + 2)) {
          fileName = ScriptVariable.sValue(tokenAt(pt - 1, args)) + fileName;
          if (type != "VAR" && pt == pt0 + 1)
            type = "image";
        }
        if (fileName.equalsIgnoreCase("clipboard"))
          fileName = null;
        break;
      default:
        error(ERROR_invalidArgument);
      }
      if (type.equals("image") || type.equals("frame")) {
        if (fileName != null && fileName.indexOf(".") >= 0)
          type = fileName.substring(fileName.lastIndexOf(".") + 1)
              .toUpperCase();
        else
          type = "JPG";
        if (type.equals("MNU"))
          type = "MENU";
        else if (type.equals("WRL") || type.equals("VRML")) {
          type = "Vrml";
          isExport = true;
        } else if (type.equals("X3D")) {
          type = "X3d";
          isExport = true;
        } else if (type.equals("IDTF")) {
          type = "Idtf";
          isExport = true;
        } else if (type.equals("MA")) {
          type = "Maya";
          isExport = true;
        } else if (type.equals("JVXL")) {
          type = "ISOX";
        } else if (type.equals("XJVXL")) {
          type = "ISOX";
        } else if (type.equals("MESH")) {
          type = "MESH";
        } else if (type.equals("JMOL")) {
          type = "ZIPALL";
        }
      }
      if (type.equals("data")) {
        if (fileName != null && fileName.indexOf(".") >= 0)
          type = fileName.substring(fileName.lastIndexOf(".") + 1)
              .toUpperCase();
        else
          type = "XYZ";
      }
      isImage = Parser.isOneOf(type, "GIF;JPEG64;JPEG;JPG64;JPG;PPM;PNG");
      if (isImage && isShow)
        type = "JPG64";
      else if (!isImage
          && !isExport
          && !Parser
              .isOneOf(
                  type,
                  "ZIP;ZIPALL;SPT;HIS;MO;ISO;ISOX;MESH;PMESH;VAR;FILE;CML;XYZ;MENU;MOL;PDB;PGRP;QUAT;RAMA;V2000;V3000;SDF;FUNCS;"))
        error(
            ERROR_writeWhat,
            "COORDS|FILE|FUNCTIONS|HISTORY|IMAGE|ISOSURFACE|JMOL|MENU|MO|POINTGROUP|QUATERNION [w,x,y,z] [derivative]"
                + "|RAMACHANDRAN|SPT|STATE|VAR x|ZIP|ZIPALL  CLIPBOARD",
            "CML|GIF|JPG|JPG64|JVXL|MESH|MOL|PDB|SDF|V2000|V3000|PMESH|PNG|PPM|SPT|XJVXL|XYZ|ZIP"
                + driverList.toUpperCase().replace(';', '|'));
      if (isSyntaxCheck)
        return "";
      data = type.intern();
      Object bytes = null;
      boolean doDefer = false;
      if (isExport) {
        // POV-Ray uses a BufferedWriter instead of a StringBuffer.
        // todo -- there's no reason this data has to be done this way.
        // we could send all of them out to file directly
        fullPath[0] = fileName;
        data = viewer.generateOutput(data, isCommand || fileName != null ? fullPath : null, width, height);
        if (data == null || data.length() == 0)
          return "";
        if (!isCommand)
          return data;
        if ((type.equals("Povray") || type.equals("Idtf"))
            && fullPath[0] != null) {
          String ext = (type.equals("Idtf") ? ".tex" : ".ini");
          fileName = fullPath[0] + ext;
          msg = viewer.createImage(fileName, ext, data, Integer.MIN_VALUE, 0,
              0, null, fullPath);
          if (type.equals("Idtf"))
            data = data.substring(0, data.indexOf("\\begin{comment}"));
          data = "Created " + fullPath[0] + ":\n\n" + data;
        } else {
          msg = data;
        }
        if (msg != null) {
          if (!msg.startsWith("OK"))
            evalError(msg, null);
          scriptStatusOrBuffer(data);
        }
        return "";

      } else if (data == "MENU") {
        data = viewer.getMenu("");
      } else if (data == "PGRP") {
        data = viewer
            .getPointGroupAsString(type2.equals("draw"), null, 0, 1.0f);
      } else if (data == "PDB") {
        if (isShow) {
          data = viewer.getPdbData(null, null);
        } else {
          doDefer = true;
          /*
           * OutputStream os = viewer.getOutputStream(fileName, fullPath); msg =
           * viewer.getPdbData(null, new BufferedOutputStream(os)); if (msg !=
           * null) msg = "OK " + msg + " " + fullPath[0]; try { os.close(); }
           * catch (IOException e) { // TODO }
           */
        }
      } else if (data == "FILE") {
        if (isShow)
          data = viewer.getCurrentFileAsString();
        else
          doDefer = true;
        if ("?".equals(fileName))
          fileName = "?Jmol." + viewer.getParameter("_fileType");
      } else if ((data == "SDF" || data == "MOL" || data == "V2000" || data == "V3000") && isCoord) {
        data = viewer.getModelExtract("selected", true, data == "SDF", data == "V3000");
        if (data.startsWith("ERROR:"))
          bytes = data;
      } else if (data == "XYZ" || data == "MOL" || data == "SDF" ||  data == "V2000" || data == "V3000" || data == "CML") {
        data = viewer.getData("selected", data);
        if (data.startsWith("ERROR:"))
          bytes = data;
      } else if (data == "FUNCS") {
        data = viewer.getFunctionCalls(null);
        type = "TXT";
      } else if (data == "VAR") {
        data = ScriptVariable.sValue((ScriptVariable) getParameter(
            ScriptVariable.sValue(tokenAt(isCommand ? 2 : 1, args)), Token.variable));
        type = "TXT";
      } else if (data == "SPT") {
        if (isCoord) {
          BitSet tainted = viewer.getTaintedAtoms(AtomCollection.TAINT_COORD);
          viewer.setAtomCoordRelative(new Point3f(0, 0, 0), null);
          data = (String) viewer.getProperty("string", "stateInfo", null);
          viewer.setTaintedAtoms(tainted, AtomCollection.TAINT_COORD);
        } else {
          data = (String) viewer.getProperty("string", "stateInfo", null);
          if (localPath != null || remotePath != null)
            data = FileManager.setScriptFileReferences(data, localPath,
                remotePath, null);
        }
      } else if (data == "ZIP" || data == "ZIPALL") {
        data = (String) viewer.getProperty("string", "stateInfo", null);
        bytes = viewer.createImage(fileName, type, data, Integer.MIN_VALUE, -1,
            -1);
      } else if (data == "HIS") {
        data = viewer.getSetHistory(Integer.MAX_VALUE);
        type = "SPT";
      } else if (data == "MO") {
        data = getMoJvxl(Integer.MAX_VALUE);
        type = "XJVXL";
      } else if (data == "PMESH") {
        if ((data = getIsosurfaceJvxl(true, JmolConstants.SHAPE_PMESH)) == null)
          error(ERROR_noData);
        type = "XJVXL";
      } else if (data == "ISO" || data == "ISOX" || data == "MESH") {
        if ((data = getIsosurfaceJvxl(data == "MESH",
            JmolConstants.SHAPE_ISOSURFACE)) == null)
          error(ERROR_noData);
        type = (data.indexOf("<?xml") >= 0 ? "XJVXL" : "JVXL");
        if (!isShow)
          showString((String) getShapeProperty(JmolConstants.SHAPE_ISOSURFACE,
              "jvxlFileInfo"));
      } else {
        // image
        len = -1;
        if (quality < 0)
          quality = -1;
      }
      if (data == null && !doDefer)
        data = "";
      if (len == 0 && !doDefer)
        len = (bytes == null ? data.length()
            : bytes instanceof String ? ((String) bytes).length()
                : ((byte[]) bytes).length);
      if (isImage) {
        refresh();
        if (width < 0)
          width = viewer.getScreenWidth();
        if (height < 0)
          height = viewer.getScreenHeight();
      }
      if (!isCommand)
        return data;
      if (isShow) {
        showString(data);
        return "";
      }
      if (bytes != null && bytes instanceof String) {
        // load error or completion message here
        scriptStatusOrBuffer((String) bytes);
        return (String) bytes;
      }
      if (bytes == null && (!isImage || fileName != null))
        bytes = data;
      if (doDefer)
        msg = viewer.streamFileData(fileName, type, type2, 0, null);
      else
        msg = viewer.createImage(fileName, type, bytes, quality, width, height,
            bsFrames, fullPath);
    }
    if (msg != null) {
      if (!msg.startsWith("OK"))
        evalError(msg, null);
      scriptStatusOrBuffer(msg
          + (isImage ? "; width=" + width + "; height=" + height : ""));
      return msg;
    }
    return "";
  }

  private void show() throws ScriptException {
    String value = null;
    String str = parameterAsString(1);
    String msg = null;
    String name = null;
    int len = 2;
    Token token = getToken(1);
    int tok = (token instanceof ScriptVariable ? Token.nada : token.tok);
    if (tok == Token.string) {
      token = Token.getTokenFromName(str.toLowerCase());
      if (token != null)
        tok = token.tok;
    }
    if (tok != Token.symop && tok != Token.state)
      checkLength(-3);
    if (statementLength == 2 && str.indexOf("?") >= 0) {
      showString(viewer.getAllSettings(str.substring(0, str.indexOf("?"))));
      return;
    }
    switch (tok) {
    case Token.nada:
      msg = Escape.escape(((ScriptVariable) theToken).value);
      break;
    case Token.dssp:
      checkLength(2);
      if (!isSyntaxCheck)
        msg = viewer.calculateStructures(null, true, false);
      break;
    case Token.smiles:
      checkLength(2);
      if (!isSyntaxCheck)
        msg = viewer.getSmiles(0, 0, viewer.getSelectionSet(false), false, true,
            false, false);
      break;
    case Token.symop:
      if (statementLength > 3) {
        Point3f pt1 = centerParameter(2);
        Point3f pt2 = centerParameter(++iToken);
        if (!isSyntaxCheck)
          msg = viewer.getSymmetryOperation(null, 0, pt1, pt2, false);
        len = ++iToken;
      } else {
        int iop = (checkLength23() == 2 ? 0 : intParameter(2));
        if (!isSyntaxCheck)
          msg = viewer.getSymmetryOperation(null, iop, null, null, false);
        len = -3;
      }
      break;
    case Token.vanderwaals:
      if (statementLength == 2) {
        if (!isSyntaxCheck)
          showString(viewer.getDefaultVdwTypeNameOrData(-1));
        return;
      }
      int vdwType = JmolConstants.getVdwType(parameterAsString(2));
      if (vdwType == JmolConstants.VDW_UNKNOWN)
        error(ERROR_invalidArgument);
      if (!isSyntaxCheck)
        showString(viewer.getDefaultVdwTypeNameOrData(vdwType));
      return;
    case Token.function:
      checkLength23();
      if (!isSyntaxCheck)
        showString(viewer.getFunctionCalls(optParameterAsString(2)));
      return;
    case Token.set:
      checkLength(2);
      if (!isSyntaxCheck)
        showString(viewer.getAllSettings(null));
      return;
    case Token.url:
      // in a new window
      if ((len = statementLength) == 2) {
        if (!isSyntaxCheck)
          viewer.showUrl(getFullPathName());
        return;
      }
      name = parameterAsString(2);
      if (!isSyntaxCheck)
        viewer.showUrl(name);
      return;
    case Token.color:
      str = "defaultColorScheme";
      break;
    case Token.scale3d:
      str = "scaleAngstromsPerInch";
      break;
    case Token.quaternion:
    case Token.ramachandran:
      if (isSyntaxCheck)
        return;
      int modelIndex = viewer.getCurrentModelIndex();
      if (modelIndex < 0)
        error(ERROR_multipleModelsDisplayedNotOK, "show " + theToken.value);
      msg = plot(statement);
      len = statementLength;
      break;
    case Token.trace:
      if (!isSyntaxCheck)
        msg = getContext(false);
      break;
    case Token.colorscheme:
      name = optParameterAsString(2);
      if (name.length() > 0)
        len = 3;
      if (!isSyntaxCheck)
        value = viewer.getColorSchemeList(name);
      break;
    case Token.variables:
      if (!isSyntaxCheck)
        msg = viewer.getVariableList() + getContext(true);
      break;
    case Token.trajectory:
      if (!isSyntaxCheck)
        msg = viewer.getTrajectoryInfo();
      break;
    case Token.historylevel:
      value = "" + commandHistoryLevelMax;
      break;
    case Token.loglevel:
      value = "" + Logger.getLogLevel();
      break;
    case Token.debugscript:
      value = "" + viewer.getDebugScript();
      break;
    case Token.strandcount:
      msg = "set strandCountForStrands "
          + viewer.getStrandCount(JmolConstants.SHAPE_STRANDS)
          + "; set strandCountForMeshRibbon "
          + viewer.getStrandCount(JmolConstants.SHAPE_MESHRIBBON);
      break;
    case Token.timeout:
      msg = viewer.showTimeout((len = statementLength) == 2 ? null
          : parameterAsString(2));
      break;
    case Token.defaultlattice:
      value = Escape.escape(viewer.getDefaultLattice());
      break;
    case Token.minimize:
      if (!isSyntaxCheck)
        msg = viewer.getMinimizationInfo();
      break;
    case Token.axes:
      switch (viewer.getAxesMode()) {
      case JmolConstants.AXES_MODE_UNITCELL:
        msg = "set axesUnitcell";
        break;
      case JmolConstants.AXES_MODE_BOUNDBOX:
        msg = "set axesWindow";
        break;
      default:
        msg = "set axesMolecular";
      }
      break;
    case Token.bondmode:
      msg = "set bondMode " + (viewer.getBondSelectionModeOr() ? "OR" : "AND");
      break;
    case Token.strands:
      if (!isSyntaxCheck)
        msg = "set strandCountForStrands "
            + viewer.getStrandCount(JmolConstants.SHAPE_STRANDS)
            + "; set strandCountForMeshRibbon "
            + viewer.getStrandCount(JmolConstants.SHAPE_MESHRIBBON);
      break;
    case Token.hbond:
      msg = "set hbondsBackbone " + viewer.getHbondsBackbone()
          + ";set hbondsSolid " + viewer.getHbondsSolid();
      break;
    case Token.spin:
      if (!isSyntaxCheck)
        msg = viewer.getSpinState();
      break;
    case Token.ssbond:
      msg = "set ssbondsBackbone " + viewer.getSsbondsBackbone();
      break;
    case Token.display:// deprecated
    case Token.selectionhalos:
      msg = "selectionHalos "
          + (viewer.getSelectionHaloEnabled(false) ? "ON" : "OFF");
      break;
    case Token.hetero:
      msg = "set selectHetero " + viewer.getRasmolSetting(tok);
      break;
    case Token.addhydrogens:
      msg = Escape.escapeArray(viewer.getAdditionalHydrogens(null, true, true,
          null));
      break;
    case Token.hydrogen:
      msg = "set selectHydrogens " + viewer.getRasmolSetting(tok);
      break;
    case Token.ambientpercent:
    case Token.diffusepercent:
    case Token.specular:
    case Token.specularpower:
    case Token.specularexponent:
      if (!isSyntaxCheck)
        msg = viewer.getSpecularState();
      break;
    case Token.save:
      if (!isSyntaxCheck)
        msg = viewer.listSavedStates();
      break;
    case Token.unitcell:
      if (!isSyntaxCheck)
        msg = viewer.getUnitCellInfoText();
      break;
    case Token.coord:
      if ((len = statementLength) == 2) {
        if (!isSyntaxCheck)
          msg = viewer.getCoordinateState(viewer.getSelectionSet(false));
        break;
      }
      String nameC = parameterAsString(2);
      if (!isSyntaxCheck)
        msg = viewer.getSavedCoordinates(nameC);
      break;
    case Token.state:
      if ((len = statementLength) == 2) {
        if (!isSyntaxCheck)
          msg = viewer.getStateInfo();
        break;
      }
      name = parameterAsString(2);
      if (name.equals("/") && (len = statementLength) == 4) {
        name = parameterAsString(3).toLowerCase();
        if (!isSyntaxCheck) {
          String[] info = TextFormat.split(viewer.getStateInfo(), '\n');
          StringBuffer sb = new StringBuffer();
          for (int i = 0; i < info.length; i++)
            if (info[i].toLowerCase().indexOf(name) >= 0)
              sb.append(info[i]).append('\n');
          msg = sb.toString();
        }
        break;
      }
      if (!isSyntaxCheck)
        msg = viewer.getSavedState(name);
      break;
    case Token.structure:
      if ((len = statementLength) == 2) {
        if (!isSyntaxCheck)
          msg = viewer.getProteinStructureState();
        break;
      }
      String shape = parameterAsString(2);
      if (!isSyntaxCheck)
        msg = viewer.getSavedStructure(shape);
      break;
    case Token.data:
      String type = ((len = statementLength) == 3 ? parameterAsString(2) : null);
      if (!isSyntaxCheck) {
        Object[] data = (type == null ? this.data : viewer.getData(type));
        msg = (data == null ? "no data" : "data \""
            + data[0]
            + "\"\n"
            + (data[1] instanceof float[] ? Escape.escape((float[]) data[1],
                false) : data[1] instanceof float[][] ? Escape.escape(
                (float[][]) data[1], false) : "" + data[1]) + "\nend \""
            + data[0] + "\";");
      }
      break;
    case Token.spacegroup:
      Hashtable info = null;
      if ((len = statementLength) == 2) {
        if (!isSyntaxCheck) {
          info = viewer.getSpaceGroupInfo(null);
        }
      } else {
        String sg = parameterAsString(2);
        if (!isSyntaxCheck)
          info = viewer.getSpaceGroupInfo(TextFormat.simpleReplace(sg, "''",
              "\""));
      }
      if (info != null)
        msg = "" + info.get("spaceGroupInfo") + info.get("symmetryInfo");
      break;
    case Token.dollarsign:
      len = 3;
      msg = setObjectProperty();
      break;
    case Token.boundbox:
      if (!isSyntaxCheck) {
        msg = viewer.getBoundBoxCommand(true);
      }
      break;
    case Token.center:
      if (!isSyntaxCheck)
        msg = "center " + Escape.escape(viewer.getRotationCenter());
      break;
    case Token.draw:
      if (!isSyntaxCheck)
        msg = (String) getShapeProperty(JmolConstants.SHAPE_DRAW, "command");
      break;
    case Token.file:
      // as as string
      if (statementLength == 2) {
        if (!isSyntaxCheck)
          msg = viewer.getCurrentFileAsString();
        if (msg == null)
          msg = "<unavailable>";
        break;
      }
      len = 3;
      value = parameterAsString(2);
      if (!isSyntaxCheck)
        msg = viewer.getFileAsString(value);
      break;
    case Token.frame:
      if (tokAt(2) == Token.all && (len = 3) > 0)
        msg = viewer.getModelFileInfoAll();
      else
        msg = viewer.getModelFileInfo();
      break;
    case Token.history:
      int n = ((len = statementLength) == 2 ? Integer.MAX_VALUE
          : intParameter(2));
      if (n < 1)
        error(ERROR_invalidArgument);
      if (!isSyntaxCheck) {
        viewer.removeCommand();
        msg = viewer.getSetHistory(n);
      }
      break;
    case Token.isosurface:
      if (!isSyntaxCheck)
        msg = (String) getShapeProperty(JmolConstants.SHAPE_ISOSURFACE,
            "jvxlDataXml");
      break;
    case Token.mo:
      if (optParameterAsString(2).equalsIgnoreCase("list")) {
        msg = viewer.getMoInfo(-1);
        len = 3;
      } else {
        int ptMO = ((len = statementLength) == 2 ? Integer.MIN_VALUE
            : intParameter(2));
        if (!isSyntaxCheck)
          msg = getMoJvxl(ptMO);
      }
      break;
    case Token.model:
      if (!isSyntaxCheck)
        msg = viewer.getModelInfoAsString();
      break;
    case Token.measurements:
      if (!isSyntaxCheck)
        msg = viewer.getMeasurementInfoAsString();
      break;
    case Token.translation:
    case Token.rotation:
    case Token.moveto:
      if (!isSyntaxCheck)
        msg = viewer.getOrientationText(tok, null);
      break;
    case Token.orientation:
      len = 2;
      if (statementLength > 3)
        break;
      switch (tok = tokAt(2)) {
      case Token.translation:
      case Token.rotation:
      case Token.moveto:
      case Token.nada:
        if (!isSyntaxCheck)
          msg = viewer.getOrientationText(tok, null);
        break;
      default:
        name = optParameterAsString(2);
        msg = viewer.getOrientationText(0, name);
      }
      len = statementLength;
      break;
    case Token.pdbheader:
      if (!isSyntaxCheck)
        msg = viewer.getPDBHeader();
      break;
    case Token.pointgroup:
      pointGroup();
      return;
    case Token.symmetry:
      if (!isSyntaxCheck)
        msg = viewer.getSymmetryInfoAsString();
      break;
    case Token.transform:
      if (!isSyntaxCheck)
        msg = "transform:\n" + viewer.getTransformText();
      break;
    case Token.zoom:
      msg = "zoom "
          + (viewer.getZoomEnabled() ? ("" + viewer.getZoomSetting()) : "off");
      break;
    case Token.frank:
      msg = (viewer.getShowFrank() ? "frank ON" : "frank OFF");
      break;
    case Token.radius:
      str = "solventProbeRadius";
      break;
    // Chime related
    case Token.basepair:
    case Token.chain:
    case Token.sequence:
    case Token.residue:
    case Token.selected:
    case Token.group:
    case Token.atoms:
    case Token.info:
    case Token.bonds:
      msg = viewer.getChimeInfo(tok);
      break;
    // not implemented
    case Token.echo:
    case Token.fontsize:
    case Token.property: // huh? why?
    case Token.help:
    case Token.solvent:
      value = "?";
      break;
    case Token.identifier:
      if (str.equalsIgnoreCase("fileHeader")) {
        if (!isSyntaxCheck)
          msg = viewer.getPDBHeader();
      } else if (str.equalsIgnoreCase("menu")) {
        if (!isSyntaxCheck)
          value = viewer.getMenu("");
      } else if (str.equalsIgnoreCase("mouse")) {
        String qualifiers = ((len = statementLength) == 2 ? null
            : parameterAsString(2));
        if (!isSyntaxCheck)
          msg = viewer.getBindingInfo(qualifiers);
      }
      break;
    }
    checkLength(len);
    if (isSyntaxCheck)
      return;
    if (msg != null)
      showString(msg);
    else if (value != null)
      showString(str + " = " + value);
    else if (str != null) {
      if (str.indexOf(" ") >= 0)
        showString(str);
      else
        showString(str + " = " + getParameterEscaped(str));
    }
  }

  private String getIsosurfaceJvxl(boolean asMesh, int iShape) {
    if (isSyntaxCheck)
      return "";
    return (String) getShapeProperty(iShape, asMesh ? "jvxlMeshXml"
        : "jvxlDataXml");
  }

  private String getMoJvxl(int ptMO) throws ScriptException {
    // 0: all; Integer.MAX_VALUE: current;
    loadShape(JmolConstants.SHAPE_MO);
    int modelIndex = viewer.getCurrentModelIndex();
    if (modelIndex < 0)
      error(ERROR_multipleModelsDisplayedNotOK, "MO isosurfaces");
    Hashtable moData = (Hashtable) viewer.getModelAuxiliaryInfo(modelIndex,
        "moData");
    if (moData == null)
      error(ERROR_moModelError);
    Integer n = (Integer) getShapeProperty(JmolConstants.SHAPE_MO, "moNumber");
    if (n == null || ((Integer) n).intValue() == 0) {
      setShapeProperty(JmolConstants.SHAPE_MO, "init", new Integer(modelIndex));
      setShapeProperty(JmolConstants.SHAPE_MO, "moData", moData);
    } else if (ptMO == Integer.MAX_VALUE) {
    }
    return (String) getShapeProperty(JmolConstants.SHAPE_MO, "showMO", ptMO);
  }

  private String extractCommandOption(String name) {
    int i = fullCommand.indexOf(name + "=");
    return (i < 0 ? null : Parser.getNextQuotedString(fullCommand, i));
  }

  private void draw() throws ScriptException {
    loadShape(JmolConstants.SHAPE_DRAW);
    switch (tokAt(1)) {
    case Token.list:
      if (listIsosurface(JmolConstants.SHAPE_DRAW))
        return;
      break;
    case Token.pointgroup:
      pointGroup();
      return;
    case Token.helix:
    case Token.quaternion:
    case Token.ramachandran:
      plot(statement);
      return;
    }
    boolean havePoints = false;
    boolean isInitialized = false;
    boolean isSavedState = false;
    boolean isTranslucent = false;
    boolean isIntersect = false;
    boolean isFrame = false;
    Point4f plane;
    int tokIntersect = 0;
    float translucentLevel = Float.MAX_VALUE;
    int colorArgb = Integer.MIN_VALUE;
    int intScale = 0;
    String swidth = "";
    int iptDisplayProperty = 0;
    Point3f center = null;
    String thisId = initIsosurface(JmolConstants.SHAPE_DRAW);
    boolean idSeen = (thisId != null);
    boolean isWild = (idSeen && getShapeProperty(JmolConstants.SHAPE_DRAW, "ID") == null);
    for (int i = iToken; i < statementLength; ++i) {
      String propertyName = null;
      Object propertyValue = null;
      switch (getToken(i).tok) {
      case Token.unitcell:
      case Token.boundbox:
        if (isSyntaxCheck)
          break;
        List vp = viewer.getPlaneIntersection(theTok, null, intScale / 100f,
            0);
        intScale = 0;
        propertyName = "polygon";
        propertyValue = vp;
        havePoints = true;
        break;
      case Token.intersection:
        switch (getToken(++i).tok) {
        case Token.unitcell:
        case Token.boundbox:
          tokIntersect = theTok;
          isIntersect = true;
          continue;
        case Token.dollarsign:
          propertyName = "intersect";
          propertyValue = objectNameParameter(++i);
          i = iToken;
          isIntersect = true;
          havePoints = true;
          break;
        default:
          error(ERROR_invalidArgument);
        }
        break;
      case Token.polygon:
        int nVertices = intParameter(++i);
        Point3f[] points = new Point3f[nVertices];
        for (int j = 0; j < nVertices; j++, i = iToken)
          points[j] = getPoint3f(++iToken, true);
        List v = new ArrayList();
        v.add(points);
        int nTriangles = intParameter(++i);
        int[][] polygons = new int[nTriangles][];
        for (int j = 0; j < nTriangles; j++, i = iToken) {
          float[] f = floatParameterSet(++i, 3, 4);
          polygons[j] = new int[] { (int) f[0], (int) f[1], (int) f[2],
              (f.length == 3 ? 7 : (int) f[3]) };
        }
        if (nVertices == 0)
          v = null;
        else
          v.add(polygons);
        propertyName = "polygon";
        propertyValue = v;
        havePoints = true;
        break;
      case Token.symop:
        String xyz = null;
        int iSym = 0;
        plane = null;
        Point3f target = null;
        switch (tokAt(++i)) {
        case Token.string:
          xyz = stringParameter(i);
          break;
        case Token.matrix4f:
          xyz = ScriptVariable.sValue(getToken(i));
          break;
        case Token.integer:
        default:
          if (!isCenterParameter(i))
            iSym = intParameter(i++);
          if (isCenterParameter(i))
            center = centerParameter(i);
          if (isCenterParameter(iToken + 1))
            target = centerParameter(++iToken);
          if (isSyntaxCheck)
            return;
          i = iToken;
        }
        BitSet bsAtoms = null;
        if (center == null && i + 1 < statementLength) {
          center = centerParameter(++i);
          // draw ID xxx symop [n or "x,-y,-z"] [optional {center}]
          // so we also check here for the atom set to get the right model
          bsAtoms = (tokAt(i) == Token.bitset
              || tokAt(i) == Token.expressionBegin ? atomExpression(i) : null);
          i = iToken + 1;
        }
        checkLast(iToken);
        if (!isSyntaxCheck)
          runScript((String) viewer.getSymmetryInfo(bsAtoms, xyz, iSym, center,
              target, thisId, Token.draw));
        return;
      case Token.frame:
        isFrame = true;
        // draw ID xxx frame {center} {q1 q2 q3 q4}
        continue;
      case Token.leftbrace:
      case Token.point4f:
      case Token.point3f:
        // {X, Y, Z}
        if (theTok == Token.point4f || !isPoint3f(i)) {
          propertyValue = getPoint4f(i);
          if (isFrame) {
            checkLast(iToken);
            if (!isSyntaxCheck)
              runScript((new Quaternion((Point4f) propertyValue)).draw(
                  (thisId == null ? "frame" : thisId), " " + swidth,
                  (center == null ? new Point3f() : center), intScale / 100f));
            return;
          }
          propertyName = "planedef";
        } else {
          propertyValue = center = getPoint3f(i, true);
          propertyName = "coord";
        }
        i = iToken;
        havePoints = true;
        break;
      case Token.hkl:
      case Token.plane:
        if (!havePoints && !isIntersect && tokIntersect == 0
            && theTok != Token.hkl) {
          propertyName = "plane";
          break;
        }
        if (theTok == Token.plane) {
          plane = planeParameter(++i);
        } else {
          plane = hklParameter(++i);
        }
        i = iToken;
        if (tokIntersect != 0) {
          if (isSyntaxCheck)
            break;
          List vpc = viewer.getPlaneIntersection(tokIntersect, plane,
              intScale / 100f, 0);
          intScale = 0;
          propertyName = "polygon";
          propertyValue = vpc;
        } else {
          propertyValue = plane;
          propertyName = "planedef";
        }
        havePoints = true;
        break;
      case Token.linedata:
        propertyName = "lineData";
        propertyValue = floatParameterSet(++i, 0, Integer.MAX_VALUE);
        i = iToken;
        havePoints = true;
        break;
      case Token.bitset:
      case Token.expressionBegin:
        propertyName = "atomSet";
        propertyValue = atomExpression(i);
        if (isFrame)
          center = centerParameter(i);
        i = iToken;
        havePoints = true;
        break;
      case Token.varray:
        propertyName = "modelBasedPoints";
        propertyValue = ScriptVariable.listValue(theToken);
        havePoints = true;
        break;
      case Token.spacebeforesquare:
      case Token.comma:
        break;
      case Token.leftsquare:
        // [x y] or [x y %]
        propertyValue = xypParameter(i);
        if (propertyValue != null) {
          i = iToken;
          propertyName = "coord";
          havePoints = true;
          break;
        }
        if (isSavedState)
          error(ERROR_invalidArgument);
        isSavedState = true;
        break;
      case Token.rightsquare:
        if (!isSavedState)
          error(ERROR_invalidArgument);
        isSavedState = false;
        break;
      case Token.reverse:
        propertyName = "reverse";
        break;
      case Token.string:
        propertyValue = stringParameter(i);
        propertyName = "title";
        break;
      case Token.vector:
        propertyName = "vector";
        break;
      case Token.length:
        propertyValue = new Float(floatParameter(++i));
        propertyName = "length";
        break;
      case Token.decimal:
        // $drawObject
        propertyValue = new Float(floatParameter(i));
        propertyName = "length";
        break;
      case Token.modelindex:
        propertyName = "modelIndex";
        propertyValue = new Integer(intParameter(++i));
        break;
      case Token.integer:
        if (isSavedState) {
          propertyName = "modelIndex";
          propertyValue = new Integer(intParameter(i));
        } else {
          intScale = intParameter(i);
        }
        break;
      case Token.scale:
        if (++i >= statementLength)
          error(ERROR_numberExpected);
        switch (getToken(i).tok) {
        case Token.integer:
          intScale = intParameter(i);
          continue;
        case Token.decimal:
          intScale = (int) (floatParameter(i) * 100);
          continue;
        }
        error(ERROR_numberExpected);
        break;
      case Token.id:
        thisId = setShapeId(JmolConstants.SHAPE_DRAW, ++i, idSeen);
        isWild = (getShapeProperty(JmolConstants.SHAPE_DRAW, "ID") == null);
        i = iToken;
        break;
      case Token.modelbased:
        propertyName = "fixed";
        propertyValue = Boolean.FALSE;
        break;
      case Token.fixed:
        propertyName = "fixed";
        propertyValue = Boolean.TRUE;
        break;
      case Token.offset:
        Point3f pt = getPoint3f(++i, true);
        i = iToken;
        propertyName = "offset";
        propertyValue = pt;
        break;
      case Token.crossed:
        propertyName = "crossed";
        break;
      case Token.width:
        propertyValue = new Float(floatParameter(++i));
        propertyName = "width";
        swidth = (String) propertyName + " " + propertyValue;
        break;
      case Token.line:
        propertyName = "line";
        propertyValue = Boolean.TRUE;
        break;
      case Token.curve:
        propertyName = "curve";
        break;
      case Token.arc:
        propertyName = "arc";
        break;
      case Token.arrow:
        propertyName = "arrow";
        break;
      case Token.circle:
        propertyName = "circle";
        break;
      case Token.cylinder:
        propertyName = "cylinder";
        break;
      case Token.vertices:
        propertyName = "vertices";
        break;
      case Token.nohead:
        propertyName = "nohead";
        break;
      case Token.rotate45:
        propertyName = "rotate45";
        break;
      case Token.perpendicular:
        propertyName = "perp";
        break;
      case Token.diameter:
        float f = floatParameter(++i);
        propertyValue = new Float(f);
        propertyName = (tokAt(i) == Token.decimal ? "width" : "diameter");
        swidth = (String) propertyName
            + (tokAt(i) == Token.decimal ? " " + f : " " + ((int) f));
        break;
      case Token.dollarsign:
        // $drawObject[m]
        if ((tokAt(i + 2) == Token.leftsquare || isFrame)) {
          Point3f pto = center = centerParameter(i);
          i = iToken;
          propertyName = "coord";
          propertyValue = pto;
          havePoints = true;
          break;
        }
        // $drawObject
        propertyValue = objectNameParameter(++i);
        propertyName = "identifier";
        havePoints = true;
        break;
      case Token.color:
      case Token.translucent:
      case Token.opaque:
        if (theTok != Token.color)
          --i;
        if (tokAt(i + 1) == Token.translucent) {
          i++;
          isTranslucent = true;
          if (isFloatParameter(i + 1))
            translucentLevel = getTranslucentLevel(++i);
        } else if (tokAt(i + 1) == Token.opaque) {
          i++;
          isTranslucent = true;
          translucentLevel = 0;
        }
        if (isColorParam(i + 1)) {
          colorArgb = getArgbParam(++i);
          i = iToken;
        } else if (!isTranslucent) {
          error(ERROR_invalidArgument);
        }
        idSeen = true;
        continue;
      default:
        if (!setMeshDisplayProperty(JmolConstants.SHAPE_DRAW, 0, theTok)) {
          if (theTok == Token.times || Token.tokAttr(theTok, Token.identifier)) {
            thisId = setShapeId(JmolConstants.SHAPE_DRAW, i, idSeen);
            i = iToken;
            break;
          }
          error(ERROR_invalidArgument);
        }
        if (iptDisplayProperty == 0)
          iptDisplayProperty = i;
        i = iToken;
        continue;
      }
      idSeen = (theTok != Token.delete);
      if (havePoints && !isInitialized && !isFrame) {
        setShapeProperty(JmolConstants.SHAPE_DRAW, "points", new Integer(
            intScale));
        isInitialized = true;
        intScale = 0;
      }
      if (havePoints && isWild)
        error(ERROR_invalidArgument);
      if (propertyName != null)
        setShapeProperty(JmolConstants.SHAPE_DRAW, propertyName, propertyValue);
    }
    if (havePoints) {
      setShapeProperty(JmolConstants.SHAPE_DRAW, "set", null);
    }
    if (colorArgb != Integer.MIN_VALUE)
      setShapeProperty(JmolConstants.SHAPE_DRAW, "color",
          new Integer(colorArgb));
    if (isTranslucent)
      setShapeTranslucency(JmolConstants.SHAPE_DRAW, "", "translucent",
          translucentLevel, null);
    if (intScale != 0) {
      setShapeProperty(JmolConstants.SHAPE_DRAW, "scale", new Integer(intScale));
    }
    if (iptDisplayProperty > 0) {
      if (!setMeshDisplayProperty(JmolConstants.SHAPE_DRAW, iptDisplayProperty,
          getToken(iptDisplayProperty).tok))
        error(ERROR_invalidArgument);
    }
  }

  private void polyhedra() throws ScriptException {
    /*
     * needsGenerating:
     *
     * polyhedra [number of vertices and/or basis] [at most two selection sets]
     * [optional type and/or edge] [optional design parameters]
     *
     * OR else:
     *
     * polyhedra [at most one selection set] [type-and/or-edge or on/off/delete]
     */
    boolean needsGenerating = false;
    boolean onOffDelete = false;
    boolean typeSeen = false;
    boolean edgeParameterSeen = false;
    boolean isDesignParameter = false;
    int nAtomSets = 0;
    loadShape(JmolConstants.SHAPE_POLYHEDRA);
    setShapeProperty(JmolConstants.SHAPE_POLYHEDRA, "init", null);
    String setPropertyName = "centers";
    String decimalPropertyName = "radius_";
    boolean isTranslucent = false;
    float translucentLevel = Float.MAX_VALUE;
    int color = Integer.MIN_VALUE;
    for (int i = 1; i < statementLength; ++i) {
      if (isColorParam(i)) {
        color = getArgbParam(i);
        i = iToken;
        continue;
      }
      String propertyName = null;
      Object propertyValue = null;
      switch (getToken(i).tok) {
      case Token.delete:
      case Token.on:
      case Token.off:
        if (i + 1 != statementLength || needsGenerating || nAtomSets > 1
            || nAtomSets == 0 && setPropertyName == "to")
          error(ERROR_incompatibleArguments);
        propertyName = parameterAsString(i);
        onOffDelete = true;
        break;
      case Token.opEQ:
      case Token.comma:
        continue;
      case Token.bonds:
        if (nAtomSets > 0)
          error(ERROR_invalidParameterOrder);
        needsGenerating = true;
        propertyName = "bonds";
        break;
      case Token.radius:
        decimalPropertyName = "radius";
        continue;
      case Token.integer:
      case Token.decimal:
        if (nAtomSets > 0 && !isDesignParameter)
          error(ERROR_invalidParameterOrder);
        if (theTok == Token.integer) {
          if (decimalPropertyName == "radius_") {
            propertyName = "nVertices";
            propertyValue = new Integer(intParameter(i));
            needsGenerating = true;
            break;
          }
        }
        propertyName = (decimalPropertyName == "radius_" ? "radius"
            : decimalPropertyName);
        propertyValue = new Float(floatParameter(i));
        decimalPropertyName = "radius_";
        isDesignParameter = false;
        needsGenerating = true;
        break;
      case Token.bitset:
      case Token.expressionBegin:
        if (typeSeen)
          error(ERROR_invalidParameterOrder);
        if (++nAtomSets > 2)
          error(ERROR_badArgumentCount);
        if (setPropertyName == "to")
          needsGenerating = true;
        propertyName = setPropertyName;
        setPropertyName = "to";
        propertyValue = atomExpression(i);
        i = iToken;
        break;
      case Token.to:
        if (nAtomSets > 1)
          error(ERROR_invalidParameterOrder);
        if (getToken(i + 1).tok == Token.bitset) {
          propertyName = "toBitSet";
          propertyValue = getToken(++i).value;
          needsGenerating = true;
          break;
        } else if (!needsGenerating) {
          error(ERROR_insufficientArguments);
        }
        setPropertyName = "to";
        continue;
      case Token.facecenteroffset:
        if (!needsGenerating)
          error(ERROR_insufficientArguments);
        decimalPropertyName = "faceCenterOffset";
        isDesignParameter = true;
        continue;
      case Token.distancefactor:
        if (!needsGenerating)
          error(ERROR_insufficientArguments);
        decimalPropertyName = "distanceFactor";
        isDesignParameter = true;
        continue;
      case Token.color:
      case Token.translucent:
      case Token.opaque:
        isTranslucent = false;
        if (theTok != Token.color)
          --i;
        if (tokAt(i + 1) == Token.translucent) {
          i++;
          isTranslucent = true;
          if (isFloatParameter(++i))
            translucentLevel = getTranslucentLevel(i);
        } else if (tokAt(i + 1) == Token.opaque) {
          i++;
          isTranslucent = true;
          translucentLevel = 0;
        }
        if (isColorParam(i + 1)) {
          color = getArgbParam(i);
          i = iToken;
        } else if (!isTranslucent)
          error(ERROR_invalidArgument);
        continue;
      case Token.collapsed:
      case Token.flat:
        propertyName = "collapsed";
        propertyValue = (theTok == Token.collapsed ? Boolean.TRUE
            : Boolean.FALSE);
        if (typeSeen)
          error(ERROR_incompatibleArguments);
        typeSeen = true;
        break;
      case Token.noedges:
      case Token.edges:
      case Token.frontedges:
        if (edgeParameterSeen)
          error(ERROR_incompatibleArguments);
        propertyName = parameterAsString(i);
        edgeParameterSeen = true;
        break;
      default:
        error(ERROR_invalidArgument);
      }
      setShapeProperty(JmolConstants.SHAPE_POLYHEDRA, propertyName,
          propertyValue);
      if (onOffDelete)
        return;
    }
    if (!needsGenerating && !typeSeen && !edgeParameterSeen)
      error(ERROR_insufficientArguments);
    if (needsGenerating)
      setShapeProperty(JmolConstants.SHAPE_POLYHEDRA, "generate", null);
    if (color != Integer.MIN_VALUE)
      setShapeProperty(JmolConstants.SHAPE_POLYHEDRA, "colorThis", new Integer(
          color));
    if (isTranslucent)
      setShapeTranslucency(JmolConstants.SHAPE_POLYHEDRA, "", "translucent",
          translucentLevel, null);
  }

  private void lcaoCartoon() throws ScriptException {
    loadShape(JmolConstants.SHAPE_LCAOCARTOON);
    if (tokAt(1) == Token.list
        && listIsosurface(JmolConstants.SHAPE_LCAOCARTOON))
      return;
    setShapeProperty(JmolConstants.SHAPE_LCAOCARTOON, "init", fullCommand);
    if (statementLength == 1) {
      setShapeProperty(JmolConstants.SHAPE_LCAOCARTOON, "lcaoID", null);
      return;
    }
    boolean idSeen = false;
    String translucency = null;
    for (int i = 1; i < statementLength; i++) {
      String propertyName = null;
      Object propertyValue = null;
      switch (getToken(i).tok) {
      case Token.cap:
      case Token.slab:
        propertyName = (String) theToken.value;
        if (tokAt(i + 1) == Token.off)
          iToken = i + 1;
        propertyValue = getCapSlabObject(null, i);
        i = iToken;
        break;
      case Token.center:
        // serialized lcaoCartoon in isosurface format
        isosurface(JmolConstants.SHAPE_LCAOCARTOON);
        return;
      case Token.rotate:
        float degx = 0;
        float degy = 0;
        float degz = 0;
        switch (getToken(++i).tok) {
        case Token.x:
          degx = floatParameter(++i) * JmolConstants.radiansPerDegree;
          break;
        case Token.y:
          degy = floatParameter(++i) * JmolConstants.radiansPerDegree;
          break;
        case Token.z:
          degz = floatParameter(++i) * JmolConstants.radiansPerDegree;
          break;
        default:
          error(ERROR_invalidArgument);
        }
        propertyName = "rotationAxis";
        propertyValue = new Vector3f(degx, degy, degz);
        break;
      case Token.on:
      case Token.display:
      case Token.displayed:
        propertyName = "on";
        break;
      case Token.off:
      case Token.hide:
      case Token.hidden:
        propertyName = "off";
        break;
      case Token.delete:
        propertyName = "delete";
        break;
      case Token.bitset:
      case Token.expressionBegin:
        propertyName = "select";
        propertyValue = atomExpression(i);
        i = iToken;
        break;
      case Token.color:
        translucency = setColorOptions(null, i + 1,
            JmolConstants.SHAPE_LCAOCARTOON, -2);
        if (translucency != null)
          setShapeProperty(JmolConstants.SHAPE_LCAOCARTOON, "settranslucency",
              translucency);
        i = iToken;
        idSeen = true;
        continue;
      case Token.translucent:
      case Token.opaque:
        setMeshDisplayProperty(JmolConstants.SHAPE_LCAOCARTOON, i, theTok);
        i = iToken;
        idSeen = true;
        continue;
      case Token.spacefill:
      case Token.string:
        propertyValue = parameterAsString(i).toLowerCase();
        if (propertyValue.equals("spacefill"))
          propertyValue = "cpk";
        propertyName = "create";
        if (optParameterAsString(i + 1).equalsIgnoreCase("molecular")) {
          i++;
          propertyName = "molecular";
        }
        break;
      case Token.select:
        if (tokAt(i + 1) == Token.bitset
            || tokAt(i + 1) == Token.expressionBegin) {
          propertyName = "select";
          propertyValue = atomExpression(i + 1);
          i = iToken;
        } else {
          propertyName = "selectType";
          propertyValue = parameterAsString(++i);
          if (propertyValue.equals("spacefill"))
            propertyValue = "cpk";
        }
        break;
      case Token.scale:
        propertyName = "scale";
        propertyValue = new Float(floatParameter(++i));
        break;
      case Token.lonepair:
      case Token.lp:
        propertyName = "lonePair";
        break;
      case Token.radical:
      case Token.rad:
        propertyName = "radical";
        break;
      case Token.molecular:
        propertyName = "molecular";
        break;
      case Token.create:
        propertyValue = parameterAsString(++i);
        propertyName = "create";
        if (optParameterAsString(i + 1).equalsIgnoreCase("molecular")) {
          i++;
          propertyName = "molecular";
        }
        break;
      case Token.id:
        propertyValue = getShapeNameParameter(++i);
        i = iToken;
        if (idSeen)
          error(ERROR_invalidArgument);
        propertyName = "lcaoID";
        break;
      default:
        if (theTok == Token.times || Token.tokAttr(theTok, Token.identifier)) {
          if (theTok != Token.times)
            propertyValue = parameterAsString(i);
          if (idSeen)
            error(ERROR_invalidArgument);
          propertyName = "lcaoID";
          break;
        }
        break;
      }
      if (theTok != Token.delete)
        idSeen = true;
      if (propertyName == null)
        error(ERROR_invalidArgument);
      setShapeProperty(JmolConstants.SHAPE_LCAOCARTOON, propertyName,
          propertyValue);
    }
    setShapeProperty(JmolConstants.SHAPE_LCAOCARTOON, "clear", null);
  }

  private Object getCapSlabObject(StringBuffer sb, int i)
      throws ScriptException {
    Object data = null;
    if (sb != null)
      sb.append(" ").append(getToken(i).value).append(" ");
    int tok = tokAt(i + 1);
    Point4f plane = null;
    switch (tok) {
    case Token.within:
      i++;
      data = getPointArray(++i, 4);
      break;
    case Token.boundbox:
      data = BoxInfo.getCriticalPoints(viewer.getBoundBoxVertices(),
          null);
      iToken = i + 1;
      break;
    case Token.unitcell:
      SymmetryInterface unitCell = viewer.getCurrentUnitCell();
      if (unitCell == null)
        error(ERROR_invalidArgument);
      Point3f[] pts = BoxInfo.getCriticalPoints(unitCell.getUnitCellVertices(),
          unitCell.getCartesianOffset());
      int iType = (int) unitCell.getUnitCellAsArray(JmolConstants.INFO_DIMENSIONS);
      Vector3f v1 = null;
      Vector3f v2 = null;
      switch(iType) {
      case 3:
        break;
      case 1: // polymer
        v2 = new Vector3f(pts[2]);
        v2.sub(pts[0]);
        v2.scale(1000f);
        // fall through
      case 2: // slab
        // "a b c" is really "z y x"
        v1 = new Vector3f(pts[1]);
        v1.sub(pts[0]);
        v1.scale(1000f);
        pts[0].sub(v1);
        pts[1].scale(2000f);
        if (iType == 1) {
          pts[0].sub(v2);
          pts[2].scale(2000f);
        }
        break;
      }
      data = pts;
      iToken = i + 1;
      break;
    default:
      plane = planeParameter(++i);
      float off = (isFloatParameter(iToken + 1) ? floatParameter(++iToken)
          : Float.NaN);
      if (!Float.isNaN(off))
        plane.w -= off;
      data = plane;
    }
    if (sb != null) {
      if (plane == null)
        sb.append("within ").append(Escape.escape(data));
      else
        sb.append(Escape.escape(plane));
    }
    return data;
  }

  private boolean mo(boolean isInitOnly) throws ScriptException {
    int offset = Integer.MAX_VALUE;
    BitSet bsModels = viewer.getVisibleFramesBitSet();
    List propertyList = new ArrayList();
    int i = 1;
    if (tokAt(1) == Token.model || tokAt(1) == Token.frame) {
      i = modelNumberParameter(2);
      if (i < 0)
        error(ERROR_invalidArgument);
      bsModels.clear();
      bsModels.set(i);
      i = 3;
    }
    for (int iModel = bsModels.nextSetBit(0); iModel >= 0; iModel = bsModels
        .nextSetBit(iModel + 1)) {
      loadShape(JmolConstants.SHAPE_MO);
      if (tokAt(i) == Token.list && listIsosurface(JmolConstants.SHAPE_MO))
        return true;
      setShapeProperty(JmolConstants.SHAPE_MO, "init", new Integer(iModel));
      String title = null;
      int moNumber = ((Integer) getShapeProperty(JmolConstants.SHAPE_MO,
          "moNumber")).intValue();
      if (isInitOnly)
        return true;// (moNumber != 0);
      if (moNumber == 0)
        moNumber = Integer.MAX_VALUE;
      String propertyName = null;
      Object propertyValue = null;
      switch (getToken(i).tok) {
      case Token.integer:
        moNumber = intParameter(i);
        break;
      case Token.next:
        moNumber = Token.next;
        break;
      case Token.prev:
        moNumber = Token.prev;
        break;
      case Token.color:
        setColorOptions(null, i + 1, JmolConstants.SHAPE_MO, 2);
        break;
      case Token.plane:
        // plane {X, Y, Z, W}
        propertyName = "plane";
        propertyValue = planeParameter(i + 1);
        break;
      case Token.scale:
        propertyName = "scale";
        propertyValue = new Float(floatParameter(i + 1));
        break;
      case Token.cutoff:
        if (tokAt(i + 1) == Token.plus) {
          propertyName = "cutoffPositive";
          propertyValue = new Float(floatParameter(i + 2));
        } else {
          propertyName = "cutoff";
          propertyValue = new Float(floatParameter(i + 1));
        }
        break;
      case Token.debug:
        propertyName = "debug";
        break;
      case Token.noplane:
        propertyName = "plane";
        break;
      case Token.pointsperangstrom:
      case Token.resolution:
        propertyName = "resolution";
        propertyValue = new Float(floatParameter(i + 1));
        break;
      case Token.squared:
        propertyName = "squareData";
        propertyValue = Boolean.TRUE;
        break;
      case Token.titleformat:
        if (i + 1 < statementLength && tokAt(i + 1) == Token.string) {
          propertyName = "titleFormat";
          propertyValue = parameterAsString(i + 1);
        }
        break;
      case Token.homo:
      case Token.lumo:
        if ((offset = moOffset(i)) == Integer.MAX_VALUE)
          error(ERROR_invalidArgument);
        moNumber = 0;
        break;
      case Token.identifier:
        error(ERROR_invalidArgument);
      default:
        int ipt = iToken;
        if (!setMeshDisplayProperty(JmolConstants.SHAPE_MO, 0, theTok))
          error(ERROR_invalidArgument);
        setShapeProperty(JmolConstants.SHAPE_MO, "setProperties", propertyList);
        setMeshDisplayProperty(JmolConstants.SHAPE_MO, ipt, tokAt(ipt));
        return true;
      }
      if (propertyName != null)
        addShapeProperty(propertyList, propertyName, propertyValue);
      if (moNumber != Integer.MAX_VALUE) {
        if (tokAt(i + 1) == Token.string)
          title = parameterAsString(i + 1);
        setCursorWait(true);
        setMoData(propertyList, moNumber, offset, iModel, title);
        addShapeProperty(propertyList, "finalize", null);
      }
      if (propertyList.size() > 0)
        setShapeProperty(JmolConstants.SHAPE_MO, "setProperties", propertyList);
      propertyList.clear();
    }
    return true;
  }

  private String setColorOptions(StringBuffer sb, int index, int iShape,
                                 int nAllowed) throws ScriptException {
    getToken(index);
    String translucency = "opaque";
    if (theTok == Token.translucent) {
      translucency = "translucent";
      if (nAllowed < 0) {
        float value = (isFloatParameter(index + 1) ? floatParameter(++index)
            : Float.MAX_VALUE);
        setShapeTranslucency(iShape, null, "translucent", value, null);
        if (sb != null) {
          sb.append(" translucent");
          if (value != Float.MAX_VALUE)
            sb.append(" ").append(value);
        }
      } else {
        setMeshDisplayProperty(iShape, index, theTok);
      }
    } else if (theTok == Token.opaque) {
      if (nAllowed >= 0)
        setMeshDisplayProperty(iShape, index, theTok);
    } else {
      iToken--;
    }
    nAllowed = Math.abs(nAllowed);
    for (int i = 0; i < nAllowed; i++) {
      if (isColorParam(iToken + 1)) {
        int color = getArgbParam(++iToken);
        setShapeProperty(iShape, "colorRGB", new Integer(color));
        if (sb != null)
          sb.append(" ").append(Escape.escapeColor(color));
      } else if (iToken < index) {
        error(ERROR_invalidArgument);
      } else {
        break;
      }
    }
    return translucency;
  }

  private int moOffset(int index) throws ScriptException {
    boolean isHomo = (getToken(index).tok == Token.homo);
    int offset = (isHomo ? 0 : 1);
    int tok = tokAt(++index);
    if (tok == Token.integer && intParameter(index) < 0)
      offset += intParameter(index);
    else if (tok == Token.plus)
      offset += intParameter(++index);
    else if (tok == Token.minus)
      offset -= intParameter(++index);
    return offset;
  }

  private void setMoData(List propertyList, int moNumber, int offset,
                         int modelIndex, String title) throws ScriptException {
    if (isSyntaxCheck)
      return;
    if (modelIndex < 0) {
      modelIndex = viewer.getCurrentModelIndex();
      if (modelIndex < 0)
        error(ERROR_multipleModelsDisplayedNotOK, "MO isosurfaces");
    }
    Hashtable moData = (Hashtable) viewer.getModelAuxiliaryInfo(modelIndex,
        "jmolSurfaceInfo");
    int firstMoNumber = moNumber;
    if (moData != null && ((String) moData.get("surfaceDataType")).equals("mo")) {
      // loadShape(shape);
      // setShapeProperty(shape, "init", new Integer(modelIndex));
    } else {
      moData = (Hashtable) viewer.getModelAuxiliaryInfo(modelIndex, "moData");
      if (moData == null)
        error(ERROR_moModelError);
      int lastMoNumber = (moData.containsKey("lastMoNumber") ? ((Integer) moData
          .get("lastMoNumber")).intValue()
          : 0);
      if (moNumber == Token.prev)
        moNumber = lastMoNumber - 1;
      else if (moNumber == Token.next)
        moNumber = lastMoNumber + 1;
      List mos = (List) (moData.get("mos"));
      int nOrb = (mos == null ? 0 : mos.size());
      if (nOrb == 0)
        error(ERROR_moCoefficients);
      if (nOrb == 1 && moNumber > 1)
        error(ERROR_moOnlyOne);
      if (offset != Integer.MAX_VALUE) {
        // 0: HOMO;
        if (moData.containsKey("HOMO")) {
          moNumber = ((Integer) moData.get("HOMO")).intValue() + offset;
        } else {
          moNumber = -1;
          Float f;
          for (int i = 0; i < nOrb; i++) {
            Map mo = (Map) mos.get(i);
            f = (Float) mo.get("occupancy");
            if (f != null) {
              if (f.floatValue() == 0) {
                moNumber = i;
                break;
              }
            } else if ((f = (Float) mo.get("energy")) == null) {
              break;
            }
            if (f.floatValue() > 0) {
              // go for HOMO = highest non-negative
              moNumber = i;
              continue;
            }
          }
          if (moNumber < 0)
            error(ERROR_moOccupancy);
          moNumber += offset;
          Logger.info("MO " + moNumber);
        }
      }
      if (moNumber < 1 || moNumber > nOrb)
        error(ERROR_moIndex, "" + nOrb);
    }
    moData.put("lastMoNumber", new Integer(moNumber));
    addShapeProperty(propertyList, "moData", moData);
    if (title != null)
      addShapeProperty(propertyList, "title", title);
    if (firstMoNumber < 0)
      addShapeProperty(propertyList, "charges", viewer.getAtomicCharges());
    addShapeProperty(propertyList, "molecularOrbital", new Integer(
        firstMoNumber < 0 ? -moNumber : moNumber));
    addShapeProperty(propertyList, "clear", null);
  }

  private String initIsosurface(int iShape) throws ScriptException {

    // handle isosurface/mo/pmesh delete and id delete here

    setShapeProperty(iShape, "init", fullCommand);
    iToken = 0;
    int tok1 = tokAt(1);
    int tok2 = tokAt(2);
    if (tok1 == Token.delete || tok2 == Token.delete
        && tokAt(++iToken) == Token.all) {
      setShapeProperty(iShape, "delete", null);
      iToken += 2;
      if (statementLength > iToken) {
        setShapeProperty(iShape, "init", fullCommand);
        setShapeProperty(iShape, "thisID", JmolConstants.PREVIOUS_MESH_ID);
      }
      return null;
    }
    iToken = 1;
    if (!setMeshDisplayProperty(iShape, 0, tok1)) {
      setShapeProperty(iShape, "thisID", JmolConstants.PREVIOUS_MESH_ID);
      if (iShape != JmolConstants.SHAPE_DRAW)
        setShapeProperty(iShape, "title", new String[] { thisCommand });
      if (tok1 != Token.id && (tok2 == Token.times
          || tok1 == Token.times && setMeshDisplayProperty(iShape, 0, tok2))) {
        String id = setShapeId(iShape, 1, false);
        iToken++;
        return id;
      }
    }
    return null;
  }

  private String getNextComment() {

    String nextCommand = getCommand(pc + 1, false, true);
    return (nextCommand.startsWith("#") ? nextCommand : "");
  }

  private boolean listIsosurface(int iShape) throws ScriptException {
    checkLength(2);
    if (!isSyntaxCheck)
      showString((String) getShapeProperty(iShape, "list"));
    return true;
  }

  private void isosurface(int iShape) throws ScriptException {
    // also called by lcaoCartoon
    loadShape(iShape);
    if (tokAt(1) == Token.list && listIsosurface(iShape))
      return;
    int iptDisplayProperty = 0;
    boolean isIsosurface = (iShape == JmolConstants.SHAPE_ISOSURFACE);
    boolean isPmesh = (iShape == JmolConstants.SHAPE_PMESH);
    boolean isPlot3d = (iShape == JmolConstants.SHAPE_PLOT3D);
    boolean isLcaoCartoon = (iShape == JmolConstants.SHAPE_LCAOCARTOON);
    boolean surfaceObjectSeen = false;
    boolean planeSeen = false;
    boolean doCalcArea = false;
    boolean doCalcVolume = false;
    boolean isCavity = false;
    boolean haveRadius = false;
    boolean isFxy = false;
    float[] nlmZ = new float[5];
    float[] data = null;
    int thisSetNumber = -1;
    int nFiles = 0;
    int nX, nY, nZ, ptX, ptY;
    float sigma = Float.NaN;
    float cutoff = Float.NaN;
    int ptWithin = 0;
    Boolean smoothing = null;
    BitSet bs;
    BitSet bsSelect = null;
    BitSet bsIgnore = null;
    StringBuffer sbCommand = new StringBuffer();
    Point3f[] pts;
    String str = null;
    int modelIndex = (isSyntaxCheck ? 0 : viewer.getCurrentModelIndex());
    setCursorWait(true);
    boolean idSeen = (initIsosurface(iShape) != null);
    boolean isWild = (idSeen && getShapeProperty(iShape, "ID") == null);
    boolean isColorSchemeTranslucent = false;
    String translucency = null;
    String colorScheme = null;
    String dataUse = null;
    short[] discreteColixes = null;
    List propertyList = new ArrayList();
    boolean defaultMesh = false;
    boolean isMapped = false;
   
    if (isPmesh || isPlot3d)
      addShapeProperty(propertyList, "fileType", "Pmesh");
    for (int i = iToken; i < statementLength; ++i) {
      String propertyName = null;
      Object propertyValue = null;
      getToken(i);
      if (theTok == Token.identifier
          && (str = parameterAsString(i)).equalsIgnoreCase("inline"))
        theTok = Token.string;
      switch (theTok) {
      case Token.boundbox:
        if (fullCommand.indexOf("# BBOX=") >= 0) {
          String[] bbox = TextFormat.split(extractCommandOption("# BBOX"), ',');
          pts = new Point3f[] { (Point3f) Escape.unescapePoint(bbox[0]),
              (Point3f) Escape.unescapePoint(bbox[1]) };
        } else if (isCenterParameter(i + 1)) {
          pts = new Point3f[] { getPoint3f(i + 1, true),
              getPoint3f(iToken + 1, true) };
          i = iToken;
        } else {
          pts = viewer.getBoundBoxVertices();
        }
        addShapeProperty(propertyList, "boundingBox", pts);
        sbCommand.append(" boundBox " + Escape.escape(pts[0]) + " "
            + Escape.escape(pts[pts.length - 1]));
        continue;
      case Token.pmesh:
        isPmesh = true;
        addShapeProperty(propertyList, "fileType", "Pmesh");
        sbCommand.append(" pmesh");
        continue;
      case Token.within:
        ptWithin = i;
        float distance;
        Point3f ptc = null;
        bs = null;
        boolean havePt = false;
        if (tokAt(i + 1) == Token.expressionBegin) {
          // within ( x.x , .... )
          distance = floatParameter(i + 3);
          if (isPoint3f(i + 4)) {
            ptc = centerParameter(i + 4);
            havePt = true;
            iToken = iToken + 2;
          } else if (isPoint3f(i + 5)) {
            ptc = centerParameter(i + 5);
            havePt = true;
            iToken = iToken + 2;
          } else {
            bs = atomExpression(statement, i + 5, statementLength, true, false,
                false, true);
            if (bs == null)
              error(ERROR_invalidArgument);
          }
        } else {
          distance = floatParameter(++i);
          ptc = centerParameter(++i);
        }
        i = iToken;
        if (fullCommand.indexOf("# WITHIN=") >= 0)
          bs = Escape.unescapeBitset(extractCommandOption("# WITHIN"));
        else if (!havePt)
          bs = (expressionResult instanceof BitSet ? (BitSet) expressionResult
              : null);
        if (!isSyntaxCheck) {
          if (bs != null)
            bs.and(viewer.getModelUndeletedAtomsBitSet(modelIndex));
          if (ptc == null)
            ptc = viewer.getAtomSetCenter(bs);

          getWithinDistanceVector(propertyList, distance, ptc, bs);
          sbCommand.append(" within ").append(distance).append(" ").append(
              bs == null ? Escape.escape(ptc) : Escape.escape(bs));
        }
        continue;
      case Token.isosurfacepropertysmoothing:
        smoothing = (getToken(++i).tok == Token.on ? Boolean.TRUE
            : theTok == Token.off ? Boolean.FALSE : null);
        if (smoothing == null)
          error(ERROR_invalidArgument);
        continue;
      case Token.property:
      case Token.variable:
        if (modelIndex < 0)
          error(ERROR_multipleModelsDisplayedNotOK, "ISOSURFACE "
              + theToken.value);
        //if (isCavity)
          //error(ERROR_invalidArgument);
        boolean isVariable = (theTok == Token.variable);
        if (dataUse == null) { // not mlp or mep
          if (!surfaceObjectSeen && !planeSeen) {
            surfaceObjectSeen = true;
            addShapeProperty(propertyList, "sasurface", new Float(0));
            sbCommand.append(" vdw");
          }
          propertyName = "property";
          if (smoothing == null)
            smoothing = viewer.getIsosurfacePropertySmoothing() ? Boolean.TRUE
                : Boolean.FALSE;
          addShapeProperty(propertyList, "propertySmoothing", smoothing);
          sbCommand.append(" isosurfacePropertySmoothing " + smoothing);
          if (viewer.isRangeSelected())
            addShapeProperty(propertyList, "rangeSelected", Boolean.TRUE);
        } else {
          propertyName = dataUse;
        }
        str = parameterAsString(i);
        sbCommand.append(" ").append(str);

        if (str.toLowerCase().indexOf("property_") == 0) {
          data = new float[viewer.getAtomCount()];
          if (isSyntaxCheck)
            continue;
          data = viewer.getDataFloat(str);
          if (data == null)
            error(ERROR_invalidArgument);
          addShapeProperty(propertyList, propertyName, data);
          continue;
        }

        int atomCount = viewer.getAtomCount();
        data = new float[atomCount];

        if (isVariable) {
          String vname = parameterAsString(++i);
          if (vname.length() == 0) {
            data = floatParameterSet(i, atomCount, atomCount);
          } else {
            data = new float[atomCount];
            if (!isSyntaxCheck)
              Parser.parseStringInfestedFloatArray(""
                  + getParameter(vname, Token.string), null, data);
          }
          if (!isSyntaxCheck)
            sbCommand.append(" \"\" ").append(Escape.escape(data));
        } else {
          int tokProperty = getToken(++i).tok;
          if (!isSyntaxCheck) {
            sbCommand.append(" " + theToken.value);
            Atom[] atoms = viewer.getModelSet().atoms;
            viewer.autoCalculate(tokProperty);
            for (int iAtom = atomCount; --iAtom >= 0;)
              data[iAtom] = Atom.atomPropertyFloat(viewer, atoms[iAtom],
                  tokProperty);
          }
          if (tokProperty == Token.color)
            colorScheme = "colorRGB";
          if (tokAt(i + 1) == Token.within) {
            float d = floatParameter(i = i + 2);
            sbCommand.append(" within " + d);
            addShapeProperty(propertyList, "propertyDistanceMax", Float.valueOf(d));
          }
        }
        propertyValue = data;
        break;
      case Token.model:
        if (surfaceObjectSeen)
          error(ERROR_invalidArgument);
        modelIndex = modelNumberParameter(++i);
        sbCommand.append(" model " + modelIndex);
        if (modelIndex < 0) {
          propertyName = "fixed";
          propertyValue = Boolean.TRUE;
          break;
        }
        propertyName = "modelIndex";
        propertyValue = new Integer(modelIndex);
        break;
      case Token.select:
        propertyName = "select";
        BitSet bs1 = atomExpression(++i);
        propertyValue = bs1;
        i = iToken;
        if (surfaceObjectSeen || isMapped) {
          sbCommand.append(" select " + Escape.escape(propertyValue));
        } else {
          bsSelect = (BitSet) propertyValue;
          if (modelIndex < 0 && bsSelect.nextSetBit(0) >= 0)
            modelIndex = viewer.getAtomModelIndex(bsSelect.nextSetBit(0));
        }
        break;
      case Token.set:
        thisSetNumber = intParameter(++i);
        sbCommand.append(" set " + thisSetNumber);
        break;
      case Token.offset:
        propertyName = "offset";
        propertyValue = centerParameter(++i);
        i = iToken;
        sbCommand.append(" offset " + Escape.escape((Point3f) propertyValue));
        break;
      case Token.center:
        propertyName = "center";
        propertyValue = centerParameter(++i);
        sbCommand.append(" center " + Escape.escape((Point3f) propertyValue));
        i = iToken;
        break;
      case Token.sign:
      case Token.color:
        int color;
        idSeen = true;
        sbCommand.append(" " + theToken.value);
        boolean isSign = (theTok == Token.sign);
        if (isSign) {
          addShapeProperty(propertyList, "sign", Boolean.TRUE);
        } else {
          if (tokAt(i + 1) == Token.density) {
            i++;
            propertyName = "colorDensity";
            sbCommand.append(" density");
            break;
          }
          /*
           * "color" now is just used as an equivalent to "sign" and as an
           * introduction to "absolute" any other use is superfluous; it has
           * been replaced with MAP for indicating "use the current surface"
           * because the term COLOR is too general.
           */

          if (getToken(i + 1).tok == Token.string) {
            colorScheme = parameterAsString(++i);
            sbCommand.append(" ").append(Escape.escape(colorScheme));
            if (colorScheme.indexOf(" ") > 0) {
              discreteColixes = Graphics3D.getColixArray(colorScheme);
              if (discreteColixes == null)
                error(ERROR_badRGBColor);
            }
          } else if (theTok == Token.mesh) {
            i++;
            sbCommand.append(" mesh");
            color = getArgbParam(++i);
            addShapeProperty(propertyList, "meshcolor", new Integer(color));
            sbCommand.append(" ").append(Escape.escapeColor(color));
            i = iToken;
            continue;
          }
          if ((theTok = tokAt(i + 1)) == Token.translucent
              || theTok == Token.opaque) {
            translucency = setColorOptions(sbCommand, i + 1,
                JmolConstants.SHAPE_ISOSURFACE, -2);
            i = iToken;
            continue;
          }
          switch (tokAt(i + 1)) {
          case Token.absolute:
          case Token.range:
            getToken(++i);
            sbCommand.append(" range");
            addShapeProperty(propertyList, "rangeAll", null);
            if (tokAt(i + 1) == Token.all) {
              i++;
              sbCommand.append(" all");
              continue;
            }
            float min = floatParameter(++i);
            float max = floatParameter(++i);
            addShapeProperty(propertyList, "red", new Float(min));
            addShapeProperty(propertyList, "blue", new Float(max));
            sbCommand.append(" ").append(min).append(" ").append(max);
            continue;
          }
        }
        if (isColorParam(i + 1)) {
          color = getArgbParam(++i);
          addShapeProperty(propertyList, "colorRGB", new Integer(color));
          sbCommand.append(" ").append(Escape.escapeColor(color));
          i = iToken;
          idSeen = true;
          if (isColorParam(i + 1)) {
            color = getArgbParam(++i);
            i = iToken;
            addShapeProperty(propertyList, "colorRGB", new Integer(color));
            sbCommand.append(" ").append(Escape.escapeColor(color));
          } else if (isSign) {
            error(ERROR_invalidParameterOrder);
          }
        } else if (!isSign && discreteColixes == null) {
          error(ERROR_invalidParameterOrder);
        }
        continue;
      case Token.file:
        continue;
      case Token.ionic:
      case Token.vanderwaals:
        sbCommand.append(" ").append(theToken.value);
        RadiusData rd = encodeRadiusParameter(i, false);
        sbCommand.append(" ").append(rd);
        if (Float.isNaN(rd.value))
          rd.value = 100;
        propertyValue = rd;
        propertyName = "radius";
        haveRadius = true;
        if (isMapped)
          surfaceObjectSeen = false;
        i = iToken;
        break;
      case Token.plane:
        // plane {X, Y, Z, W}
        planeSeen = true;
        propertyName = "plane";
        propertyValue = planeParameter(++i);
        i = iToken;
        sbCommand.append(" plane ").append(
            Escape.escape((Point4f) propertyValue));
        break;
      case Token.scale3d:
        propertyName = "scale3d";
        propertyValue = new Float(floatParameter(++i));
        sbCommand.append(" scale3d ").append(propertyValue);
        break;
      case Token.scale:
        propertyName = "scale";
        propertyValue = new Float(floatParameter(++i));
        sbCommand.append(" scale ").append(propertyValue);
        break;
      case Token.all:
        if (idSeen)
          error(ERROR_invalidArgument);
        propertyName = "thisID";
        break;
      case Token.ellipsoid:
        // ellipsoid {xc yc zc f} where a = b and f = a/c
        // OR ellipsoid {u11 u22 u33 u12 u13 u23}
        surfaceObjectSeen = true;
        ++i;
        try {
          propertyValue = getPoint4f(i);
          propertyName = "ellipsoid";
          i = iToken;
          sbCommand.append(" ellipsoid ").append(
              Escape.escape((Point4f) propertyValue));
          break;
        } catch (ScriptException e) {
        }
        try {
          propertyName = "ellipsoid";
          propertyValue = floatParameterSet(i, 6, 6);
          i = iToken;
          sbCommand.append(" ellipsoid ").append(
              Escape.escape((float[]) propertyValue));
          break;
        } catch (ScriptException e) {
        }
        bs = atomExpression(i);
        sbCommand.append(" ellipsoid ").append(Escape.escape(bs));
        int iAtom = bs.nextSetBit(0);
        Atom[] atoms = viewer.getModelSet().atoms;
        if (iAtom >= 0)
          propertyValue = atoms[iAtom].getEllipsoid();
        if (propertyValue == null)
          return;
        i = iToken;
        propertyName = "ellipsoid";
        if (!isSyntaxCheck)
          addShapeProperty(propertyList, "center", viewer.getAtomPoint3f(iAtom));
        break;
      case Token.hkl:
        // miller indices hkl
        planeSeen = true;
        propertyName = "plane";
        propertyValue = hklParameter(++i);
        i = iToken;
        sbCommand.append(" plane ").append(
            Escape.escape((Point4f) propertyValue));
        break;
      case Token.lcaocartoon:
        surfaceObjectSeen = true;
        String lcaoType = parameterAsString(++i);
        addShapeProperty(propertyList, "lcaoType", lcaoType);
        sbCommand.append(" lcaocartoon ").append(Escape.escape(lcaoType));
        switch (getToken(++i).tok) {
        case Token.bitset:
        case Token.expressionBegin:
          propertyName = "lcaoCartoon";
          bs = atomExpression(i);
          sbCommand.append(" ").append(Escape.escape(bs));
          i = iToken;
          int atomIndex = bs.nextSetBit(0);
          modelIndex = 0;
          Point3f pt;
          if (atomIndex < 0) {
            if (!isSyntaxCheck)
              error(ERROR_expressionExpected);
            pt = new Point3f();
          } else {
            modelIndex = viewer.getAtomModelIndex(atomIndex);
            pt = viewer.getAtomPoint3f(atomIndex);
          }
          addShapeProperty(propertyList, "modelIndex", new Integer(modelIndex));
          Vector3f[] axes = { new Vector3f(), new Vector3f(), new Vector3f(pt),
              new Vector3f() };
          if (!isSyntaxCheck
              && !lcaoType.equalsIgnoreCase("s")
              && viewer.getHybridizationAndAxes(atomIndex, axes[0], axes[1],
                  lcaoType) == null)
            return;
          propertyValue = axes;
          break;
        default:
          error(ERROR_expressionExpected);
        }
        break;
      case Token.mo:
        // mo 1-based-index
        int moNumber = Integer.MAX_VALUE;
        int offset = Integer.MAX_VALUE;
        switch (tokAt(++i)) {
        case Token.nada:
          error(ERROR_badArgumentCount);
        case Token.homo:
        case Token.lumo:
          offset = moOffset(i);
          moNumber = 0;
          i = iToken;
          sbCommand.append(" mo HOMO ");
          if (offset > 0)
            sbCommand.append("+");
          if (offset != 0)
            sbCommand.append(offset);
          break;
        case Token.integer:
          moNumber = intParameter(i);
          sbCommand.append(" mo ").append(moNumber);
          break;
        }
        setMoData(propertyList, moNumber, offset, modelIndex, null);
        surfaceObjectSeen = true;
        continue;
      case Token.mep:
      case Token.mlp:
        boolean isMep = (theTok == Token.mep);
        propertyName = (isMep ? "mep" : "mlp");
        sbCommand.append(" " + propertyName);
        String fname = null;
        int calcType = -1;
        surfaceObjectSeen = true;
        if (tokAt(i + 1) == Token.integer) {
          calcType = intParameter(++i);
          sbCommand.append(" " + calcType);
          addShapeProperty(propertyList, "mepCalcType", new Integer(calcType));
        }
        if (tokAt(i + 1) == Token.string) {
          fname = stringParameter(++i);
          sbCommand.append(" /*file*/" + Escape.escape(fname));
        } else if (tokAt(i + 1) == Token.property) {
          dataUse = propertyName;
          continue;
        }
        if (!isSyntaxCheck)
          try {
            data = (fname == null && isMep ? viewer.getPartialCharges()
                : viewer.getAtomicPotentials(isMep, bsSelect, bsIgnore, fname));
          } catch (Exception e) {
            // ignore
          }
        if (!isSyntaxCheck && data == null)
          error(ERROR_noPartialCharges);
        propertyValue = data;
        break;
      case Token.volume:
        doCalcVolume = !isSyntaxCheck;
        sbCommand.append(" volume");
        break;
      case Token.id:
        setShapeId(iShape, ++i, idSeen);
        isWild = (getShapeProperty(iShape, "ID") == null);
        i = iToken;
        break;
      case Token.colorscheme:
        // either order NOT OK -- documented for TRANSLUCENT "rwb"
        if (tokAt(i + 1) == Token.translucent) {
          isColorSchemeTranslucent = true;
          i++;
        }
        colorScheme = parameterAsString(++i);
        sbCommand.append(" colorScheme");
        if (isColorSchemeTranslucent)
          sbCommand.append(" translucent");
        sbCommand.append(" ").append(Escape.escape(colorScheme));
        break;
      case Token.addhydrogens:
        propertyName = "addHydrogens";
        propertyValue = Boolean.TRUE;
        sbCommand.append(" addHydrogens");
        break;
      case Token.angstroms:
        propertyName = "angstroms";
        sbCommand.append(" angstroms");
        break;
      case Token.anisotropy:
        propertyName = "anisotropy";
        propertyValue = getPoint3f(++i, false);
        sbCommand.append(" anisotropy").append(
            Escape.escape((Point3f) propertyValue));
        i = iToken;
        break;
      case Token.area:
        doCalcArea = !isSyntaxCheck;
        sbCommand.append(" area");
        break;
      case Token.atomicorbital:
      case Token.orbital:
        surfaceObjectSeen = true;
        nlmZ[0] = intParameter(++i);
        nlmZ[1] = intParameter(++i);
        nlmZ[2] = intParameter(++i);
        nlmZ[3] = (isFloatParameter(i + 1) ? floatParameter(++i) : 6f);
        sbCommand.append(" atomicOrbital ").append((int) nlmZ[0]).append(" ")
            .append((int) nlmZ[1]).append(" ").append((int) nlmZ[2])
            .append(" ").append(nlmZ[3]);
        propertyName = "hydrogenOrbital";
        propertyValue = nlmZ;
        break;
      case Token.binary:
        sbCommand.append(" binary");
        // for PMESH, specifically
        // ignore for now
        continue;
      case Token.blockdata:
        sbCommand.append(" blockData");
        propertyName = "blockData";
        propertyValue = Boolean.TRUE;
        break;
      case Token.cap:
      case Token.slab:
        propertyName = (String) theToken.value;
        propertyValue = getCapSlabObject(sbCommand, i);
        i = iToken;
        break;
      case Token.cavity:
        if (!isIsosurface)
          error(ERROR_invalidArgument);
        isCavity = true;
        if (isSyntaxCheck)
          continue;
        float cavityRadius = (isFloatParameter(i + 1) ? floatParameter(++i)
            : 1.2f);
        float envelopeRadius = (isFloatParameter(i + 1) ? floatParameter(++i)
            : 10f);
        if (envelopeRadius > 10f)
          integerOutOfRange(0, 10);
        sbCommand.append(" cavity ").append(cavityRadius).append(" ").append(
            envelopeRadius);
        addShapeProperty(propertyList, "envelopeRadius", new Float(
            envelopeRadius));
        addShapeProperty(propertyList, "cavityRadius", new Float(cavityRadius));
        propertyName = "cavity";
        break;
      case Token.contour:
      case Token.contours:
        propertyName = "contour";
        sbCommand.append(" contour");
        switch (tokAt(i + 1)) {
        case Token.discrete:
          propertyValue = floatParameterSet(i + 2, 1, Integer.MAX_VALUE);
          sbCommand.append(" discrete ").append(Escape.escape(propertyValue));
          i = iToken;
          break;
        case Token.increment:
          Point3f pt = getPoint3f(i + 2, false);
          if (pt.z <= 0 || pt.y < pt.x)
            error(ERROR_invalidArgument); // from to step
          if (pt.z == (int) pt.z && pt.z > (pt.y - pt.x))
            pt.z = (pt.y - pt.x) / pt.z;
          propertyValue = pt;
          i = iToken;
          sbCommand.append(" increment ").append(Escape.escape(pt));
          break;
        default:
          propertyValue = new Integer(
              tokAt(i + 1) == Token.integer ? intParameter(++i) : 0);
          sbCommand.append(" ").append(propertyValue);
        }
        break;
      case Token.decimal:
      case Token.integer:
      case Token.plus:
      case Token.cutoff:
        sbCommand.append(" cutoff ");
        if (theTok == Token.cutoff)
          i++;
        if (tokAt(i) == Token.plus) {
          propertyName = "cutoffPositive";
          propertyValue = new Float(cutoff = floatParameter(++i));
          sbCommand.append("+").append(propertyValue);
        } else {
          propertyName = "cutoff";
          propertyValue = new Float(cutoff = floatParameter(i));
          sbCommand.append(propertyValue);
        }
        break;
      case Token.downsample:
        propertyName = "downsample";
        propertyValue = new Integer(intParameter(++i));
        sbCommand.append(" downsample ").append(propertyValue);
        break;
      case Token.eccentricity:
        propertyName = "eccentricity";
        propertyValue = getPoint4f(++i);
        sbCommand.append(" eccentricity ").append(
            Escape.escape((Point4f) propertyValue));
        i = iToken;
        break;
      case Token.ed:
        sbCommand.append(" ed");
        // electron density - never documented
        setMoData(propertyList, -1, 0, modelIndex, null);
        surfaceObjectSeen = true;
        continue;
      case Token.debug:
      case Token.nodebug:
        sbCommand.append(" ").append(theToken.value);
        propertyName = "debug";
        propertyValue = (theTok == Token.debug ? Boolean.TRUE : Boolean.FALSE);
        break;
      case Token.fixed:
        sbCommand.append(" fixed");
        propertyName = "fixed";
        propertyValue = Boolean.TRUE;
        break;
      case Token.fullplane:
        sbCommand.append(" fullPlane");
        propertyName = "fullPlane";
        propertyValue = Boolean.TRUE;
        break;
      case Token.functionxy:
      case Token.functionxyz:
        boolean isFxyz = (theTok == Token.functionxyz);
        propertyName = "" + theToken.value;
        // isosurface functionXY "functionName"|"data2d_xxxxx"
        // isosurface functionXYZ "functionName"|"data3d_xxxxx"
        // {origin} {ni ix iy iz} {nj jx jy jz} {nk kx ky kz}
        List vxy = new ArrayList();
        i++;
        String name = parameterAsString(i++);
        // override of function or data name when saved as a state
        String dName = extractCommandOption("# DATA" + (isFxy ? "2" : ""));
        if (dName == null)
          dName = "inline";
        else
          name = dName;
        sbCommand.append(" ").append(propertyName).append(" inline");
        boolean isXYZ = (name.indexOf("data2d_") == 0);
        boolean isXYZV = (name.indexOf("data3d_") == 0);
        boolean isInline = name.equals("inline");
        vxy.add(name); // (0) = name
        Point3f pt3 = getPoint3f(i, false);
        sbCommand.append(" ").append(Escape.escape(pt3));
        vxy.add(pt3); // (1) = {origin}
        Point4f pt4;
        ptX = ++iToken;
        vxy.add(pt4 = getPoint4f(ptX)); // (2) = {ni ix iy iz}
        sbCommand.append(" ").append(Escape.escape(pt4));
        nX = (int) pt4.x;
        ptY = ++iToken;
        vxy.add(pt4 = getPoint4f(ptY)); // (3) = {nj jx jy jz}
        sbCommand.append(" ").append(Escape.escape(pt4));
        nY = (int) pt4.x;
        vxy.add(pt4 = getPoint4f(++iToken)); // (4) = {nk kx ky kz}
        sbCommand.append(" ").append(Escape.escape(pt4));
        nZ = (int) pt4.x;

        if (nX == 0 || nY == 0 || nZ == 0)
          error(ERROR_invalidArgument);
        if (!isSyntaxCheck) {
          float[][] fdata = null;
          float[][][] xyzdata = null;
          if (isFxyz) {
            if (isInline) {
              nX = Math.abs(nX);
              nY = Math.abs(nY);
              nZ = Math.abs(nZ);
              xyzdata = floatArraySet(++iToken, nX, nY, nZ);
            } else if (isXYZV) {
              xyzdata = viewer.getDataFloat3D(name);
            } else {
              xyzdata = viewer.functionXYZ(name, nX, nY, nZ);
            }
            nX = Math.abs(nX);
            nY = Math.abs(nY);
            nZ = Math.abs(nZ);
            if (xyzdata == null) {
              iToken = ptX;
              error(ERROR_what, "xyzdata is null.");
            }
            if (xyzdata.length != nX || xyzdata[0].length != nY
                || xyzdata[0][0].length != nZ) {
              iToken = ptX;
              error(ERROR_what, "xyzdata[" + xyzdata.length + "]["
                  + xyzdata[0].length + "][" + xyzdata[0][0].length
                  + "] is not of size [" + nX + "][" + nY + "][" + nZ + "]");
            }
            vxy.add(xyzdata); // (5) = float[][][] data
            sbCommand.append(" ").append(Escape.escape(xyzdata));
          } else {
            if (isInline) {
              nX = Math.abs(nX);
              nY = Math.abs(nY);
              fdata = floatArraySet(++iToken, nX, nY);
            } else if (isXYZ) {
              fdata = viewer.getDataFloat2D(name);
              nX = (fdata == null ? 0 : fdata.length);
              nY = 3;
            } else {
              fdata = viewer.functionXY(name, nX, nY);
              nX = Math.abs(nX);
              nY = Math.abs(nY);
            }
            if (fdata == null) {
              iToken = ptX;
              error(ERROR_what, "fdata is null.");
            }
            if (fdata.length != nX && !isXYZ) {
              iToken = ptX;
              error(ERROR_what, "fdata length is not correct: " + fdata.length
                  + " " + nX + ".");
            }
            for (int j = 0; j < nX; j++) {
              if (fdata[j] == null) {
                iToken = ptY;
                error(ERROR_what, "fdata[" + j + "] is null.");
              }
              if (fdata[j].length != nY) {
                iToken = ptY;
                error(ERROR_what, "fdata[" + j + "] is not the right length: "
                    + fdata[j].length + " " + nY + ".");
              }
            }
            vxy.add(fdata); // (5) = float[][] data
            sbCommand.append(" ").append(Escape.escape(fdata));
          }
        }
        i = iToken;
        propertyValue = vxy;
        isFxy = surfaceObjectSeen = true;
        break;
      case Token.gridpoints:
        propertyName = "gridPoints";
        sbCommand.append(" gridPoints");
        break;
      case Token.ignore:
        propertyName = "ignore";
        propertyValue = bsIgnore = atomExpression(++i);
        sbCommand.append(" ignore ").append(
            Escape.escape((BitSet) propertyValue));
        i = iToken;
        break;
      case Token.insideout:
        propertyName = "insideOut";
        sbCommand.append(" insideout");
        break;
      case Token.internal:
      case Token.interior:
      case Token.pocket:
        sbCommand.append(" ").append(theToken.value);
        propertyName = "pocket";
        propertyValue = (theTok == Token.pocket ? Boolean.TRUE : Boolean.FALSE);
        break;
      case Token.lobe:
        // lobe {eccentricity}
        surfaceObjectSeen = true;
        propertyName = "lobe";
        propertyValue = getPoint4f(++i);
        i = iToken;
        sbCommand.append(" lobe ").append(
            Escape.escape((Point4f) propertyValue));
        break;
      case Token.lonepair:
      case Token.lp:
        // lp {eccentricity}
        surfaceObjectSeen = true;
        propertyName = "lp";
        propertyValue = getPoint4f(++i);
        i = iToken;
        sbCommand.append(" lp ").append(Escape.escape((Point4f) propertyValue));
        break;
      case Token.mapProperty:
        if (isMapped || statementLength == i + 1)
          error(ERROR_invalidArgument);
        sbCommand.append(" map");
        isMapped = true;
        if ((isCavity || haveRadius) && !surfaceObjectSeen) {
          surfaceObjectSeen = true;
          addShapeProperty(propertyList, "bsSolvent",
              (haveRadius ? new BitSet() : lookupIdentifierValue("solvent")));
          addShapeProperty(propertyList, "sasurface", new Float(0));
        }
        //surfaceObjectSeen = !isCavity;
        propertyName = "map";
        break;
      case Token.maxset:
        propertyName = "maxset";
        propertyValue = new Integer(intParameter(++i));
        sbCommand.append(" maxSet ").append(propertyValue);
        break;
      case Token.minset:
        propertyName = "minset";
        propertyValue = new Integer(intParameter(++i));
        sbCommand.append(" minSet ").append(propertyValue);
        break;
      case Token.radical:
        // rad {eccentricity}
        surfaceObjectSeen = true;
        propertyName = "rad";
        propertyValue = getPoint4f(++i);
        i = iToken;
        sbCommand.append(" radical ").append(
            Escape.escape((Point4f) propertyValue));
        break;
      case Token.modelbased:
        propertyName = "fixed";
        propertyValue = Boolean.FALSE;
        sbCommand.append(" modelBased");
        break;
      case Token.molecular:
      case Token.sasurface:
      case Token.solvent:
        if (modelIndex < 0)
          error(ERROR_multipleModelsDisplayedNotOK, "ISOSURFACE "
              + theToken.value);
        surfaceObjectSeen = true;
        float radius;
        if (theTok == Token.molecular) {
          propertyName = "molecular";
          sbCommand.append(" molecular");
          radius = 1.4f;
        } else {
          addShapeProperty(propertyList, "bsSolvent",
              lookupIdentifierValue("solvent"));
          propertyName = (theTok == Token.sasurface ? "sasurface" : "solvent");
          sbCommand.append(" ").append(theToken.value);
          radius = (isFloatParameter(i + 1) ? floatParameter(++i) : viewer
              .getSolventProbeRadius());
          sbCommand.append(" ").append(radius);
        }
        propertyValue = Float.valueOf(radius);
        break;
      case Token.object:
      case Token.obj:
        addShapeProperty(propertyList, "fileType", "Obj");
        sbCommand.append(" obj");
        continue;
      case Token.phase:
        if (surfaceObjectSeen)
          error(ERROR_invalidArgument);
        propertyName = "phase";
        propertyValue = (tokAt(i + 1) == Token.string ? stringParameter(++i)
            : "_orb");
        sbCommand.append(" phase ").append(Escape.escape(propertyValue));
        break;
      case Token.pointsperangstrom:
      case Token.resolution:
        propertyName = "resolution";
        propertyValue = new Float(floatParameter(++i));
        sbCommand.append(" resolution ").append(propertyValue);
        break;
      case Token.reversecolor:
        propertyName = "reverseColor";
        propertyValue = Boolean.TRUE;
        sbCommand.append(" reversecolor");
        break;
      case Token.sigma:
        propertyName = "sigma";
        propertyValue = new Float(sigma = floatParameter(++i));
        sbCommand.append(" sigma ").append(propertyValue);
        break;
      case Token.sphere:
        // sphere [radius]
        surfaceObjectSeen = true;
        propertyName = "sphere";
        propertyValue = new Float(floatParameter(++i));
        sbCommand.append(" sphere ").append(propertyValue);
        break;
      case Token.squared:
        propertyName = "squareData";
        propertyValue = Boolean.TRUE;
        sbCommand.append(" squared");
        break;
      case Token.string:
        String filename = parameterAsString(i);
        boolean firstPass = (!surfaceObjectSeen && !planeSeen);
        if (filename.startsWith("=") && filename.length() > 1) {
          // Uppsala Electron Density Server (default, at least)
          String[] info = (String[]) viewer.setLoadFormat(filename, '_', false);
          filename = info[0];
          String strCutoff = (!firstPass || !Float.isNaN(cutoff) ? null
              : info[1]);
          if (strCutoff != null && !isSyntaxCheck) {
            cutoff = ScriptVariable.fValue(ScriptVariable.getVariable(viewer
                .evaluateExpression(strCutoff)));
            if (cutoff > 0) {
              if (!Float.isNaN(sigma)) {
                cutoff *= sigma;
                sigma = Float.NaN;
                addShapeProperty(propertyList, "sigma", new Float(sigma));
              }
              addShapeProperty(propertyList, "cutoff", new Float(cutoff));
              sbCommand.append(" cutoff ").append(cutoff);
            }
          }
          if (ptWithin == 0) {
            bs = viewer.getModelUndeletedAtomsBitSet(modelIndex);
            getWithinDistanceVector(propertyList, 2.0f, null, bs);
            sbCommand.append(" within 2.0 ").append(Escape.escape(bs));
          }
          if (firstPass)
            defaultMesh = true;
        }
        if (firstPass && viewer.getParameter("_fileType").equals("Pdb")
            && Float.isNaN(sigma) && Float.isNaN(cutoff)) {
          // negative sigma just indicates that
          addShapeProperty(propertyList, "sigma", new Float(-1));
          sbCommand.append(" sigma -1.0");
        }
        propertyName = (firstPass ? "readFile" : "mapColor");
        /*
         * a file name, optionally followed by an integer file index. OR empty.
         * In that case, if the model auxiliary info has the data stored in it,
         * we use that. There are two possible structures:
         *
         * jmolSurfaceInfo jmolMappedDataInfo
         *
         * Both can be present, but if jmolMappedDataInfo is missing, then
         * jmolSurfaceInfo is used by default.
         */

        if (filename.equals("TESTDATA") && testData != null) {
          propertyValue = testData;
          break;
        }
        if (filename.equals("TESTDATA2") && testData2 != null) {
          propertyValue = testData2;
          break;
        }
        if (filename.length() == 0) {
          if (surfaceObjectSeen || planeSeen)
            propertyValue = viewer.getModelAuxiliaryInfo(modelIndex,
                "jmolMappedDataInfo");
          if (propertyValue == null)
            propertyValue = viewer.getModelAuxiliaryInfo(modelIndex,
                "jmolSurfaceInfo");
          surfaceObjectSeen = true;
          if (propertyValue != null)
            break;
          filename = getFullPathName();
        }
        surfaceObjectSeen = true;
        int fileIndex = -1;
        if (tokAt(i + 1) == Token.integer)
          addShapeProperty(propertyList, "fileIndex", new Integer(
              fileIndex = intParameter(++i)));
        if (filename.equalsIgnoreCase("INLINE")) {
          // inline PMESH data
          if (tokAt(i + 1) != Token.string)
            error(ERROR_stringExpected);
          // addShapeProperty(propertyList, "fileType", "Pmesh");
          String sdata = parameterAsString(++i);
          if (isPmesh)
            sdata = TextFormat.replaceAllCharacters(sdata, "{,}|", ' ');
          if (logMessages)
            Logger.debug("pmesh inline data:\n" + sdata);
          propertyValue = (isSyntaxCheck ? null : sdata);
          addShapeProperty(propertyList, "fileName", "");
          sbCommand.append(" INLINE ").append(Escape.escape(sdata));
        } else if (!isSyntaxCheck) {
          String[] fullPathNameOrError;
          String localName = null;
          if (fullCommand.indexOf("# FILE" + nFiles + "=") >= 0) {
            filename = extractCommandOption("# FILE" + nFiles);
            if (tokAt(i + 1) == Token.as)
              i += 2; // skip that
          } else if (tokAt(i + 1) == Token.as) {
            localName = viewer.getFilePath(
                stringParameter(iToken = (i = i + 2)), false);
            fullPathNameOrError = viewer.getFullPathNameOrError(localName);
            localName = fullPathNameOrError[0];
            addShapeProperty(propertyList, "localName", localName);
          }
          // just checking here, and getting the full path name
          fullPathNameOrError = viewer.getFullPathNameOrError(filename);
          filename = fullPathNameOrError[0];
          if (fullPathNameOrError[1] != null)
            error(ERROR_fileNotFoundException, filename + ":"
                + fullPathNameOrError[1]);
          Logger.info("reading isosurface data from " + filename);
          addShapeProperty(propertyList, "fileName", filename);
          if (localName != null)
            filename = localName;
          sbCommand.append(" /*file*/").append(Escape.escape(filename));
          propertyValue = null;
          // null value indicates that we need a reader based on the fileName
        }
        if (fileIndex >= 0)
          sbCommand.append(" ").append(fileIndex);
        break;
      case Token.identifier:
        if (str.equalsIgnoreCase("LINK")) { // for state of lcaoCartoon
          propertyName = "link";
          sbCommand.append(" link");
          break;
        } else if (str.equalsIgnoreCase("REMAPPABLE")) { // testing only
          propertyName = "remappable";
          sbCommand.append(" remappable");
          break;
        } else {
          propertyName = "thisID";
          propertyValue = str;
        }
        // fall through
      default:
        if (planeSeen && !surfaceObjectSeen) {
          addShapeProperty(propertyList, "nomap", new Float(0));
          surfaceObjectSeen = true;
        }
        if (!setMeshDisplayProperty(iShape, 0, theTok)) {
          if (Token.tokAttr(theTok, Token.identifier) && !idSeen) {
            setShapeId(iShape, i, idSeen);
            i = iToken;
            break;
          }
          error(ERROR_invalidArgument);
        }
        if (iptDisplayProperty == 0)
          iptDisplayProperty = i;
        i = statementLength - 1;
        break;
      }
      idSeen = (theTok != Token.delete);
      if (isWild && surfaceObjectSeen)
        error(ERROR_invalidArgument);
      if (propertyName != null)
        addShapeProperty(propertyList, propertyName, propertyValue);
    }
    if ((isCavity || haveRadius) && !surfaceObjectSeen) {
      surfaceObjectSeen = true;
      addShapeProperty(propertyList, "bsSolvent",
          (haveRadius ? new BitSet() : lookupIdentifierValue("solvent")));
      addShapeProperty(propertyList, "sasurface", new Float(0));
    }

    if (planeSeen && !surfaceObjectSeen) {
      addShapeProperty(propertyList, "nomap", new Float(0));
      surfaceObjectSeen = true;
    }
    if (thisSetNumber >= 0)
      addShapeProperty(propertyList, "getSurfaceSets", new Integer(
          thisSetNumber - 1));
    if (discreteColixes != null) {
      addShapeProperty(propertyList, "colorDiscrete", discreteColixes);
    } else if ("sets".equals(colorScheme)) {
      addShapeProperty(propertyList, "setColorScheme",
          isColorSchemeTranslucent ? Boolean.TRUE : Boolean.FALSE);
    } else if (colorScheme != null) {
      ColorEncoder ce = viewer.getColorEncoder(colorScheme);
      if (ce != null) {
        ce.isTranslucent = isColorSchemeTranslucent;
        ce.hi = Float.MAX_VALUE;
        addShapeProperty(propertyList, "remapColor", ce);
      }
    }

    // OK, now send them all
    setShapeProperty(iShape, "setProperties", propertyList);

    if (defaultMesh) {
      setShapeProperty(iShape, "token", new Integer(Token.mesh));
      setShapeProperty(iShape, "token", new Integer(Token.nofill));
      setShapeProperty(iShape, "token", new Integer(Token.frontonly));
      sbCommand.append(" mesh nofill frontOnly");
    }
    if (iptDisplayProperty > 0) {
      if (!setMeshDisplayProperty(iShape, iptDisplayProperty,
          getToken(iptDisplayProperty).tok))
        error(ERROR_invalidArgument);
    }

    Object area = null;
    Object volume = null;
    if (doCalcArea) {
      area = getShapeProperty(iShape, "area");
      if (area instanceof Float)
        viewer.setFloatProperty("isosurfaceArea", ((Float) area).floatValue());
      else
        viewer.setUserVariable("isosurfaceArea", ScriptVariable
            .getVariable(area));
    }
    if (doCalcVolume) {
      volume = (doCalcVolume ? getShapeProperty(iShape, "volume") : null);
      if (volume instanceof Float)
        viewer.setFloatProperty("isosurfaceVolume", ((Float) volume)
            .floatValue());
      else
        viewer.setUserVariable("isosurfaceVolume", ScriptVariable
            .getVariable(volume));
    }
    if (surfaceObjectSeen && !isLcaoCartoon && !isSyntaxCheck) {
      if (bsSelect == null)
        bsSelect = BitSetUtil.copy(viewer.getSelectionSet(false));
      bsSelect.and(viewer.getModelUndeletedAtomsBitSet(modelIndex));
      setShapeProperty(iShape, "finalize", " select " + Escape.escape(bsSelect) + " "
          + sbCommand);
      String s = (String) getShapeProperty(iShape, "ID");
      if (s != null) {
        cutoff = ((Float) getShapeProperty(iShape, "cutoff")).floatValue();
        if (Float.isNaN(cutoff) && !Float.isNaN(sigma)) {
          Logger.error("sigma not supported");
        }
        s += " created with cutoff=" + cutoff;
        float[] minMax = (float[]) getShapeProperty(iShape, "minMaxInfo");
        if (minMax[0] != Float.MAX_VALUE)
          s += " min=" + minMax[0] + " max=" + minMax[1];
        s += "; number of isosurfaces = " + getShapeProperty(iShape, "count");
        s += getIsosurfaceDataRange(iShape, "\n");
        if (doCalcArea)
          s += "\nisosurfaceArea = " + Escape.escapeArray(area);
        if (doCalcVolume)
          s += "\nisosurfaceVolume = " + Escape.escapeArray(volume);
        showString(s);
      }
    } else if (doCalcArea || doCalcVolume) {
      if (doCalcArea)
        showString("isosurfaceArea = " + Escape.escapeArray(area));
      if (doCalcVolume)
        showString("isosurfaceVolume = " + Escape.escapeArray(volume));
    }
    if (translucency != null)
      setShapeProperty(iShape, "translucency", translucency);
    setShapeProperty(iShape, "clear", null);
  }

  private String getIsosurfaceDataRange(int iShape, String sep) {
    float[] dataRange = (float[]) getShapeProperty(iShape, "dataRange");
    return (dataRange != null && dataRange[0] != Float.MAX_VALUE
        && dataRange[0] != dataRange[1] ? sep + "isosurface"
            + " full data range " + dataRange[0] + " to " + dataRange[1]
            + " with color scheme spanning " + dataRange[2] + " to " + dataRange[3]
         : "");
  }

  private static Object testData; // for isosurface
  private static Object testData2; // for isosurface

  private void getWithinDistanceVector(List propertyList, float distance,
                                       Point3f ptc, BitSet bs) {
    List v = new ArrayList();
    Point3f[] pts = new Point3f[2];
    if (bs == null) {
      Point3f pt1 = new Point3f(distance, distance, distance);
      Point3f pt0 = new Point3f(ptc);
      pt0.sub(pt1);
      pt1.add(ptc);
      pts[0] = pt0;
      pts[1] = pt1;
      v.add(ptc);
    } else {
      BoxInfo bbox = viewer.getBoxInfo(bs, -distance);
      pts[0] = bbox.getBboxVertices()[0];
      pts[1] = bbox.getBboxVertices()[7];
      if (bs.cardinality() == 1)
        v.add(viewer.getAtomPoint3f(bs.nextSetBit(0)));
    }
    if (v.size() == 1) {
      addShapeProperty(propertyList, "withinDistance", new Float(distance));
      addShapeProperty(propertyList, "withinPoint", (Point3f) v.get(0));
    }
    addShapeProperty(propertyList, "withinPoints", new Object[] {
        new Float(distance), pts, bs, v });
  }

  /**
   * @param shape
   * @param i
   * @param tok
   * @return true if successful
   * @throws ScriptException
   */
  private boolean setMeshDisplayProperty(int shape, int i, int tok)
      throws ScriptException {
    String propertyName = null;
    Object propertyValue = null;
    boolean checkOnly = (i == 0);
    // these properties are all processed in MeshCollection.java
    if (!checkOnly)
      iToken = i;
    switch (tok) {
    case Token.lattice:
      if (checkOnly)
        return true;
      Point3f lattice = getPoint3f(iToken + 1, false);
      lattice.x = (int) lattice.x;
      lattice.y = (int) lattice.y;
      lattice.z = (int) lattice.z;
      propertyName = "lattice";
      propertyValue = lattice;
      break;
    case Token.opaque:
    case Token.translucent:
      if (!checkOnly)
        colorShape(shape, iToken, false);
      return true;
    case Token.nada:
    case Token.delete:
    case Token.on:
    case Token.off:
    case Token.hide:
    case Token.hidden:
    case Token.display:
    case Token.displayed:
      if (iToken == 1 && shape >= 0)
        setShapeProperty(shape, "thisID", null);
      if (tok == Token.nada)
        return (iToken == 1);
      if (checkOnly)
        return true;
      if (tok == Token.delete) {
        setShapeProperty(shape, "delete", null);
        return true;
      }
      if (tok == Token.hidden || tok == Token.hide)
        tok = Token.off;
      else if (tok == Token.displayed || tok == Token.display)
        tok = Token.on;
      // fall through for on/off
    case Token.frontlit:
    case Token.backlit:
    case Token.fullylit:
    case Token.contourlines:
    case Token.nocontourlines:
    case Token.dots:
    case Token.nodots:
    case Token.mesh:
    case Token.nomesh:
    case Token.fill:
    case Token.nofill:
    case Token.triangles:
    case Token.notriangles:
    case Token.frontonly:
    case Token.notfrontonly:
      propertyName = "token";
      propertyValue = new Integer(tok);
      break;
    }
    if (propertyName == null)
      return false;
    if (checkOnly)
      return true;
    setShapeProperty(shape, propertyName, propertyValue);
    if ((tok = tokAt(iToken + 1)) != Token.nada) {
      if (!setMeshDisplayProperty(shape, ++iToken, tok))
        --iToken;
    }
    return true;
  }

  private void bind() throws ScriptException {
    /*
     * bind "MOUSE-ACTION" actionName bind "MOUSE-ACTION" "script" range
     * [xyrange] [xyrange]
     */
    String mouseAction = stringParameter(1);
    String name = parameterAsString(2);
    Point3f range1 = null;
    Point3f range2 = null;
    if (tokAt(3) == Token.range) {
      range1 = xypParameter(4);
      range2 = xypParameter(++iToken);
      checkLast(iToken);
    } else {
      checkLength(3);
    }
    if (!isSyntaxCheck)
      viewer.bindAction(mouseAction, name, range1, range2);
  }

  private void unbind() throws ScriptException {
    /*
     * unbind "MOUSE-ACTION"|all ["...script..."|actionName|all]
     */
    if (statementLength != 1)
      checkLength23();
    String mouseAction = optParameterAsString(1);
    String name = optParameterAsString(2);
    if (mouseAction.length() == 0 || tokAt(1) == Token.all)
      mouseAction = null;
    if (name.length() == 0 || tokAt(2) == Token.all)
      name = null;
    if (name == null && mouseAction != null
        && ActionManager.getActionFromName(mouseAction) >= 0) {
      name = mouseAction;
      mouseAction = null;
    }
    if (!isSyntaxCheck)
      viewer.unBindAction(mouseAction, name);
  }
}
TOP

Related Classes of org.jmol.script.ScriptEvaluator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.