Package org.jmol.script

Source Code of org.jmol.script.ScriptCompiler

/* $Author: hansonr $
* $Date: 2011-05-29 01:32:52 +0200 (dim., 29 mai 2011) $
* $Revision: 15508 $
*
* Copyright (C) 2002-2005  The Jmol Development Team
*
* 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 org.jmol.util.Escape;
import org.jmol.util.CommandHistory;
import org.jmol.util.JpegEncoder;
import org.jmol.util.Logger;
import org.jmol.util.Parser;
import org.jmol.viewer.JmolConstants;
import org.jmol.viewer.Viewer;
import org.jmol.i18n.GT;
import org.jmol.modelset.Group;
import org.jmol.modelset.Bond.BondSet;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.BitSet;

import javax.vecmath.Matrix3f;
import javax.vecmath.Matrix4f;

public class ScriptCompiler extends ScriptCompilationTokenParser {

  /*
   * The Compiler class is really two parts --
   *
   * Compiler.class          going from characters to tokens
   * CompilationTokenParser  further syntax checking and modifications
   *
   * The data structures follow the following sequences:
   *
   * String script ==> List lltoken[][] --> Token[][] aatokenCompiled[][]
   *
   * A given command goes through the sequence:
   *
   * String characters --> Token token --> List ltoken[] --> Token[][] aatokenCompiled[][]
   *
   */
 
  public ScriptCompiler(Viewer viewer) {
    this.viewer = viewer;
  }
 
  public ScriptCompiler(ScriptCompiler compiler) {
    this.viewer = compiler.viewer;
  }

  private String filename;
  private boolean isSilent;

  // returns:
 
  private Hashtable contextVariables;
  private Token[][] aatokenCompiled;
  private short[] lineNumbers;
  private int[][] lineIndices;
 
  private int lnLength = 8;
  private boolean preDefining;
  private boolean isShowScriptOutput;
  private boolean isCheckOnly;
  private boolean haveComments;

  String scriptExtensions;
 
  private ScriptFunction thisFunction;
 
  /**
   * return a structure that is only the first part of the process - identifying lines and commands
   * for the scriptEditor
   *
   * @param script
   * @return        ScriptContext
   */
 
  ScriptContext parseScriptForTokens(String script) {
    this.script = script;
    filename = null;
    isCheckOnly = true;
    isSilent = true;
    logMessages = false;
    preDefining = false;
    return parseScript(false);
  }
  private ScriptContext parseScript(boolean doFull) {
    if (!compile0(doFull))
      handleError();
    ScriptContext sc = new ScriptContext();
    sc.script = script;
    sc.scriptExtensions = scriptExtensions;
    sc.errorType = errorType;
    if (errorType != null) {
      sc.iCommandError = iCommand;
      setAaTokenCompiled();
    }
    sc.aatoken = aatokenCompiled;
    sc.errorMessage = errorMessage;
    sc.errorMessageUntranslated = (errorMessageUntranslated == null
        ? errorMessage : errorMessageUntranslated);
    sc.lineIndices = lineIndices;
    sc.lineNumbers = lineNumbers;
    sc.contextVariables = contextVariables;
    return sc;
  }

  ScriptContext compile(String filename, String script, boolean isPredefining,
                  boolean isSilent, boolean debugScript, boolean isCheckOnly) {
    this.isCheckOnly = isCheckOnly;
    this.filename = filename;
    this.isSilent = isSilent;
    this.script = script;
    logMessages = (!isSilent && !isPredefining && debugScript);
    preDefining = (filename == "#predefine");
    return parseScript(true);
  }

  private void addContextVariable(String ident) {
    theToken = new Token(Token.identifier, ident);
    if (pushCount > 0) {
      ContextToken ct = (ContextToken) vPush.get(pushCount - 1);
      ct.addName(ident);
      if (ct.tok != Token.trycmd)
        return;
    }
    if (thisFunction == null) {
      if (contextVariables == null)
        contextVariables = new Hashtable();
      addContextVariable(contextVariables, ident);
    } else {
      thisFunction.addVariable(ident, false);
    }
  }
 
  static void addContextVariable(Hashtable contextVariables, String ident) {
    contextVariables.put(ident, (new ScriptVariable(Token.string, "")).setName(ident));
  }

  private boolean isContextVariable(String ident) {
    for (int i = vPush.size(); --i >= 0;) {
      ContextToken ct = (ContextToken) vPush.get(i);
      if (ct.contextVariables != null && ct.contextVariables.containsKey(ident))
        return true;
    }
    return (thisFunction != null ? thisFunction.isVariable(ident)
        : contextVariables != null && contextVariables.containsKey(ident));
  }
 
  /**
   * allows for three kinds of comments.
   * NOTE: closing involves asterisks and slash together, but that can't be shown here.
   *
   * 1) /** .... ** /  super-comment
   * 2) /* ..... * /   may be INSIDE /**....** /).
   * 3)  \n//.....\n   single-line comments -- like #, but removed entirely
   * The reason is that /* ... * / will appear as standard in MOVETO command
   * but we still might want to escape it, so around that you can have /** .... ** /
   *
   * The terminator is not necessary -- so you can quickly escape anything in a file
   * after /** or /*
   *
   * In addition, we can have [/*|/**] .... **** Jmol Embedded Script ****  [script commands] [** /|* /]
   * Then ONLY that script is taken. This is a powerful and simple way then to include Jmol scripting
   * in any file -- including, for example, HTML as an HTML comment. Just send the whole file to
   * Jmol, and it will find its script!
   *
   * @param script
   * @return cleaned script
   */
  private String cleanScriptComments(String script) {
    int pt = (script.indexOf("\1##"));
    if (pt >= 0) {
      // these are for jmolConsole and scriptEditor
      scriptExtensions = script.substring(pt + 1);
      script = script.substring(0, pt);
    }
    haveComments = (script.indexOf("#") >= 0); // speeds processing
    return getEmbeddedScript(script);
  }
 
  public static String getEmbeddedScript(String script) {
    if (script == null)
      return script;
    int pt = script.indexOf(JmolConstants.EMBEDDED_SCRIPT_TAG);
    if (pt < 0)
      return script;
    int pt1 = script.lastIndexOf("/*", pt);
    int pt2 = script.indexOf((script.charAt(pt1 + 2) == '*' ? "*" : "") + "*/",
        pt);
    if (pt1 >= 0 && pt2 >= pt)
      script = script.substring(
          pt + JmolConstants.EMBEDDED_SCRIPT_TAG.length(), pt2)
          + "\n";
    while ((pt1 = script.indexOf(JpegEncoder.CONTINUE_STRING)) >= 0)
      script = script.substring(0, pt1)
          + script.substring(pt1 + JpegEncoder.CONTINUE_STRING.length() + 4);
    if (Logger.debugging)
      Logger.info(script);
    return script;
  }

  private ScriptFlowContext flowContext;
  private List ltoken;
  private List lltoken;
  private List vBraces;


  private int ichBrace;
  private int cchToken;
  private int cchScript;

  private int nSemiSkip;
  private int parenCount;
  private int braceCount;
  private int setBraceCount;
  private int bracketCount;
  private int ptSemi;
  private int forPoint3;
  private int setEqualPt;
  private int iBrace;

  private boolean iHaveQuotedString;
  private boolean isEndOfCommand;
  private boolean needRightParen;
  private boolean endOfLine;

  private String comment;

  private void addTokenToPrefix(Token token) {
    if (logMessages)
      Logger.info("addTokenToPrefix" + token);
    ltoken.add(token);
    lastToken = token;
  }

  private final static int OK = 0;
  private final static int OK2 = 1;
  private final static int CONTINUE = 2;
  private final static int EOL = 3;
  private final static int ERROR = 4;

  private int tokLastMath;
 
  private List vFunctionStack;
 
  private boolean compile0(boolean isFull) {
    vFunctionStack = new ArrayList();
    htUserFunctions = new Hashtable();
    script = script.replace('\u201C', '"').replace('\u201D', '"');
    script = cleanScriptComments(script);
    ichToken = script.indexOf(Viewer.STATE_VERSION_STAMP);
    isStateScript = (ichToken >= 0);
    if (isStateScript) {
      ptSemi = script.indexOf(";", ichToken);
      if (ptSemi >= ichToken)
        viewer.setStateScriptVersion(script.substring(
            ichToken + Viewer.STATE_VERSION_STAMP.length(), ptSemi).trim());
    }
    cchScript = this.script.length();

    // these four will be returned:
    contextVariables = null;
    lineNumbers = null;
    lineIndices = null;
    aatokenCompiled = null;
    thisFunction = null;
    flowContext = null;
    errorType = null;
    errorMessage = null;
    errorMessageUntranslated = null;
    errorLine = null;

    nSemiSkip = 0;
    ichToken = 0;
    ichCurrentCommand = 0;
    ichComment = 0;
    ichBrace = 0;
    lineCurrent = 1;
    iCommand = 0;
    tokLastMath = 0;
    lastToken = Token.tokenOff;
    vBraces = new ArrayList();
    vPush = new ArrayList();
    pushCount = 0;
    iBrace = 0;
    braceCount = 0;
    parenCount = 0;
    ptSemi = -10;
    cchToken = 0;
    lnLength = 8;
    lineNumbers = new short[lnLength];
    lineIndices = new int[lnLength][2];
    isNewSet = isSetBrace = false;
    ptNewSetModifier = 1;
    isShowScriptOutput = false;
    iHaveQuotedString = false;
    lltoken = new ArrayList();
    ltoken = new ArrayList();
    tokCommand = Token.nada;
    lastFlowCommand = null;
    tokenAndEquals = null;
    setBraceCount = 0;
    bracketCount = 0;
    forPoint3 = -1;
    setEqualPt = Integer.MAX_VALUE;
    endOfLine = false;
    comment = null;
    isEndOfCommand = false;
    needRightParen = false;
    theTok = Token.nada;
    short iLine = 1;

    for (; true; ichToken += cchToken) {
      if ((nTokens = ltoken.size()) == 0) {
        if (thisFunction != null && thisFunction.chpt0 == 0)
          thisFunction.chpt0 = ichToken;
        ichCurrentCommand = ichToken;
        iLine = lineCurrent;
      }
      if (lookingAtLeadingWhitespace())
        continue;
      endOfLine = false;
      if (!isEndOfCommand) {
        endOfLine = lookingAtEndOfLine();
        switch (endOfLine ? OK : lookingAtComment()) {
        case CONTINUE: //  short /*...*/ or comment to completely ignore
          continue;
        case EOL: // /* .... \n ... */ -- flag as end of line but ignore
          isEndOfCommand = true;
          continue;
        case OK2: // really just line-ending comment -- mark it for later inclusion
          isEndOfCommand = true;
          // start-of line comment -- include as Token.nada
          comment = script.substring(ichToken, ichToken + cchToken).trim();
          break;
        }
        isEndOfCommand = isEndOfCommand || endOfLine
            || lookingAtEndOfStatement();
      }

      if (isEndOfCommand) {
        isEndOfCommand = false;
        switch (processTokenList(iLine, isFull)) {
        case CONTINUE:
          continue;
        case ERROR:
          return false;
        }
        if (ichToken < cchScript)
          continue;
        setAaTokenCompiled();
        return (flowContext == null || error(ERROR_missingEnd, Token
            .nameOf(flowContext.token.tok)));
      }

      if (nTokens > 0) {
        switch (checkSpecialParameterSyntax()) {
        case CONTINUE:
          continue;
        case ERROR:
          return false;
        }
      }
      if (lookingAtLookupToken(ichToken)) {
        String ident = getPrefixToken();
        //System.out.println(ident);
        switch (parseKnownToken(ident)) {
        case CONTINUE:
          continue;
        case ERROR:
          return false;
        }
        switch (parseCommandParameter(ident)) {
        case CONTINUE:
          continue;
        case ERROR:
          return false;
        }
        addTokenToPrefix(theToken);
        continue;
      }
      if (nTokens == 0 || (isNewSet || isSetBrace)
          && nTokens == ptNewSetModifier) {
        if (nTokens == 0) {
          if (lookingAtString(true)) {
            addTokenToPrefix(setCommand(Token.tokenScript));
            cchToken = 0;
            continue;
          }
          if (lookingAtImpliedString(true))
            ichEnd = ichToken + cchToken;
        }
        return commandExpected();
      }
      return error(ERROR_unrecognizedToken, script.substring(ichToken,
          ichToken + 1));
    }
  }
 
  private void setAaTokenCompiled() {
    aatokenCompiled = new Token[lltoken.size()][];
    for (int i = lltoken.size(); --i >= 0;)
      aatokenCompiled[i] = (Token[]) lltoken.get(i);
  }

  private boolean lookingAtLeadingWhitespace() {
    int ichT = ichToken;
    while (ichT < cchScript && isSpaceOrTab(script.charAt(ichT)))
      ++ichT;
    if (isLineContinuation(ichT, true))
      ichT += 1 + nCharNewLine(ichT + 1);
    cchToken = ichT - ichToken;
    return cchToken > 0;
  }

  private boolean isLineContinuation(int ichT, boolean checkMathop) {
    boolean isEscaped = (ichT + 2 < cchScript && script.charAt(ichT) == '\\' && nCharNewLine(ichT + 1) > 0
        || checkMathop && lookingAtMathContinuation(ichT));  
    if (isEscaped)
      lineCurrent++;
    return isEscaped;
  }

  private boolean lookingAtMathContinuation(int ichT) {
    int n;
    if (ichT >= cchScript || (n = nCharNewLine(ichT)) == 0 || lastToken.tok == Token.leftbrace)
      return false;
    if (parenCount > 0 || bracketCount > 0)
      return true;
    if ((tokCommand != Token.set || !isNewSet) && tokCommand != Token.print && tokCommand != Token.log)
        return false;
    if (lastToken.tok == tokLastMath)
      return true;
    ichT += n;
    while (ichT < cchScript && isSpaceOrTab(script.charAt(ichT)))
      ++ichT;
    return (lookingAtLookupToken(ichT)
        && tokLastMath == 1);
  }

  private boolean lookingAtEndOfLine() {
    int ichT = ichEnd = ichToken;
    if (ichToken >= cchScript) {
      ichEnd = cchScript;
      return true;
    }
    int n = nCharNewLine(ichT);
    if (n == 0)
      return false;
    ichEnd = ichToken;
    cchToken = n;
    return true;   
  }
 
  private int nCharNewLine(int ichT) {
    char ch;
    return (ichT >= cchScript ? 0
        : (ch = script.charAt(ichT)) != '\r' ? (ch == '\n' ? 1 : 0)
        : ++ichT < cchScript && script.charAt(ichT) == '\n' ? 2 : 1);
  }

  private boolean lookingAtEndOfStatement() {
    boolean isSemi = (script.charAt(ichToken) == ';');
    if (isSemi && nTokens > 0)
      ptSemi = nTokens;
    if (!isSemi || nSemiSkip-- > 0)
      return false;
    cchToken = 1;
    return true;
  }

  private boolean isShowCommand;
 
  private int lookingAtComment() {
    char ch = script.charAt(ichToken);
    int ichT = ichToken;
    int ichFirstSharp = -1;

    // return CONTINUE: totally ignore
    // return EOL: treat as line end, even though it isn't
    // return OK: no comment here

    /*
     * New in Jmol 11.1.9: we allow for output from the set showScript command
     * to be used as input. These lines start with $ and have a [...] phrase
     * after them. Their presence switches us to this new mode where we use
     * those statements as our commands and any line WITHOUT those as comments.
     */
    if (ichToken == ichCurrentCommand && ch == '$') {
      isShowScriptOutput = true;
      isShowCommand = true;
      while (ch != ']' && ichT < cchScript && !eol(ch = script.charAt(ichT)))
        ++ichT;
      cchToken = ichT - ichToken;
      return CONTINUE;
    } else if (isShowScriptOutput && !isShowCommand) {
      ichFirstSharp = ichT;
    }
    if (ch == '/' && ichT + 1 < cchScript)
      switch (script.charAt(++ichT)) {
      case '/':
        ichFirstSharp = ichToken;
        ichEnd = ichT - 1;
        break;
      case '*':
        ichEnd = ichT - 1;
        String terminator = (++ichT < cchScript && (ch = script.charAt(ichT)) == '*'
            ? "**/" : "*/");
        ichT = script.indexOf(terminator, ichToken + 2);
        if (ichT < 0) {
          ichToken = cchScript;
          return EOL;
        }
        // ichT points at char after /*, whatever that is. So even /***/ will be caught
        incrementLineCount(script.substring(ichToken, ichT));
        cchToken = ichT + (ch == '*' ? 3 : 2) - ichToken;
        return CONTINUE;
      default:
        return OK;
      }

    boolean isSharp = (ichFirstSharp < 0);
    if (isSharp && !haveComments)
      return OK;

    // old way:
    // first, find the end of the statement and scan for # (sharp) signs

    if (ichComment > ichT)
      ichT = ichComment;
    for (; ichT < cchScript; ichT++) {
      if (eol(ch = script.charAt(ichT))) {
        ichEnd = ichT;
        if (isLineContinuation(ichT - 1, false)) {
          ichT += nCharNewLine(ichT);
          continue;
        }
        if (!isSharp && ch == ';')
          continue;
        break;
      }
      if (ichFirstSharp >= 0)
        continue;
      if (ch == '#')
        ichFirstSharp = ichT;
    }
    if (ichFirstSharp < 0) // there were no sharps found
      return OK;
    ichComment = ichFirstSharp;
    /****************************************************************
     * check for #jc comment if it occurs anywhere in the statement, then the
     * statement is not executed. This allows statements which are executed in
     * RasMol but are comments in Jmol
     ****************************************************************/

    if (isSharp && nTokens == 0 && cchScript - ichFirstSharp >= 3
        && script.charAt(ichFirstSharp + 1) == 'j'
        && script.charAt(ichFirstSharp + 2) == 'c') {
      // statement contains a #jc before then end ... strip it all
      cchToken = ichT - ichToken;
      return CONTINUE;
    }

    // if the sharp was not the first character then it isn't a comment
    if (ichFirstSharp != ichToken)
      return OK;

    /****************************************************************
     * check for leading #jx <space> or <tab> if you see it, then only strip
     * those 4 characters. if they put in #jx <newline> then they are not going
     * to execute anything, and the regular code will take care of it
     ****************************************************************/
    if (isSharp && cchScript > ichToken + 3 && script.charAt(ichToken + 1) == 'j'
        && script.charAt(ichToken + 2) == 'x'
        && isSpaceOrTab(script.charAt(ichToken + 3))) {
      cchToken = 4; // #jx[\s\t]
      return CONTINUE;
    }
   
    if (ichT == ichToken)
      return OK;

    // first character was a sharp, but was not #jx ... strip it all
    cchToken = ichT - ichToken;
    return (nTokens == 0 ? OK2 : CONTINUE);
  }

  private int processTokenList(short iLine, boolean doCompile) {
    if (nTokens > 0 || comment != null) {
      if (nTokens == 0) {
        // just a comment
        ichCurrentCommand = ichToken;
        if (comment != null)
          addTokenToPrefix(new Token(Token.nada, comment));
      } else if (setBraceCount > 0 && endOfLine && ichToken < cchScript) {
        return CONTINUE;
      }
      // end of command or comment
      iCommand = lltoken.size();
      if (thisFunction != null && thisFunction.cmdpt0 < 0) {
        thisFunction.cmdpt0 = iCommand;
      }
      if (nTokens == 1 && braceCount == 1) {
        // ...{...
        if (lastFlowCommand == null) {
          parenCount = setBraceCount = braceCount = 0;
          ltoken.remove(0);
          iBrace++;
          Token t = new ContextToken(Token.push, 0, "{");
          addTokenToPrefix(setCommand(t));
          pushCount++;
          vPush.add(t);
          vBraces.add(tokenCommand);
        } else {
          parenCount = setBraceCount = 0;
          setCommand(lastFlowCommand);
          if (lastFlowCommand.tok != Token.process
              && (tokAt(0) == Token.leftbrace))
            ltoken.remove(0);
          lastFlowCommand = null;
        }
      }
      if (bracketCount > 0 || setBraceCount > 0 || parenCount > 0
          || braceCount == 1 && !checkFlowStartBrace(true)) {
        error(nTokens == 1 ? ERROR_commandExpected
            : ERROR_endOfCommandUnexpected);
        return ERROR;
      }
      if (needRightParen) {
        addTokenToPrefix(Token.tokenRightParen);
        needRightParen = false;
      }

      if (ltoken.size() > 0) {
        if (doCompile && !compileCommand())
          return ERROR;
        if (logMessages) {
          Logger.debug("-------------------------------------");
        }
        boolean doEval = true;
        switch (tokCommand) {
        case Token.trycmd:
        case Token.parallel:
        case Token.function: // formerly "noeval"
        case Token.end:         
          // end switch may have - or + intValue, depending upon default or not
          // end function and the function call itself has intValue 0,
          // but the FUNCTION declaration itself will have MAX_VALUE intValue
          doEval = (atokenInfix.length > 0 && atokenInfix[0].intValue != Integer.MAX_VALUE);
          break;
        }
        if (doEval) {
          if (iCommand == lnLength) {
            short[] lnT = new short[lnLength * 2];
            System.arraycopy(lineNumbers, 0, lnT, 0, lnLength);
            lineNumbers = lnT;
            int[][] lnI = new int[lnLength * 2][2];
            System.arraycopy(lineIndices, 0, lnI, 0, lnLength);
            lineIndices = lnI;
            lnLength *= 2;
          }
          lineNumbers[iCommand] = iLine;
          lineIndices[iCommand][0] = ichCurrentCommand;
          lineIndices[iCommand][1] = Math.max(ichCurrentCommand, Math.min(
              cchScript, ichEnd == ichCurrentCommand ? ichToken : ichEnd));
          lltoken.add(atokenInfix);
          iCommand = lltoken.size();
        }
        if (tokCommand == Token.set)
          lastFlowCommand = null;
      }
      setCommand(null);
      comment = null;
      iHaveQuotedString = isNewSet = isSetBrace = needRightParen = false;
      ptNewSetModifier = 1;
      ltoken.clear();
      nTokens = nSemiSkip = 0;
      tokenAndEquals = null;
      ptSemi = -10;
      forPoint3 = -1;
      setEqualPt = Integer.MAX_VALUE;

    }
    if (endOfLine) {
      if (flowContext != null && flowContext.checkForceEndIf(1)) {
        forceFlowEnd(flowContext.token);
        isEndOfCommand = true;
        cchToken = 0;
        ichCurrentCommand = ichToken;
        lineCurrent--;
        return CONTINUE;
      }
      isShowCommand = false;
      ++lineCurrent;
    }
    if (ichToken >= cchScript) {
      // check for end of all brace work
      setCommand(Token.tokenAll);
      theTok = Token.nada;
      switch (checkFlowEndBrace()) {
      case ERROR:
        return ERROR;
      case CONTINUE:
        isEndOfCommand = true;
        cchToken = 0;
        return CONTINUE;
      }
      ichToken = cchScript;
      return OK; // main loop exit
    }
    return OK;
  }

  private boolean compileCommand() {
    if (ltoken.size() == 0) {
      // comment
      atokenInfix = new Token[0];
      return true;
    }
    setCommand(tokenAt(0));
    int size = ltoken.size();
    if (size == 1 && Token.tokAttr(tokCommand, Token.defaultON))
      addTokenToPrefix(Token.tokenOn);
    if (tokenAndEquals != null) {
      int j;
      int i = 0;
      for (i = 1; i < size; i++) {
        if ((j = tokAt(i)) == Token.andequals)
          break;
      }
      size = i;
      i++;
      if (ltoken.size() < i) {
        Logger.error("COMPILER ERROR! - andEquals ");
      } else {
        for (j = 1; j < size; j++, i++)
          ltoken.add(i, tokenAt(j));
        ltoken.set(size, Token.tokenEquals);
        ltoken.add(i, tokenAndEquals);
        ltoken.add(++i, Token.tokenLeftParen);
        addTokenToPrefix(Token.tokenRightParen);
      }
    }

    atokenInfix = new Token[size = ltoken.size()];
    for (int i = ltoken.size(); --i >= 0;)
      atokenInfix[i] = (Token) ltoken.get(i);
   
    if (logMessages) {
      Logger.debug("token list:");
      for (int i = 0; i < atokenInfix.length; i++)
        Logger.debug(i + ": " + atokenInfix[i]);
      Logger.debug("vBraces list:");
      for (int i = 0; i < vBraces.size(); i++)
        Logger.debug(i + ": " + vBraces.get(i));
      Logger.debug("-------------------------------------");
    }
   
    // compile expressions  (ScriptCompilerTokenParser.java)
    return compileExpressions();
   
  }

  private Token tokenAt(int i) {
    return (Token) ltoken.get(i);
  }

  protected int tokAt(int i) {
    return (i < ltoken.size() ? tokenAt(i).tok : Token.nada);
  }
 
  private Token setCommand(Token token) {
    tokenCommand = token;
    if (token == null) {
      tokCommand = Token.nada;
    } else {
      tokCommand = tokenCommand.tok;
      isMathExpressionCommand = (tokCommand == Token.identifier || Token.tokAttr(tokCommand,
          Token.mathExpressionCommand));
      isSetOrDefine = (tokCommand == Token.set || tokCommand == Token.define);
      isCommaAsOrAllowed = Token.tokAttr(tokCommand,
          Token.atomExpressionCommand);
    }
    return token;
  }


  private void replaceCommand(Token token) {
    ltoken.remove(0);
    ltoken.add(0, setCommand(token));
  }

  private boolean isUserToken;
 
  private String getPrefixToken() {
    String ident = script.substring(ichToken, ichToken + cchToken);
    String identLC = ident.toLowerCase();
    boolean isUserVar = isContextVariable(identLC);
    if (nTokens == 0)
      isUserToken = isUserVar;
    if (nTokens == 1 && (tokCommand == Token.function  || tokCommand == Token.parallel || tokCommand == Token.var)
        || nTokens != 0 && isUserVar
        || isUserFunction(identLC&& (thisFunction == null || !thisFunction.name.equals(identLC))) {
      // we need to allow:
     
      // var color = "xxx"
      // color @color
      // print color
      // etc.
      // BUT we also should allow
      // color = ...
      // color += ...
      // color[ ...
     
      ident = identLC;
      theToken = null;
    } else if (ident.length() == 1) {
      // hack to support case sensitive alternate locations and chains
      // if an identifier is a single character long, then
      // allocate a new Token with the original character preserved
      if ((theToken = Token.getTokenFromName(ident)) == null
          && (theToken = Token.getTokenFromName(identLC)) != null)
        theToken = new Token(theToken.tok, theToken.intValue, ident);
    } else {
      ident = identLC;
      theToken = Token.getTokenFromName(ident);
    }
    if (theToken == null) {
      if (ident.indexOf("property_") == 0)
        theToken = new Token(Token.property, ident);
      else
        theToken = new Token(Token.identifier, ident);
    }   
    theTok = theToken.tok;
    return ident;
  }

  private int checkSpecialParameterSyntax() {
    char ch;
    if (nTokens == ptNewSetModifier) {
      ch = script.charAt(ichToken);
      boolean isAndEquals = ("+-\\*/&|=".indexOf(ch) >= 0);
      boolean isOperation = (isAndEquals || ch == '.' || ch == '[');
      char ch2 = (ichToken + 1 >= cchScript ? 0 : script.charAt(ichToken + 1));
      if (!isNewSet && isUserToken && isOperation && (ch == '=' || ch2 == ch || ch2 == '=')) {
        isNewSet = true;
        // Var data = ""
        // data = 5
        // data++
        // data += what
       
      }
      if (isNewSet || tokCommand == Token.set || Token.tokAttr(tokCommand, Token.setparam)) {
        if (ch == '=')
          setEqualPt = ichToken;

        // axes, background, define, display, echo, frank, hbond, history,
        // set, var
        // can all appear with or without "set" in front of them. These
        // are then
        // both commands and parameters for the SET command, but only if
        // they are
        // the FIRST parameter of the set command.
        if (Token.tokAttr(tokCommand, Token.setparam) && ch == '='
            || (isNewSet || isSetBrace) && isOperation) {
          setCommand(isAndEquals ? Token.tokenSet
              : ch == '[' && !isSetBrace ? Token.tokenSetArray
                  : Token.tokenSetProperty);
          ltoken.add(0, tokenCommand);
          cchToken = 1;
          switch (ch) {
          case '[':
            addTokenToPrefix(new Token(Token.leftsquare, "["));
            bracketCount++;
            return CONTINUE;
          case '.':
            addTokenToPrefix(new Token(Token.per, "."));
            return CONTINUE;
          case '-':
          case '+':
          case '*':
          case '/':
          case '\\':
          case '&':
          case '|':
            if (ch2 == 0)
              return ERROR(ERROR_endOfCommandUnexpected);
            if (ch2 != ch && ch2 != '=')
              return ERROR(ERROR_badContext, "\"" + ch + "\"");
            break;
          default:
            lastToken = Token.tokenMinus; // just to allow for {(....)}
            return CONTINUE;
          }
        }
      }
    }
   
    // cd, echo, gotocmd, help, hover, javascript, label, message, and pause
    // all are implicitly strings. You CAN use "..." but you don't have to,
    // and you cannot use '...'. This way the introduction of single quotes
    // as an equivalent of double quotes cannot break existing scripts. -- BH 06/2009
   
    if (lookingAtString(!Token.tokAttr(tokCommand, Token.implicitStringCommand))) {
      if (cchToken < 0)
        return ERROR(ERROR_endOfCommandUnexpected);
      String str;
      if ((tokCommand == Token.load || tokCommand == Token.background || tokCommand == Token.script)
          && !iHaveQuotedString) {
        str = script.substring(ichToken + 1, ichToken + cchToken - 1);
        if (str.indexOf("\\u") >= 0)
          str = Escape.unescapeUnicode(str);
      } else {
        str = getUnescapedStringLiteral();
      }
      iHaveQuotedString = true;
      if (tokCommand == Token.load && lastToken.tok == Token.data
          || tokCommand == Token.data && str.indexOf("@") < 0) {
        if (!getData(str))
          return ERROR(ERROR_missingEnd, "data");
      } else {
        addTokenToPrefix(new Token(Token.string, str));
        if (Token.tokAttr(tokCommand, Token.implicitStringCommand))
          isEndOfCommand = true;
      }
      return CONTINUE;
    }
    if (tokCommand == Token.sync && nTokens == 1 && charToken()) {
      String ident = script.substring(ichToken, ichToken + cchToken);
      addTokenToPrefix(new Token(Token.identifier, ident));
      return CONTINUE;
    }
    switch(tokCommand) {
    case Token.load:
      if (script.charAt(ichToken) == '@') {
        iHaveQuotedString = true;
        return OK;
      }
      if (nTokens == 1 && lookingAtLoadFormat()) {
        String strFormat = script.substring(ichToken, ichToken + cchToken);
        Token token = Token.getTokenFromName(strFormat.toLowerCase());
        switch (token == null ? Token.nada : token.tok) {
        case Token.append:
        case Token.data:
        case Token.file:
        case Token.inline:
        case Token.menu:
        case Token.model:
        case Token.smiles:
        case Token.trajectory:
          addTokenToPrefix(token);
          break;
        default:
          // skip entirely if not recognized
          int tok = (strFormat.indexOf("=") == 0 || strFormat.indexOf("$") == 0 ? Token.string
              : Parser.isOneOf(strFormat = strFormat.toLowerCase(), JmolConstants.LOAD_ATOM_DATA_TYPES) ? Token.identifier
                  : 0);
          if (tok != 0) {
            addTokenToPrefix(new Token(tok, strFormat));
            iHaveQuotedString = (tok == Token.string);
          }
        }
        return CONTINUE;
      }
      BitSet bs;
      if (script.charAt(ichToken) == '{' || parenCount > 0) {
      } else if ((bs = lookingAtBitset()) != null) {
        addTokenToPrefix(new Token(Token.bitset, bs));
        return CONTINUE;
      } else if (!iHaveQuotedString && lookingAtImpliedString(false)) {
        String str = script.substring(ichToken, ichToken + cchToken);
        addTokenToPrefix(new Token(Token.string, str));
        iHaveQuotedString = true;
        return CONTINUE;
      }
      break;
    case Token.script:
    case Token.getproperty:
      if (!iHaveQuotedString && lookingAtImpliedString(false)) {
        String str = script.substring(ichToken, ichToken + cchToken);
        addTokenToPrefix(new Token(Token.string, str));
        iHaveQuotedString = true;
        return CONTINUE;
      }
      break;
    case Token.write:
      // write image 300 300 filename
      // write script filename
      // write spt filename
      // write jpg filename
      // write filename
      if (nTokens == 2 && lastToken.tok == Token.frame)
        iHaveQuotedString = true;
      if (!iHaveQuotedString) {
        if (script.charAt(ichToken) == '@') {
          iHaveQuotedString = true;
          return OK;
        }
        if (lookingAtImpliedString(true)) {
          int pt = cchToken;
          String str = script.substring(ichToken, ichToken + cchToken);
          if (str.indexOf(" ") < 0) {
            addTokenToPrefix(new Token(Token.string, str));
            iHaveQuotedString = true;
            return CONTINUE;
          }
          cchToken = pt;
        }
      }
      break;
    }
    if (Token.tokAttr(tokCommand, Token.implicitStringCommand)
        && !(tokCommand == Token.script && iHaveQuotedString)
        && lookingAtImpliedString(true)) {
      String str = script.substring(ichToken, ichToken + cchToken);
      if (tokCommand == Token.label
          && Parser.isOneOf(str.toLowerCase(), "on;off;hide;display"))
        addTokenToPrefix(Token.getTokenFromName(str.toLowerCase()));
      else
        addTokenToPrefix(new Token(Token.string, str));
      return CONTINUE;
    }
    float value;
    if (!Float.isNaN(value = lookingAtExponential())) {
      addTokenToPrefix(new Token(Token.decimal, new Float(value)));
      return CONTINUE;
    }
    if (lookingAtObjectID(nTokens == 1)) {
      addTokenToPrefix(Token.getTokenFromName("$"));
      addTokenToPrefix(new Token(Token.identifier, script.substring(ichToken,
          ichToken + cchToken)));
      return CONTINUE;
    }
    if (lookingAtDecimal()) {
      value =
      // can't use parseFloat with jvm 1.1
      // Float.parseFloat(script.substring(ichToken, ichToken +
      // cchToken));
      Float.valueOf(script.substring(ichToken, ichToken + cchToken))
          .floatValue();
      int intValue = (JmolConstants.modelValue(script.substring(ichToken, ichToken + cchToken)));
      addTokenToPrefix(new Token(Token.decimal, intValue, new Float(value)));
      return CONTINUE;
    }
    if (lookingAtSeqcode()) {
      ch = script.charAt(ichToken);
      try {
        int seqNum = (ch == '*' || ch == '^' ? Integer.MAX_VALUE : Integer
            .parseInt(script.substring(ichToken, ichToken + cchToken - 2)));
        char insertionCode = script.charAt(ichToken + cchToken - 1);
        if (insertionCode == '^')
          insertionCode = ' ';
        if (seqNum < 0) {
          seqNum = -seqNum;
          addTokenToPrefix(Token.tokenMinus);
        }
        int seqcode = Group.getSeqcode(seqNum, insertionCode);
        addTokenToPrefix(new Token(Token.seqcode, seqcode, "seqcode"));
        return CONTINUE;
      } catch (NumberFormatException nfe) {
        return ERROR(ERROR_invalidExpressionToken, "" + ch);
      }
    }
    int val = lookingAtInteger();
    if (val != Integer.MAX_VALUE) {
      String intString = script.substring(ichToken, ichToken + cchToken);
      if (tokCommand == Token.breakcmd || tokCommand == Token.continuecmd) {
        if (nTokens != 1)
          return ERROR(ERROR_badArgumentCount);
        ScriptFlowContext f = (flowContext == null ? null : flowContext
            .getBreakableContext(val = Math.abs(val)));
        if (f == null)
          return ERROR(ERROR_badContext, (String) tokenCommand.value);
        tokenAt(0).intValue = f.pt0; // copy
      }
      if (val == 0 && intString.equals("-0"))
        addTokenToPrefix(Token.tokenMinus);
      addTokenToPrefix(new Token(Token.integer, val, intString));
      return CONTINUE;
    }
    if (!isMathExpressionCommand && parenCount == 0
        || lastToken.tok != Token.identifier && !tokenAttr(lastToken, Token.mathfunc)) {
      // here if:
      //   structure helix ({...})
      //   frame align ({...})
      //   polyhedra BONDS ({...})
      //   isosurface select ({...})
      //   isosurface within({...})
      // NOT
      //   myfunc({...})
      //   mathFunc({...})
      // if you want to use a bitset there, you must use
      // bitsets properly: x.distance( ({1 2 3}) )
      boolean isBondOrMatrix = (script.charAt(ichToken) == '[');
      BitSet bs = lookingAtBitset();
      if (bs == null) {
        if (isBondOrMatrix) {
          Object m = lookingAtMatrix();
          if (m instanceof Matrix3f || m instanceof Matrix4f) {
            addTokenToPrefix(new Token((m instanceof Matrix3f ? Token.matrix3f : Token.matrix4f), m));           
            return CONTINUE;
          }
        }
      } else {
        if (isBondOrMatrix)
          addTokenToPrefix(new Token(Token.bitset, new BondSet(bs)));
        // occasionally BondSet appears unknown in Eclipse even though it
        // is defined
        // in Eval.java -- doesn't seem to matter.
        else
          addTokenToPrefix(new Token(Token.bitset, bs));
        return CONTINUE;
      }
    }
    return OK;
  }

  private Object lookingAtMatrix() {
    int ipt;
    Object m;
    if (ichToken + 4 >= cchScript
        || script.charAt(ichToken) != '[' || script.charAt(ichToken + 1) != '['
        || (ipt = script.indexOf("]]", ichToken)) < 0
        || (m = Escape.unescapeMatrix(script.substring(ichToken, ipt + 2))) == null)
      return null;
    cchToken = ipt + 2 - ichToken;
    return m;
  }

  private int parseKnownToken(String ident) {

    // specific token-based issues depend upon where we are in the command
   
    Token token;

    if (tokLastMath != 0)
      tokLastMath = theTok;
    if (flowContext != null && flowContext.token.tok == Token.switchcmd
        && flowContext.var != null
        && theTok != Token.casecmd && theTok != Token.defaultcmd
        && lastToken.tok != Token.switchcmd)
      return ERROR(ERROR_badContext, ident);
    switch (theTok) {
    case Token.identifier:
      if (nTokens == 0) {
        if (ident.charAt(0) == '\'') {
          addTokenToPrefix(setCommand(Token.tokenScript));
          cchToken = 0;
          return CONTINUE;
        }
        if (ichToken + cchToken < cchScript && script.charAt(ichToken + cchToken) == '.') {
          addTokenToPrefix(setCommand(Token.tokenScript));
          nTokens = 1;
          cchToken = 0;
          return CONTINUE;
        }
      }
      break;
    case Token.andequals:
      if (theTok == Token.andequals) {
        if (nSemiSkip == forPoint3 && nTokens == ptSemi + 2) {
          token = lastToken;
          addTokenToPrefix(Token.tokenEquals);
          addTokenToPrefix(token);
          token = Token.getTokenFromName(ident.substring(0, 1));
          addTokenToPrefix(token);
          addTokenToPrefix(Token.tokenLeftParen);
          needRightParen = true;
          return CONTINUE;
        }
        if (tokCommand == Token.set) {
          tokenAndEquals = Token.getTokenFromName(ident.substring(0, 1));
          setEqualPt = ichToken;
          return OK;
        }
        if (tokCommand == Token.slab || tokCommand == Token.depth) {
          addTokenToPrefix(tokenCommand);
          replaceCommand(Token.tokenSet);
          tokenAndEquals = Token.getTokenFromName(ident.substring(0, 1));
          setEqualPt = ichToken;
          return OK;
        }
        // otherwise ignore
        return CONTINUE;
      }
      break;
    case Token.end:
    case Token.endifcmd:
      if (flowContext != null)
       flowContext.forceEndIf = false;
       // fall through
    case Token.elsecmd:
      if (nTokens > 0) {
        isEndOfCommand = true;
        cchToken = 0;
        return CONTINUE;
      }
      break;
    case Token.forcmd:
      if (bracketCount > 0// ignore [FOR], as in 1C4D
        break;
      // fall through
    case Token.casecmd:
    case Token.defaultcmd:
    case Token.elseif:
    case Token.ifcmd:
    case Token.switchcmd:
    case Token.whilecmd:
    case Token.catchcmd:
      if (nTokens > 1 && tokCommand != Token.set) {
        isEndOfCommand = true;
        if (flowContext != null)
          flowContext.forceEndIf = true;
        cchToken = 0;
        return CONTINUE;           
      }
      break;
    case Token.minusMinus:
    case Token.plusPlus:
      if (!isNewSet && nTokens == 1)
        checkNewSetCommand();
      if (isNewSet && parenCount == 0 && bracketCount == 0 && ichToken <= setEqualPt) {
        nTokens = ltoken.size();
        addTokenToPrefix(Token.tokenEquals);
        setEqualPt = 0;
        for (int i = 1; i < nTokens; i++)
          addTokenToPrefix((Token)ltoken.get(i));
        addTokenToPrefix(theTok == Token.minusMinus ? Token.tokenMinus : Token.tokenPlus);
        addTokenToPrefix(Token.intToken(1));
        return CONTINUE; 
      } else if (nSemiSkip == forPoint3 && nTokens == ptSemi + 2) {           
        token = lastToken;
        addTokenToPrefix(Token.tokenEquals);
        addTokenToPrefix(token);
        addTokenToPrefix(theTok == Token.minusMinus ? Token.tokenMinus : Token.tokenPlus);
        addTokenToPrefix(Token.intToken(1));
        return CONTINUE;
      }
      break;
    case Token.opEQ:
      if (parenCount == 0 && bracketCount == 0)
        setEqualPt = ichToken;
      break;
    case Token.per:
      if (tokCommand == Token.set && parenCount == 0 && bracketCount == 0 && ichToken < setEqualPt) {
        ltoken.add(1, Token.tokenExpressionBegin);
        addTokenToPrefix(Token.tokenExpressionEnd);
        ltoken.set(0, Token.tokenSetProperty);
        setEqualPt = 0;
      }           
      break;
    case Token.leftbrace:
      braceCount++;
      if (braceCount == 1 && parenCount == 0 && checkFlowStartBrace(false)) {
        isEndOfCommand = true;
        if (flowContext != null)
          flowContext.forceEndIf = false;
        return CONTINUE;
      }
      // fall through
    case Token.leftparen:
      parenCount++;
      // the select() function uses dual semicolon notation
      // but we must differentiate from isosurface select(...) and set
      // picking select
      if (nTokens > 1
          && (lastToken.tok == Token.select
              || lastToken.tok == Token.forcmd || lastToken.tok == Token.ifcmd))
        nSemiSkip += 2;
      break;
    case Token.rightbrace:
      if (iBrace > 0 && parenCount == 0 && braceCount == 0) {
        ichBrace = ichToken;
        if (nTokens == 0) {
          braceCount = parenCount = 1;
        } else {
          braceCount = parenCount = nSemiSkip = 0;
          if (theToken.tok != Token.casecmd && theToken.tok != Token.defaultcmd)
            vBraces.add(theToken);
          iBrace++;
          isEndOfCommand = true;
          ichEnd = ichToken;
          return CONTINUE;
        }
      }
      braceCount--;
      // fall through
    case Token.rightparen:
      parenCount--;
      if (parenCount < 0)
        return ERROR(ERROR_tokenUnexpected, ident);
      // we need to remove the semiskip if parentheses or braces have been
      // closed. 11.5.46
      if (parenCount == 0)
        nSemiSkip = 0;
      if (needRightParen) {
        addTokenToPrefix(Token.tokenRightParen);
        needRightParen = false;
      }
      break;
    case Token.leftsquare:
      if (ichToken > 0 && Character.isWhitespace(script.charAt(ichToken - 1)))
        addTokenToPrefix(Token.tokenSpaceBeforeSquare);
      bracketCount++;
      break;
    case Token.rightsquare:
      bracketCount--;
      if (bracketCount < 0)
        return ERROR(ERROR_tokenUnexpected, "]");
    }
    return OK;
  }

  private boolean checkNewSetCommand() {
    String name = ((Token) ltoken.get(0)).value.toString();
    if (!isContextVariable(name.toLowerCase()))
      return false;
    Token t = setNewSetCommand(false, name);
    setCommand(Token.tokenSet);
    ltoken.add(0, tokenCommand);
    ltoken.set(1, t);
    return true;
  }

  private int parseCommandParameter(String ident) {
    // PART II:
    //
    // checking tokens based on the current command
    // all command starts are handled by case Token.nada

   
    nTokens = ltoken.size();
    switch (tokCommand) {
    case Token.nada:
      // first token in command
      lastToken = Token.tokenOff;
      ichCurrentCommand = ichEnd = ichToken;
      setCommand(theToken);
     
      if (Token.tokAttr(tokCommand, Token.flowCommand)) {
        lastFlowCommand = tokenCommand;
      }
      // before processing this command, check to see if we have completed
      // a right-brace.
      int ret = checkFlowEndBrace();
      if (ret == ERROR)
        return ERROR;
      else if (ret == CONTINUE) {
        // yes, so re-read this one
        isEndOfCommand = true;
        cchToken = 0;
        return CONTINUE;
      }

      if (Token.tokAttr(tokCommand, Token.flowCommand)) {
        if (!checkFlowCommand((String) tokenCommand.value))
          return ERROR;
        theToken = tokenCommand;
        if (theTok == Token.casecmd) {
          addTokenToPrefix(tokenCommand);
          theToken = Token.tokenLeftParen;
        }
        break;
      }
      if (theTok == Token.colon) {
        braceCount++;
        isEndOfCommand = true;
        break;
      }
      if (theTok == Token.rightbrace) {
        // if }, just push onto vBrace, but NOT onto ltoken
        vBraces.add(tokenCommand);
        iBrace++;
        tokCommand = Token.nada;
        return CONTINUE;
      }
      if (theTok != Token.leftbrace)
        lastFlowCommand = null;

      if (Token.tokAttr(tokCommand, Token.scriptCommand))
        break;

      // not the standard command
      // isSetBrace: {xxx}.yyy = or {xxx}[xx].
      // isNewSet: xxx =
      // but not xxx = where xxx is a known "set xxx" variant
      // such as "set hetero" or "set hydrogen" or "set solvent"
     
      isSetBrace = (theTok == Token.leftbrace);
      if (isSetBrace) {
        if (!lookingAtBraceSyntax()) {
          isEndOfCommand = true;
          if (flowContext != null)
            flowContext.forceEndIf = false;
        }
      } else {
        switch (theTok) {
        case Token.plusPlus:
        case Token.minusMinus:
        case Token.identifier:
        case Token.var:
        case Token.define:
          break;
        default:
          if (!Token.tokAttr(theTok, Token.misc)
              && !Token.tokAttr(theTok, Token.setparam)
              && !isContextVariable(ident)) {
            commandExpected();
            return ERROR;
          }
        }
      }
      theToken = setNewSetCommand(isSetBrace, ident);
      break;
    case Token.catchcmd:
      switch(nTokens) {
      case 1:
        if (theTok != Token.leftparen)
          return ERROR(ERROR_tokenExpected, "(");
        break;
      case 2:
        ((ContextToken)tokenCommand).name0 = ident;
        addContextVariable(ident);
        break;
      case 3:
        if (theTok != Token.rightparen)
          return ERROR(ERROR_tokenExpected, ")");
        isEndOfCommand = true;
        ichEnd = ichToken + 1;
        flowContext.setLine();
        break;
      default:
        return ERROR(ERROR_badArgumentCount);
      }
      break;
    case Token.parallel:
    case Token.function:
      if (tokenCommand.intValue == 0) {
        if (nTokens != 1)
          break; // anything after name is ok
        // user has given macro command
        tokenCommand.value = ident;
        return CONTINUE; // don't store name in stack
      }
      if (nTokens == 1) {
        if (thisFunction != null)
          vFunctionStack.add(0, thisFunction);
        thisFunction = (tokCommand == Token.parallel ? new ParallelProcessor(ident, tokCommand) : new ScriptFunction(ident, tokCommand));
        htUserFunctions.put(ident, Boolean.TRUE);
        flowContext.setFunction(thisFunction);
        break; // function f
      }
      if (nTokens == 2) {
        if (theTok != Token.leftparen)
          return ERROR(ERROR_tokenExpected, "(");
        break; // function f (
      }
      if (nTokens == 3 && theTok == Token.rightparen)
        break; // function f ( )
      if (nTokens % 2 == 0) {
        // function f ( x , y )
        if (theTok != Token.comma && theTok != Token.rightparen)
          return ERROR(ERROR_tokenExpected, ")");
        break;
      }
      thisFunction.addVariable(ident, true);
      break;
    case Token.casecmd:
      if (nTokens > 1 && parenCount == 0 && braceCount == 0 && theTok == Token.colon) {
        addTokenToPrefix(Token.tokenRightParen);
        braceCount = 1;
        isEndOfCommand = true;
        cchToken = 0;
        return CONTINUE;
      }
      break;
    case Token.defaultcmd:
      if (nTokens > 1) {
        braceCount = 1;
        isEndOfCommand = true;
        cchToken = 0;
        return CONTINUE;
      }
      break;
    case Token.elsecmd:
      if (nTokens == 1 && theTok != Token.ifcmd) {
        isEndOfCommand = true;
        cchToken = 0;
        return CONTINUE;
      }
      if (nTokens != 1 || theTok != Token.ifcmd && theTok != Token.leftbrace)
        return ERROR(ERROR_badArgumentCount);
      replaceCommand(flowContext.token = new ContextToken(Token.elseif, "elseif"));
      tokCommand = Token.elseif;
      return CONTINUE;
    case Token.var:
      if (nTokens != 1)
        break;
      addContextVariable(ident);
      replaceCommand(Token.tokenSetVar);
      tokCommand = Token.set;
      break;
    case Token.end:
      if (nTokens != 1)
        return ERROR(ERROR_badArgumentCount);
      if (!checkFlowEnd(theTok, ident, ichCurrentCommand))
        return ERROR;
      if (theTok == Token.function || theTok == Token.parallel) {
        return CONTINUE;
      }
      break;
    case Token.switchcmd:
    case Token.whilecmd:
      if (nTokens > 2 && braceCount == 0 && parenCount == 0) {
        isEndOfCommand = true;
        ichEnd = ichToken + 1;
        flowContext.setLine();
      }
      break;
    case Token.elseif:
    case Token.ifcmd:
      if (nTokens > 2 && braceCount == 0 && parenCount == 0) {
        // so { or : end up new commands
        isEndOfCommand = true;
        ichEnd = ichToken + 1;
        flowContext.setLine();
      }
      break;
    case Token.process:
      isEndOfCommand = true;
      ichEnd = ichToken + 1;
      flowContext.setLine();
      break;
    case Token.forcmd:
      if (nTokens == 1) {
        if (theTok != Token.leftparen)
          return ERROR(ERROR_unrecognizedToken, ident);
        forPoint3 = nSemiSkip = 0;
        nSemiSkip += 2;
      } else if (nTokens == 3 && tokAt(2) == Token.var) {
        addContextVariable(ident);
      } else if ((nTokens == 3 || nTokens == 4) && theTok == Token.in) {
        nSemiSkip -= 2;
        forPoint3 = 2;
        // for ( var x IN
        // for ( x IN
      } else if (braceCount == 0 && parenCount == 0) {
        isEndOfCommand = true;
        ichEnd = ichToken + 1;
        flowContext.setLine();
      }
      break;
    case Token.set:
      if (theTok == Token.leftbrace)
        setBraceCount++;
      else if (theTok == Token.rightbrace) {
        setBraceCount--;
        if (isSetBrace && setBraceCount == 0
            && ptNewSetModifier == Integer.MAX_VALUE)
          ptNewSetModifier = nTokens + 1;
      }
      if (nTokens == ptNewSetModifier) { // 1 when { is not present
        Token token = tokenAt(0);
        if (theTok == Token.leftparen || isUserFunction(token.value.toString())) {
          // mysub(xxx,xxx,xxx)
          ltoken.set(0, setCommand(new Token(Token.identifier, 0, token.value)));
          setBraceCount = 0;
          break;
        }
        if (theTok != Token.identifier && theTok != Token.andequals
            && theTok != Token.define
            && (!Token.tokAttr(theTok, Token.setparam))) {
          if (isNewSet)
            commandExpected();
          else
            error(ERROR_unrecognizedParameter, "SET", ": " + ident);
          return ERROR;
        }
        if (nTokens == 1
            && (lastToken.tok == Token.plusPlus || lastToken.tok == Token.minusMinus)) {
          replaceCommand(Token.tokenSet);
          addTokenToPrefix(lastToken);
          break;
        }
      }
      break;
    case Token.load:
      if (theTok == Token.define && (nTokens == 1 || lastToken.tok == Token.filter || lastToken.tok == Token.spacegroup)) {
        addTokenToPrefix(Token.tokenDefineString);
        return CONTINUE;         
      }
      if (theTok == Token.as)
        iHaveQuotedString = false;
      break;
    case Token.display:
    case Token.hide:
    case Token.restrict:
    case Token.select:
    case Token.delete:
    case Token.define:
      if (tokCommand == Token.define) {
        if (nTokens == 1) {
          // we are looking at the variable name
          if (theTok != Token.identifier) {
            if (preDefining) {
              if (!Token.tokAttr(theTok, Token.predefinedset)) {
                error(
                    "ERROR IN Token.java or JmolConstants.java -- the following term was used in JmolConstants.java but not listed as predefinedset in Token.java: "
                        + ident, null);
                return ERROR;
              }
            } else if (Token.tokAttr(theTok, Token.predefinedset)) {
              Logger
                  .warn("WARNING: predefined term '"
                      + ident
                      + "' has been redefined by the user until the next file load.");
            } else if (!isCheckOnly && ident.length() > 1) {
              Logger
                  .warn("WARNING: redefining "
                      + ident
                      + "; was "
                      + theToken
                      + "not all commands may continue to be functional for the life of the applet!");
              theTok = theToken.tok = Token.identifier;
              Token.addToken(ident, theToken);
            }
          }
          addTokenToPrefix(theToken);
          lastToken = Token.tokenComma;
          return CONTINUE;
        }
        if (nTokens == 2) {
          if (theTok == Token.opEQ) {
            // we are looking at @x =.... just insert a SET command
            // and ignore the =. It's the same as set @x ...
            ltoken.add(0, Token.tokenSet);
            return CONTINUE;
          }
        }
      }
      if (bracketCount == 0 && theTok != Token.identifier
          && !Token.tokAttr(theTok, Token.expression)
          && !Token.tokAttr(theTok, Token.misc)
          && (theTok & Token.minmaxmask) != theTok)
        return ERROR(ERROR_invalidExpressionToken, ident);
      break;
    case Token.center:
      if (theTok != Token.identifier && theTok != Token.dollarsign
          && !Token.tokAttr(theTok, Token.expression))
        return ERROR(ERROR_invalidExpressionToken, ident);
      break;
    case Token.plot3d:
    case Token.pmesh:
    case Token.isosurface:
      // isosurface ... name.xxx
      char ch = nextChar();
      if (parenCount == 0 && bracketCount == 0
          && ".:/\\+-!?".indexOf(ch) >= 0 && !(ch == '-' && ident.equals("=")))
        checkUnquotedFileName();
    }
    return OK;
  }

  private Token setNewSetCommand(boolean isSetBrace, String ident) {
    tokCommand = Token.set;
    isNewSet = (!isSetBrace && !isUserFunction(ident));
    setBraceCount = (isSetBrace ? 1 : 0);
    bracketCount = 0;
    setEqualPt = Integer.MAX_VALUE;
    ptNewSetModifier = (isNewSet ? 1 : Integer.MAX_VALUE);
    return (isSetBrace ? theToken : new Token(Token.identifier, ident));
  }

  private char nextChar() {
    int ich = ichToken + cchToken;
    return (ich >= cchScript ? ' ' : script.charAt(ich));
  }

  private void checkUnquotedFileName() {
    int ichT = ichToken;
    char ch;
    while (++ichT < cchScript
        && !Character.isWhitespace(ch = script.charAt(ichT))
        && ch != '#' && ch != ';' && ch != '}') {
    }
    String name = script.substring(ichToken, ichT).replace('\\','/');
    cchToken = ichT - ichToken;
    theToken = new Token(Token.string, name);  
  }

  private boolean checkFlowStartBrace(boolean atEnd) {
    if ((!Token.tokAttr(tokCommand, Token.flowCommand)
        || tokCommand == Token.breakcmd || tokCommand == Token.continuecmd))
      return false;
    if (atEnd) {
      if (tokenCommand.tok != Token.casecmd && tokenCommand.tok != Token.defaultcmd) {
        iBrace++;
        vBraces.add(tokenCommand);
        lastFlowCommand = null;
      }
      parenCount = braceCount = 0;
    }
    return true;
  }

  List vPush = new ArrayList();
  int pushCount;
 
  private int checkFlowEndBrace() {
   
    if (iBrace <= 0
        || ((Token) vBraces.get(iBrace - 1)).tok != Token.rightbrace)
      return OK;
    // time to execute end
    vBraces.remove(--iBrace);
    Token token = (Token) vBraces.remove(--iBrace);
    if (theTok == Token.leftbrace) {
      braceCount--;
      parenCount--;
    }
    if (token.tok == Token.push) {
      vPush.remove(--pushCount);
      addTokenToPrefix(setCommand(new ContextToken(Token.pop, 0, "}")));
      isEndOfCommand = true;
      return CONTINUE;
    }
    switch (flowContext == null ? 0 :
      flowContext.token.tok) {
    case Token.ifcmd:
    case Token.elseif:
    case Token.elsecmd:     
      if (tokCommand == Token.elsecmd || tokCommand == Token.elseif)
        return OK;
      break;
    case Token.switchcmd:
    case Token.casecmd:
    case Token.defaultcmd:
      if (tokCommand == Token.casecmd || tokCommand == Token.defaultcmd)
        return OK;
    }
    return forceFlowEnd(token);
  }

  private int forceFlowEnd(Token token) {   
    Token t0 = tokenCommand;   
    setCommand(new Token(Token.end, "end"));
    if (!checkFlowCommand("end"))
      return Token.nada;
    addTokenToPrefix(tokenCommand);
    switch (token.tok) {
    case Token.ifcmd:
    case Token.elsecmd:
    case Token.elseif:
      token = Token.tokenIf;
      break;
    case Token.defaultcmd:
    case Token.casecmd:
      token = Token.tokenSwitch;
      break;
    default:
      token = Token.getTokenFromName((String)token.value);
      break;
    }
    if (!checkFlowEnd(token.tok, (String)token.value, ichBrace))
      return ERROR;
    if (token.tok != Token.function && token.tok != Token.parallel
        && token.tok != Token.trycmd)
      addTokenToPrefix(token);
    setCommand(t0);
    return CONTINUE;
  }

  static boolean isBreakableContext(int tok) {
    return tok == Token.forcmd
      || tok == Token.process
      || tok == Token.whilecmd
      || tok == Token.casecmd
      || tok == Token.defaultcmd;
  }

  private boolean checkFlowCommand(String ident) {
    int pt = lltoken.size();
    boolean isEnd = false;
    boolean isNew = true;
    switch (tokCommand) {
    case Token.function:
    case Token.parallel:
      if (flowContext != null)
        return error(ERROR_badContext, Token.nameOf(tokCommand));
      break;
    case Token.end:
      if (flowContext == null)
        return error(ERROR_badContext, ident);
      isEnd = true;
      if (flowContext.token.tok != Token.function && flowContext.token.tok != Token.parallel
          && flowContext.token.tok != Token.trycmd)
        setCommand(new Token(tokCommand, (flowContext.ptDefault > 0 ? flowContext.ptDefault : -flowContext.pt0), ident)); //copy
      break;
    case Token.trycmd:
    case Token.catchcmd:
      break;
    case Token.forcmd:
    case Token.ifcmd:
    case Token.process:
    case Token.switchcmd:
    case Token.whilecmd:
      break;
    case Token.endifcmd:
      isEnd = true;
      if (flowContext == null
          || flowContext.token.tok != Token.ifcmd
          && flowContext.token.tok != Token.process
          && flowContext.token.tok != Token.elsecmd
          && flowContext.token.tok != Token.elseif)
        return error(ERROR_badContext, ident);
      break;
    case Token.elsecmd:
      if (flowContext == null || flowContext.token.tok != Token.ifcmd
          && flowContext.token.tok != Token.elseif)
        return error(ERROR_badContext, ident);
      flowContext.token.intValue = flowContext.setPt0(pt, false);
      break;
    case Token.breakcmd:
    case Token.continuecmd:
      isNew = false;
      ScriptFlowContext f = (flowContext == null ? null : flowContext.getBreakableContext(0));
      if (f == null)
        return error(ERROR_badContext, ident);
      setCommand( new Token(tokCommand, f.pt0, ident)); //copy
      break;
    case Token.defaultcmd:
      if (flowContext == null
          || flowContext.token.tok != Token.switchcmd
          && flowContext.token.tok != Token.casecmd
          && flowContext.ptDefault > 0)
        return error(ERROR_badContext, ident);
      flowContext.token.intValue = flowContext.setPt0(pt, true);
      break;
    case Token.casecmd:
      if (flowContext == null
          || flowContext.token.tok != Token.switchcmd
          && flowContext.token.tok != Token.casecmd
          && flowContext.token.tok != Token.defaultcmd)
        return error(ERROR_badContext, ident);
      flowContext.token.intValue = flowContext.setPt0(pt, false);
      break;
    case Token.elseif:
      if (flowContext == null || flowContext.token.tok != Token.ifcmd
          && flowContext.token.tok != Token.elseif
          && flowContext.token.tok != Token.elsecmd)
        return error(ERROR_badContext, "elseif");
      flowContext.token.intValue = flowContext.setPt0(pt, false);
      break;
    }
    if (isEnd) {
      flowContext.token.intValue = (tokCommand == Token.catchcmd ? -pt : pt);
      if (tokCommand == Token.endifcmd)
        flowContext = flowContext.getParent();
      if (tokCommand == Token.trycmd) {
       
      }
    } else if (isNew) {
      ContextToken ct = new ContextToken(tokCommand, tokenCommand.value);
      setCommand(ct); //copy
      switch (tokCommand) {
      case Token.trycmd:
        flowContext = new ScriptFlowContext(this, ct, pt, flowContext);
        if (thisFunction != null)
          vFunctionStack.add(0, thisFunction);
        thisFunction = new ParallelProcessor("", tokCommand);
        flowContext.setFunction(thisFunction);
        pushCount++;
        vPush.add(ct);
        break;
      case Token.elsecmd:
      case Token.elseif:
        flowContext.token = ct;
        break;
      case Token.casecmd:
      case Token.defaultcmd:
        ct.contextVariables = flowContext.token.contextVariables;
        flowContext.token = ct;
        break;
      case Token.process:
      case Token.forcmd:
      case Token.whilecmd:
      case Token.catchcmd:
        pushCount++;
        vPush.add(ct);
        // fall through
      case Token.ifcmd:
      case Token.switchcmd:
      default:
        flowContext = new ScriptFlowContext(this, ct, pt, flowContext);
        break;
      }
    }
    return true;
  }

  private boolean checkFlowEnd(int tok, String ident, int pt1) {
    if (flowContext == null || flowContext.token.tok != tok) {
      boolean isOK = true;
      switch(tok) {
      case Token.ifcmd:
        isOK = (flowContext.token.tok == Token.elsecmd
            || flowContext.token.tok == Token.elseif);
        break;
      case Token.switchcmd:
        isOK = (flowContext.token.tok == Token.casecmd
            || flowContext.token.tok == Token.defaultcmd);
        break;
      default:
        isOK = false;
      }
      if (!isOK)
        return error(ERROR_badContext, "end " + ident);
    }
    switch (tok) {
    case Token.ifcmd:
    case Token.switchcmd:
      break;
    case Token.catchcmd:
    case Token.forcmd:
    case Token.process:
    case Token.whilecmd:
      vPush.remove(--pushCount);
      break;
    case Token.parallel:
    case Token.function:
    case Token.trycmd:
      if (!isCheckOnly) {
        addTokenToPrefix(new Token(tok, thisFunction));
        ScriptFunction.setFunction(thisFunction, script, pt1, lltoken.size(),
            lineNumbers, lineIndices, lltoken);
      }
      thisFunction = (vFunctionStack.size() == 0 ? null : (ScriptFunction) vFunctionStack.remove(0));
      tokenCommand.intValue = 0;
      if (tok == Token.trycmd)
        vPush.remove(--pushCount);
      break;
    default:
      return error(ERROR_unrecognizedToken, "end " + ident);
    }
    flowContext = flowContext.getParent();
    return true;
  }

  private boolean getData(String key) {
    addTokenToPrefix(new Token(Token.string, key));
    ichToken += key.length() + 2;
    if (script.length() > ichToken && script.charAt(ichToken) == '\r') {
      lineCurrent++;ichToken++;
    }
    if (script.length() > ichToken && script.charAt(ichToken) == '\n') {
      lineCurrent++;ichToken++;
    }
    int i = script.indexOf(chFirst + key + chFirst, ichToken) - 4;
    if (i < 0 || !script.substring(i, i + 4).equalsIgnoreCase("END "))
      return false;
    String str = script.substring(ichToken, i);
    incrementLineCount(str);
    addTokenToPrefix(new Token(Token.data, str));
    addTokenToPrefix(new Token(Token.identifier, "end"));
    addTokenToPrefix(new Token(Token.string, key));
    cchToken = i - ichToken + key.length() + 6;
    return true;
  }

  private int incrementLineCount(String str) {
    char ch;
    int pt = str.indexOf('\r');
    int pt2 = str.indexOf('\n');
    if (pt < 0 && pt2 < 0)
      return 0;
    int n = lineCurrent;
    if (pt < 0 || pt2 < pt)
      pt = pt2;
    for (int i = str.length(); --i >= pt;) {
      if ((ch = str.charAt(i)) == '\n' || ch == '\r')
        lineCurrent++;
    }
    return lineCurrent - n;
  }
 
  private static boolean isSpaceOrTab(char ch) {
    return ch == ' ' || ch == '\t';
  }

  private boolean eol(char ch) {
    return eol(ch, nSemiSkip)
  }
 
  static boolean eol(char ch, int nSkip) {
    return (ch == '\r' || ch == '\n' || ch == ';' && nSkip <= 0)
  }
 
  private boolean lookingAtBraceSyntax() {
    // isSetBrace: {xxx}.yyy =  or {xxx}[xx].
    int ichT = ichToken;
    int nParen = 1;
    while (++ichT < cchScript && nParen > 0) {
      switch (script.charAt(ichT)) {
      case '{':
        nParen++;
        break;
      case '}':
        nParen--;
      break;
      }
    }
    if (ichT < cchScript && script.charAt(ichT) == '[' && ++nParen == 1)
      while (++ichT < cchScript && nParen > 0) {
        switch (script.charAt(ichT)) {
        case '[':
          nParen++;
          break;
        case ']':
          nParen--;
        break;
        }
      }
    if (ichT < cchScript && script.charAt(ichT) == '.' && nParen == 0) {
      return true;
    }
   
    return false;
  }

  char chFirst;
  private boolean lookingAtString(boolean allowPrime) {
    if (ichToken == cchScript)
      return false;
    chFirst = script.charAt(ichToken);
    if (chFirst != '"' && (!allowPrime || chFirst != '\''))
      return false;
    int ichT = ichToken;
    char ch;
    boolean previousCharBackslash = false;
    while (++ichT < cchScript) {
      ch = script.charAt(ichT);
      if (ch == chFirst && !previousCharBackslash)
        break;
      previousCharBackslash = (ch == '\\' ? !previousCharBackslash : false);
    }
    if (ichT == cchScript)
      cchToken = -1;
    else
      cchToken = ++ichT - ichToken;
    return true;
  }

  String getUnescapedStringLiteral() {
    if (cchToken < 2)
      return "";
    StringBuffer sb = new StringBuffer(cchToken - 2);
    int ichMax = ichToken + cchToken - 1;
    int ich = ichToken + 1;
    while (ich < ichMax) {
      char ch = script.charAt(ich++);
      if (ch == '\\' && ich < ichMax) {
        ch = script.charAt(ich++);
        switch (ch) {
        case 'b':
          ch = '\b';
          break;
        case 'n':
          ch = '\n';
          break;
        case 't':
          ch = '\t';
          break;
        case 'r':
          ch = '\r';
        // fall into
        case '"':
        case '\\':
        case '\'':
          break;
        case 'x':
        case 'u':
          int digitCount = ch == 'x' ? 2 : 4;
          if (ich < ichMax) {
            int unicode = 0;
            for (int k = digitCount; --k >= 0 && ich < ichMax;) {
              char chT = script.charAt(ich);
              int hexit = Escape.getHexitValue(chT);
              if (hexit < 0)
                break;
              unicode <<= 4;
              unicode += hexit;
              ++ich;
            }
            ch = (char) unicode;
          }
        }
      }
      sb.append(ch);
    }
    return sb.toString();
  }

  private boolean lookingAtLoadFormat() {
    // just allow a simple word or =xxxx or $CCCC
    int ichT = ichToken;
    char ch = '\0';
    boolean allchar = (ichT < cchScript && ((ch = script.charAt(ichT)) == '$' || ch == '='));
    while (ichT < cchScript
        && (Character.isLetterOrDigit(ch = script.charAt(ichT)) || allchar
            && (!eol(ch) && !Character.isWhitespace(ch))))
      ++ichT;
    if (!allchar && ichT == ichToken || !isSpaceOrTab(ch))
      return false;
    cchToken = ichT - ichToken;
    return true;
  }

  /**
   * An "implied string" is a parameter that is not quoted but because of its
   * position in a command is implied to be a string. First we must exclude @xxxx.
   * Then we consume the entire math syntax @{......} or any set of
   * characters not involving white space.
   * echo, hover, label, message, pause are odd-valued; no initial parsing of variables for them.
   * @param allowSpace
   *
   * @return true or false
   */
  private boolean lookingAtImpliedString(boolean allowSpace) {
    int ichT = ichToken;
    char ch = script.charAt(ichT);
    boolean parseVariables = !(Token.tokAttr(tokCommand, Token.implicitStringCommand)
        || (tokCommand & 1) == 0);
    boolean isVariable = (ch == '@');
    boolean isMath = (isVariable && ichT + 3 < cchScript && script.charAt(ichT + 1) == '{');
    if (isMath && parseVariables) {
      ichT = ichMathTerminator(script, ichToken + 1, cchScript);
      return (ichT != cchScript && (cchToken = ichT + 1 - ichToken) > 0);
    }
    int ptSpace = -1;
    int ptLastChar = -1;
    // look ahead to \n, \r, terminal ;, or }
    boolean isOK = true;
    int parenpt = 0;
    while (isOK && ichT < cchScript && !eol(ch = script.charAt(ichT))) {
      switch (ch) {
      case '{':
        parenpt++;
        break;
      case '}':
        // only consider this if it is extra
        parenpt--;
        if (parenpt < 0 && (braceCount > 0 || iBrace > 0)) {
          isOK = false;
          continue;
        }
        break;
      default:
        if (Character.isWhitespace(ch)) {
          if (ptSpace < 0)
            ptSpace = ichT;
        } else {
          ptLastChar = ichT;
        }
        break;
      }
      if (Character.isWhitespace(ch)) {
        if (ptSpace < 0)
          ptSpace = ichT;
      } else {
        ptLastChar = ichT;
      }
      ++ichT;
    }
    // message/echo/label @x
    // message/echo/label @{.....}
    // message/echo/label @{....} testing  NOT ok
    // message/echo/label @x bananas OK -- "@x bananas"
    // {... message/echo label ok } 
    if (allowSpace)
      ichT = ptLastChar + 1;
    else if (ptSpace > 0)
      ichT = ptSpace;
    if (isVariable && ptSpace < 0 && parenpt <= 0 && ichT - ichToken > 1) {
      // if we have @xxx then this is not an implied string
      return false;
    }
    return (cchToken = ichT - ichToken) > 0;
  }

  /**
   * For @{....}
   *
   * @param script
   * @param ichT
   * @param len
   * @return     position of "}"
   */
  public static int ichMathTerminator(String script, int ichT, int len) {
    int nP = 1;
    char chFirst = '\0';
    char chLast = '\0';
    while (nP > 0 && ++ichT < len) {
      char ch = script.charAt(ichT);
      if (chFirst != '\0') {
        if (chLast == '\\') {
          ch = '\0';
        } else if (ch == chFirst) {
          chFirst = '\0';
        }
        chLast = ch;
        continue;
      }
      switch(ch) {
      case '\'':
      case '"':
        chFirst = ch;
        break;
      case '{':
        nP++;
        break;
      case '}':
        nP--;
        break;
      }
    }
    return ichT;
  }

  private float lookingAtExponential() {
    if (ichToken == cchScript)
      return Float.NaN; //end
    int ichT = ichToken;
    int pt0 = ichT;
    if (script.charAt(ichT) == '-')
      ++ichT;
    boolean isOK = false;
    char ch = 'X';
    while (ichT < cchScript && Character.isDigit(ch = script.charAt(ichT))) {
      ++ichT;
      isOK = true;
    }
    if (ichT < cchScript && ch == '.')
      ++ichT;
    while (ichT < cchScript && Character.isDigit(ch = script.charAt(ichT))) {
      ++ichT;
      isOK = true;
    }
    if (ichT == cchScript || !isOK)
      return Float.NaN; //integer
    isOK = (ch != 'E' && ch != 'e');
    if (isOK || ++ichT == cchScript)
      return Float.NaN;
    ch = script.charAt(ichT);
    // I THOUGHT we only should allow "E+" or "E-" here, not "2E1" because
    // "2E1" might be a PDB het group by that name. BUT it turns out that
    // any HET group starting with a number is unacceptable and must
    // be given as [nXm], in brackets.

    if (ch == '-' || ch == '+')
      ichT++;
    while (ichT < cchScript && Character.isDigit(ch = script.charAt(ichT))) {
      ichT++;
      isOK = true;
    }
    if (!isOK)
      return Float.NaN;
    cchToken = ichT - ichToken;
    return (float) Double.valueOf(script.substring(pt0, ichT)).doubleValue();
  }

  private boolean lookingAtDecimal() {
    if (ichToken == cchScript)
      return false;
    int ichT = ichToken;
    if (script.charAt(ichT) == '-')
      ++ichT;
    boolean digitSeen = false;
    char ch = 'X';
    while (ichT < cchScript && Character.isDigit(ch = script.charAt(ichT++)))
      digitSeen = true;
    if (ch != '.')
      return false;
    // only here if  "dddd."

    // to support 1.ca, let's check the character after the dot
    // to determine if it is an alpha
    char ch1;
    if (ichT < cchScript && !eol(ch1 = script.charAt(ichT))) {
      if (Character.isLetter(ch1) || ch1 == '?' || ch1 == '*')
        return false;
      //well, guess what? we also have to look for 86.1Na, so...
      //watch out for moveto..... 56.;refresh...
      if (ichT + 1 < cchScript
          && (Character.isLetter(ch1 = script.charAt(ichT + 1)) || ch1 == '?'))
        return false;
    }
    while (ichT < cchScript && Character.isDigit(script.charAt(ichT))) {
      ++ichT;
      digitSeen = true;
    }
    cchToken = ichT - ichToken;
    return digitSeen;
  }

  private boolean lookingAtSeqcode() {
    int ichT = ichToken;
    char ch = ' ';
    if (ichT + 1 < cchScript && script.charAt(ichT) == '*'
        && script.charAt(ichT + 1) == '^') {
      ch = '^';
      ++ichT;
    } else {
      if (script.charAt(ichT) == '-')
        ++ichT;
      while (ichT < cchScript && Character.isDigit(ch = script.charAt(ichT)))
        ++ichT;
    }
    if (ch != '^')
      return false;
    ichT++;
    if (ichT == cchScript)
      ch = ' ';
    else
      ch = script.charAt(ichT++);
    if (ch != ' ' && ch != '*' && ch != '?' && !Character.isLetter(ch))
      return false;
    cchToken = ichT - ichToken;
    return true;
  }

  private int lookingAtInteger() {
    if (ichToken == cchScript)
      return Integer.MAX_VALUE;
    int ichT = ichToken;
    if (script.charAt(ichToken) == '-')
      ++ichT;
    int ichBeginDigits = ichT;
    while (ichT < cchScript && Character.isDigit(script.charAt(ichT)))
      ++ichT;
    if (ichBeginDigits == ichT)
      return Integer.MAX_VALUE;
    cchToken = ichT - ichToken;
    try {
      int val = Integer.parseInt(script.substring(ichToken, ichT));
      return val;
    } catch (NumberFormatException e) {
      // ignore
    }
    return Integer.MAX_VALUE;
  }

  BitSet lookingAtBitset() {
    // ({n n:m n}) or ({null})
    // [{n:m}] is a BOND bitset
    // EXCEPT if the previous token was a function:
    // {carbon}.distance({3 3 3})
    // Yes, I wish I had used {{...}}, but this will work.
    // WITHIN ({....}) unfortunately has two contexts
   
    if (script.indexOf("({null})", ichToken) == ichToken) {
      cchToken = 8;
      return new BitSet();
    }
    int ichT;
    if (ichToken + 4 > cchScript
        || script.charAt(ichToken + 1) != '{'
        || (ichT = script.indexOf("}", ichToken)) < 0
        || ichT + 1 == cchScript)
    return null;
    BitSet bs = Escape.unescapeBitset(script.substring(ichToken, ichT + 2));
    if (bs != null)
      cchToken = ichT + 2 - ichToken;
    return bs;
  }
 
  private boolean lookingAtObjectID(boolean allowWildID) {
    int ichT = ichToken;
    if (ichT == cchScript || script.charAt(ichT) != '$')
      return false;
    if (++ichT != cchScript && script.charAt(ichT) == '"')
      return false;
    while (ichT < cchScript) {
      char ch;
      if (Character.isWhitespace(ch = script.charAt(ichT))) {
        if (ichT == ichToken + 1)
          return false;
        break;
      }
      if (!Character.isLetterOrDigit(ch)) {
        switch (ch) {
        default:
          return false;
        case '*':
          if (!allowWildID)
            return false;
        case '~':
        case '_':
          break;
        }
      }
      ichT++;
    }
    cchToken = ichT - (++ichToken);
    return true;
  }

  private boolean lookingAtLookupToken(int ichT) {
    if (ichT == cchScript)
      return false;
    int ichT0 = ichT;
    tokLastMath = 0;
    char ch;
    switch (ch = script.charAt(ichT++)) {
    case '-':
    case '+':
    case '&':
    case '|':
    case '*':
      if (ichT < cchScript) {
        if (script.charAt(ichT) == ch) {
          ++ichT;
          if (ch == '-' || ch == '+')
            break;
          if (ch == '&' && ichT < cchScript && script.charAt(ichT) == ch)
            ++ichT; // &&&
        } else if (script.charAt(ichT) == '=') {
          ++ichT;
        }
      }
      tokLastMath = 1;
      break;
    case '/':
      if (ichT < cchScript && script.charAt(ichT) == '/')
        break;
    case '\\'// leftdivide
    case '!':
      if (ichT < cchScript && script.charAt(ichT) == '=')
        ++ichT;
      tokLastMath = 1;
      break;
    case ')':
    case ']':
    case '}':
    case '.':
      break;
    case '@':
    case '{':
      tokLastMath = 2; // NOT considered a continuation if at beginning of a line
      break;
    case ':':
      tokLastMath = 1;
      break;
    case '(':
    case ',':
    case '$':
    case ';':
    case '[':
    case '%':
      tokLastMath = 1;
      break;
    case '<':
    case '=':
    case '>':
      if (ichT < cchScript
          && ((ch = script.charAt(ichT)) == '<' || ch == '=' || ch == '>'))
        ++ichT;
      tokLastMath = 1;
      break;
    default:
      if (!Character.isLetter(ch))
        return false;
    //fall through
    case '~':
    case '_':
    case '\'':
    case '?': // include question marks in identifier for atom expressions
      if (ch == '?')
        tokLastMath = 1;
      while (ichT < cchScript
          && (Character.isLetterOrDigit(ch = script.charAt(ichT))
              || ch == '_' || ch == '?' || ch == '~' || ch == '\'')
          ||
          // hack for insertion codes embedded in an atom expression :-(
          // select c3^a
          (ch == '^' && ichT > ichT0 && Character.isDigit(script
              .charAt(ichT - 1)))
          || ch == '\\' && ichT + 1 < cchScript && script.charAt(ichT + 1) == '?')
        ++ichT;
      break;
    }
    cchToken = ichT - ichT0;
    return true;
  }

  private boolean charToken() {
    char ch;
    if (ichToken == cchScript || (ch = script.charAt(ichToken)) == '"' || ch == '@')
      return false;
    int ichT = ichToken;
    while (ichT < cchScript && !isSpaceOrTab(ch = script.charAt(ichT))
        && ch != '#' && ch != '}' && !eol(ch))
        ++ichT;
    cchToken = ichT - ichToken;
    return true;
  }
 
  private int ERROR(int error) {
    error(error, null, null);
    return ERROR;
  }
 
  private int ERROR(int error, String value) {
    error(error, value);
    return ERROR;
  }
 
  private boolean handleError() {
    errorType = errorMessage;
    errorLine = script.substring(ichCurrentCommand, ichEnd <= ichCurrentCommand ? ichToken : ichEnd);
    String lineInfo = (ichToken < ichEnd
        ? errorLine.substring(0, ichToken - ichCurrentCommand)
              + " >>>> " + errorLine.substring(ichToken - ichCurrentCommand)
        : errorLine)
        + " <<<<";
    errorMessage = GT._("script compiler ERROR: ") + errorMessage
         + ScriptEvaluator.setErrorLineMessage(null, filename, lineCurrent, iCommand, lineInfo);
    if (!isSilent) {
      viewer.addCommand(errorLine + CommandHistory.ERROR_FLAG);
      Logger.error(errorMessage);
    }
    return false;
  }

  /**
   * used by app to separate a command line into three sections:
   *
   * prefix....;cmd ........ token
   *
   * where token can be a just-finished single or double quote or
   * a string of characters
   *
   * @param cmd
   * @return String[] {prefix, cmd..... token}
   */
  public static String[] splitCommandLine(String cmd) {
    String[] sout = new String[3];
    boolean isEscaped1 = false;
    boolean isEscaped2 = false;
    boolean isEscaped = false;
    if (cmd.length() == 0)
      return null;
    int ptQ = -1;
    int ptCmd = 0;
    int ptToken = 0;
    for (int i = 0; i < cmd.length(); i++) {
      switch(cmd.charAt(i)) {
      case '"':
        if (!isEscaped && !isEscaped1) {
          isEscaped2 = !isEscaped2;
          if (isEscaped2)
            ptQ = ptToken = i;
        }
        break;
      case '\'':
        if (!isEscaped && !isEscaped2) {
          isEscaped1 = !isEscaped1;
          if (isEscaped1)
            ptQ = ptToken = i;
        }
        break;
      case '\\':
        isEscaped = !isEscaped;
        continue;
      case ' ':
        if (!isEscaped && !isEscaped1 && !isEscaped2) {
          ptToken = i + 1;
          ptQ = -1;
        }
        break;
      case ';':
        if (!isEscaped1 && !isEscaped2) {
          ptCmd = ptToken = i + 1;
          ptQ = -1;
        }
        break;
      default:
        if (!isEscaped1 && !isEscaped2)
          ptQ = -1;
      }
      isEscaped = false;       
     }
    sout[0] = cmd.substring(0, ptCmd);
    sout[1] = (ptToken == ptCmd ? cmd.substring(ptCmd) : cmd.substring(ptCmd, (ptToken > ptQ ? ptToken : ptQ)));
    sout[2] = (ptToken == ptCmd ? null : cmd.substring(ptToken));
    return sout;
  }


}
TOP

Related Classes of org.jmol.script.ScriptCompiler

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.