Package org.jmol.script

Source Code of org.jmol.script.ScriptCompilationTokenParser

/* $Author: hansonr $
* $Date: 2009-06-08 18:20:22 -0500 (Mon, 08 Jun 2009) $
* $Revision: 10975 $
*
* 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.Logger;
import org.jmol.util.TextFormat;
import org.jmol.viewer.JmolConstants;
import org.jmol.viewer.Viewer;
import org.jmol.api.JmolEdge;
import org.jmol.i18n.GT;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.vecmath.Point3f;

abstract class ScriptCompilationTokenParser {
 
  /*
   * An abstract class taking care of the second phase of
   * syntax checking, after all the tokens are created, 
   * these methods ensure that they are in the proper order
   * in terms of expressions, primarily.
   *
   * Here we are going from an "infix" to a "postfix" set
   * of tokens and then back to infix for final storage.
   *
   */

  protected Viewer viewer;

  protected String script;
  boolean isStateScript;

  protected short lineCurrent;
  protected int iCommand;
  protected int ichCurrentCommand, ichComment, ichEnd;
  protected int ichToken;
 
  protected Token theToken;
  protected Token lastFlowCommand;
  protected Token tokenCommand;
  protected Token lastToken;
  protected Token tokenAndEquals;

  protected int theTok;
  protected int nTokens;
  protected int tokCommand;
 
  protected int ptNewSetModifier;
  protected boolean isNewSet;

 
//----------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------
//---------------PHASE II -- TOKEN-BASED COMPILING ---------------------------------------
//----------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------

  protected boolean logMessages = false;

  protected Token[] atokenInfix;
  protected int itokenInfix;

  protected boolean isSetBrace;
  protected boolean isMathExpressionCommand;
  protected boolean isSetOrDefine;

  private List ltokenPostfix;

  protected boolean isEmbeddedExpression;
  protected boolean isCommaAsOrAllowed;
 
  private Object theValue;

  protected boolean compileExpressions() {
   
    isEmbeddedExpression = (tokCommand != Token.nada
        && (tokCommand != Token.function && tokCommand != Token.parallel
            && tokCommand != Token.trycmd && tokCommand != Token.catchcmd
            || tokenCommand.intValue != Integer.MAX_VALUE)
        && tokCommand != Token.end && !Token.tokAttrOr(tokCommand, Token.atomExpressionCommand,
            Token.implicitStringCommand));
    isMathExpressionCommand = (tokCommand == Token.identifier || Token.tokAttr(tokCommand, Token.mathExpressionCommand));
    boolean checkExpression = isEmbeddedExpression
        || (Token.tokAttr(tokCommand, Token.atomExpressionCommand));

    // $ at beginning disallow expression checking for center, delete, hide, or
    // display commands
    if (tokAt(1) == Token.dollarsign
        && Token.tokAttr(tokCommand, Token.atomExpressionCommand))
      checkExpression = false;
    if (checkExpression && !compileExpression())
      return false;

    // check statement length

    int size = atokenInfix.length;

    int nDefined = 0;
    for (int i = 1; i < size; i++) {
      if (tokAt(i) == Token.define)
        nDefined++;
    }

    size -= nDefined;
    if (isNewSet) {
      if (size == 1) {
        atokenInfix[0] = new Token(Token.function, 0, atokenInfix[0].value);
        isNewSet = false;
      }
    }

    if ((isNewSet || isSetBrace) && size < ptNewSetModifier + 2)
      return commandExpected();
    return (size == 1 || !Token.tokAttr(tokCommand, Token.noArgs) ? true
        : error(ERROR_badArgumentCount));
  }


  protected boolean compileExpression() {
    int firstToken = (isSetOrDefine && !isSetBrace ? 2 : 1);
    ltokenPostfix = new ArrayList();
    itokenInfix = 0;
    Token tokenBegin = null;
    if (tokCommand == Token.restrict && tokAt(1) == Token.bonds) {
      addNextToken();
      addNextToken();
    } else {
      for (int i = 0; i < firstToken && addNextToken(); i++) {
      }
    }
    while (moreTokens()) {
      if (isEmbeddedExpression) {
        while (!isExpressionNext()) {
          if (tokPeek(Token.identifier) && !(tokCommand == Token.load && itokenInfix == 1)) {
            String name = (String) atokenInfix[itokenInfix].value;
            Token t = Token.getTokenFromName(name);
            if (t != null)
              if (!isMathExpressionCommand && lastToken.tok != Token.define
                  || (lastToken.tok == Token.per || tokAt(itokenInfix + 1) == Token.leftparen)
                        && !isUserFunction(name)) {
                // Checking here for known token mascarading as identifier due to VAR definition.
                // We reset it to its original mapping if it's a known token and:
                //    a) this isn't a math expression command, and not preceeded by @, or
                //    b) it is preceded by "." or followed by "("
                //             and it isn't the name of a user function

                atokenInfix[itokenInfix] = t;
            }
          }
          if (!addNextToken())
            break;
        }
        if (!moreTokens())
          break;
      }
      if (lastToken.tok == Token.define) {
        if (!clauseDefine(true, false))
          return false;
        continue;
      }
      if (!isMathExpressionCommand)
        addTokenToPostfix(tokenBegin = new Token(Token.expressionBegin, "implicitExpressionBegin"));
      if (!clauseOr(isCommaAsOrAllowed || !isMathExpressionCommand
          && tokPeek(Token.leftparen)))
        return false;
      if (!isMathExpressionCommand
          && !(isEmbeddedExpression && lastToken == Token.tokenCoordinateEnd)) {
        addTokenToPostfix(Token.tokenExpressionEnd);
      }
      if (moreTokens()) {
        if (tokCommand != Token.select && !isEmbeddedExpression)
          return error(ERROR_endOfExpressionExpected);
        if (tokCommand == Token.select) {
          // advanced select, with two expressions, the first
          // being an atom expression; the second being a property selector expression
          tokenBegin.intValue = 0;
          tokCommand = Token.nada;
          isEmbeddedExpression = true;
          isMathExpressionCommand = true;
          isCommaAsOrAllowed = false;
        }
      }
    }
    atokenInfix = new Token[ltokenPostfix.size()];
    for (int i = ltokenPostfix.size(); --i >= 0;)
      atokenInfix[i] = (Token) ltokenPostfix.get(i);
    return true;
  }

  protected Map htUserFunctions;
  protected boolean isUserFunction(String name) {
    return (!isStateScript && (viewer.isFunction(name) || htUserFunctions.containsKey(name)));
  }

  private boolean isExpressionNext() {
    return tokPeek(Token.leftbrace)
    && !(tokAt(itokenInfix + 1) == Token.string
         && tokAt(itokenInfix + 2) == Token.colon)
    || !isMathExpressionCommand && tokPeek(Token.leftparen);
  }

  protected static boolean tokenAttr(Token token, int tok) {
    return token != null && Token.tokAttr(token.tok, tok);
  }
 
  private boolean moreTokens() {
    return (itokenInfix < atokenInfix.length);
  }
 
  protected int tokAt(int i) {
    return (i < atokenInfix.length ? atokenInfix[i].tok : Token.nada);
  }
 
  private int tokPeek() {
    return (itokenInfix >= atokenInfix.length ? Token.nada
        : atokenInfix[itokenInfix].tok);
  }

  private boolean tokPeek(int tok) {
    return (tokAt(itokenInfix) == tok);
  }

  private int intPeek() {
    return (itokenInfix >= atokenInfix.length ? Integer.MAX_VALUE
        : atokenInfix[itokenInfix].intValue);
  }
 
  private Object valuePeek() {
    return (moreTokens() ? atokenInfix[itokenInfix].value : "");
  }
  /**
   * increments the pointer; does NOT set theToken or theValue
   * @return the next token
   */
  private Token tokenNext() {
    return (itokenInfix >= atokenInfix.length ? null
        : atokenInfix[itokenInfix++]);
  }
 
  private boolean tokenNext(int tok) {
    Token token = tokenNext();
    return (token != null && token.tok == tok);
  }

  private boolean returnToken() {
    itokenInfix--;
    return false;
  }

  /**
   * gets the next token and sets global theToken and theValue
   * @return the next token
   */
  private Token getToken() {
    theValue = ((theToken = tokenNext()) == null ? null : theToken.value);
    return theToken;
  }
 
  private boolean isToken(int tok) {
    return theToken != null && theToken.tok == tok;
  }
 
  private boolean getNumericalToken() {
    return (getToken() != null
        && (isToken(Token.integer) || isToken(Token.decimal)));
  }
 
  private float floatValue() {
    switch (theToken.tok) {
    case Token.integer:
      return theToken.intValue;
    case Token.decimal:
      return ((Float) theValue).floatValue();
    }
    return 0;
  }

  private boolean addTokenToPostfix(int tok, Object value) {
    return addTokenToPostfix(new Token(tok, value));
  }

  private boolean addTokenToPostfix(int tok, int intValue, Object value) {
    return addTokenToPostfix(new Token(tok, intValue, value));
  }

  private boolean addTokenToPostfix(Token token) {
    if (token == null)
      return false;
    if (logMessages)
        Logger.debug("addTokenToPostfix" + token);
    ltokenPostfix.add(token);
    lastToken = token;
    return true;
  }

  private boolean addNextToken() {
    return addTokenToPostfix(tokenNext());
  }
 
  private boolean addNextTokenIf(int tok) {
    return (tokPeek(tok) && addNextToken());
  }
 
  private boolean addSubstituteTokenIf(int tok, Token token) {
    if (!tokPeek(tok))
      return false;
    itokenInfix++;
    return addTokenToPostfix(token);
  }
 
  boolean haveString;
 
  private boolean clauseOr(boolean allowComma) {
    haveString = false;
    if (!clauseAnd())
      return false;
    if (isEmbeddedExpression && lastToken.tok == Token.expressionEnd)
      return true;

    //for simplicity, giving XOR (toggle) same precedence as OR
    //OrNot: First OR, but if that makes no change, then NOT (special toggle)
    int tok;
    while ((tok = tokPeek())== Token.opOr || tok == Token.opXor
        || tok==Token.opToggle|| allowComma && tok == Token.comma) {
      if (tok == Token.comma && !haveString)
        addSubstituteTokenIf(Token.comma, Token.tokenOr);
      else
        addNextToken();
      if (!clauseAnd())
        return false;
      if (allowComma && (lastToken.tok == Token.rightbrace || lastToken.tok == Token.bitset))
        haveString = true;
    }
    return true;
  }

  private boolean clauseAnd() {
    if (!clauseNot())
      return false;
    if (isEmbeddedExpression && lastToken.tok == Token.expressionEnd)
      return true;
    while (tokPeek(Token.opAnd)) {
      addNextToken();
      if (!clauseNot())
        return false;
    }
    return true;
  }

  // for RPN processor, not reversed
  private boolean clauseNot() {
    if (tokPeek(Token.opNot)) {
      addNextToken();
      return clauseNot();
    }
    return (clausePrimitive());
  }
 
  private boolean clausePrimitive() {
    int tok = tokPeek();
    switch (tok) {
    case Token.spacebeforesquare:
      itokenInfix++;
      return clausePrimitive();
    case Token.nada:
      return error(ERROR_endOfCommandUnexpected);
    case Token.all:
    case Token.bitset:
    case Token.divide:
    case Token.helix:
    case Token.helix310:
    case Token.helixalpha:
    case Token.helixpi:
    case Token.isaromatic:
    case Token.none:
    case Token.sheet:
      // nothing special
      return addNextToken();
    case Token.string:
      haveString = true;
      return addNextToken();
    case Token.decimal:
      // create a file_model integer as part of the token
      return addTokenToPostfix(Token.spec_model2, getToken().intValue, theValue);
    case Token.cell:
      return clauseCell();
    case Token.connected:
      return clauseConnected();
    case Token.search:
    case Token.smiles:
      return clauseSubstructure();
    case Token.within:
      return clauseWithin();
    case Token.define:
      return clauseDefine(false, false);
    case Token.bonds:
    case Token.measure:
      addNextToken();
      if (tokPeek(Token.bitset))
        addNextToken();
      else if (tokPeek(Token.define))
        return clauseDefine(false, false);
      return true;
    case Token.leftparen:
      addNextToken();
      if (!clauseOr(true))
        return false;
      if (!addNextTokenIf(Token.rightparen))
        return error(ERROR_tokenExpected, ")");
      return checkForItemSelector(true);
    case Token.leftbrace:
      return checkForCoordinate(isMathExpressionCommand);
    default:
      // may be a residue specification
      if (clauseResidueSpec())
        return true;
      if (isError())
        return false;
      if (Token.tokAttr(tok, Token.atomproperty)) {
        int itemp = itokenInfix;
        boolean isOK = clauseComparator(true);
        if (isOK || itokenInfix != itemp)
          return isOK;
        if (tok == Token.substructure) {
          return clauseSubstructure();
        }

      }
      //if (tok != Token.integer && !Token.tokAttr(tok, Token.predefinedset))
        //break;
      return addNextToken();

    }
//    return error(ERROR_unrecognizedExpressionToken, "" + valuePeek());
  }

  private boolean checkForCoordinate(boolean isImplicitExpression) {
    /*
     * A bit tricky here: we have three contexts for braces --
     *
     * 1) expressionCommands SELECT, RESTRICT, DEFINE,
     *    DISPLAY, HIDE, CENTER, and SUBSET
     *
     * 2) embeddedExpression commands such as DRAW and ISOSURFACE
     *
     * 3) IF and SET
     *
     * Then, within these, we have the possibility that we are
     * looking at a coordinate {0 0 0} (with or without commas, and
     * possibly fractional, {1/2 1/2 1}, and possibly a plane Point4f
     * definition, {a b c d}) or an expression.
     *
     * We assume an expression initially and then adjust accordingly
     * if it turns out this is a coordinate.
     *
     * Note that due to tha nuances of how expressions such as (1-4) are
     * reported as special codes, Eval must still intepret these
     * carefully. This could be corrected for here, I think.
     *
     */
    boolean isCoordinate = false;
    int pt = ltokenPostfix.size();
    if (isImplicitExpression) {
      addTokenToPostfix(Token.tokenExpressionBegin);
      tokenNext();
    } else if (isEmbeddedExpression) {
      tokenNext();
      pt--;
    } else {
      addNextToken();
    }
    boolean isHash = tokPeek(Token.string);
    if (isHash) {
      isImplicitExpression = false;
      returnToken();
      ltokenPostfix.remove(ltokenPostfix.size() - 1);
      addNextToken();
      int nBrace = 1;
      while (nBrace != 0) {
        if (tokPeek(Token.leftbrace)) {
          if (isExpressionNext()) {
            addTokenToPostfix(new Token(Token.expressionBegin,
                "implicitExpressionBegin"));
            if (!clauseOr(true))
              return false;
            if (lastToken != Token.tokenCoordinateEnd) {
              addTokenToPostfix(Token.tokenExpressionEnd);
            }
          } else {
            nBrace++;
          }
        }
        if (tokPeek(Token.rightbrace))
          nBrace--;
        addNextToken();
      }
    } else {
      if (!tokPeek(Token.rightbrace) && !clauseOr(false))
        return false;
      int n = 1;
      while (!tokPeek(Token.rightbrace)) {
        boolean haveComma = addNextTokenIf(Token.comma);
        if (!clauseOr(false))
          return (haveComma || n < 3 ? false : error(ERROR_tokenExpected, "}"));
        n++;
      }
      isCoordinate = (n >= 2); // could be {1 -2 3}
    }
    if (isCoordinate && (isImplicitExpression || isEmbeddedExpression)) {
      ltokenPostfix.set(pt, Token.tokenCoordinateBegin);
      addTokenToPostfix(Token.tokenCoordinateEnd);
      tokenNext();
    } else if (isImplicitExpression) {
      addTokenToPostfix(Token.tokenExpressionEnd);
      tokenNext();
    } else if (isEmbeddedExpression && !isHash) {
      tokenNext();
    } else {
      addNextToken();
    }
    return checkForItemSelector(!isHash);
  }
 
  private boolean checkForItemSelector(boolean allowNumeric) {
    // {x[1]}  @{x}[1][3]  (atomno=3)[2][5]
    int tok;
    if ((tok = tokAt(itokenInfix + 1)) == Token.leftsquare
        || allowNumeric && tok == Token.leftbrace)
      return true; // [[, as in a matrix or [{ ... not totally acceptable!
   
    // the real problem is that after an expression you can have
    while (true) {//for (int i = 0; i < (allowNumeric ? 2 : 1); i++) {
      if (!addNextTokenIf(Token.leftsquare))
        break;
      if (!clauseItemSelector())
        return false;
      if (!addNextTokenIf(Token.rightsquare))
        return error(ERROR_tokenExpected, "]");
    }
    return true;
  }
 
  private boolean clauseWithin() {

    // within ( distance, plane, planeExpression)
    // within ( distance, hkl, hklExpression)
    // within ( distance, coord, point)
    // within ( distance, orClause)
    // within ( group|branch|etc, ....)
    // within ( distance, group, ....)

    addNextToken();
    if (!addNextTokenIf(Token.leftparen))
      return false;
    if (getToken() == null)
      return false;
    float distance = Float.MAX_VALUE;
    String key = null;
    boolean allowComma = true;
    int tok0 = theToken.tok;
    int tok;
    switch (tok0) {
    case Token.minus:
      if (getToken() == null)
        return false;
      if (theToken.tok != Token.integer)
        return error(ERROR_numberExpected);
      distance = -theToken.intValue;
      break;
    case Token.integer:
    case Token.decimal:
      distance = floatValue();
      break;
    case Token.search:
    case Token.smiles: 
      addTokenToPostfix(Token.string, theValue);
      if (!addNextTokenIf(Token.comma))
        return false;
      allowComma = false;
      tok = tokPeek();
      switch (tok) {
      case Token.nada:
        return false;
      case Token.string:
        addNextToken();
        key = "";
        break;
      case Token.define:
        if (!clauseDefine(false, true))
          return false;
        key = "";
        break;
      default:
        return false;
      }
      break;
    case Token.branch:
      allowComma = false;
      // fall through
    case Token.atomtype:
    case Token.atomname:
    case Token.basepair:
    case Token.boundbox:
    case Token.chain:
    case Token.coord:
    case Token.element:
    case Token.group:
    case Token.helix:
    case Token.hkl:
    case Token.model:
    case Token.molecule:
    case Token.plane:
    case Token.polymer:
    case Token.sequence:
    case Token.sheet:
    case Token.site:
    case Token.structure:
    case Token.string:
      key = (String) theValue;
      break;
    case Token.identifier:
      key = ((String) theValue).toLowerCase();
      break;
    default:
      return error(ERROR_unrecognizedParameter, "WITHIN", ": " + theToken.value);
    }
    if (key == null)
      addTokenToPostfix(Token.decimal, new Float(distance));
    else if (key.length() > 0)
      addTokenToPostfix(Token.string, key);
    while (true) {
      if (!addNextTokenIf(Token.comma))
        break;
      tok = tokPeek();
      switch (tok0) {
      case Token.integer:
      case Token.decimal:
        if (tok == Token.on || tok == Token.off) {
          addTokenToPostfix(getToken());
          if (!addNextTokenIf(Token.comma))
            break;
          tok = tokPeek();
        }
        break;
      }
      boolean isCoordOrPlane = false;
      if (key == null) {
        switch (tok) {
        case Token.hkl:
        case Token.coord:
        case Token.plane:
          isCoordOrPlane = true;
          addNextToken();
          break;
        case Token.group:
          getToken();
          addTokenToPostfix(Token.string, "group");
          break;
        case Token.leftbrace:
          returnToken();
          isCoordOrPlane = true;
          addTokenToPostfix(Token
              .getTokenFromName(distance == Float.MAX_VALUE ? "plane" : "coord"));
        }
        addNextTokenIf(Token.comma);
      }
      tok = tokPeek();
      if (isCoordOrPlane) {
        while (!tokPeek(Token.rightparen)) {
          switch (tokPeek()) {
          case Token.nada:
            return error(ERROR_endOfCommandUnexpected);
          case Token.leftparen:
            addTokenToPostfix(Token.tokenExpressionBegin);
            addNextToken();
            if (!clauseOr(false))
              return error(ERROR_unrecognizedParameter, "WITHIN", ": ?");
            if (!addNextTokenIf(Token.rightparen))
              return error(ERROR_tokenExpected, ", / )");
            addTokenToPostfix(Token.tokenExpressionEnd);
            break;
          case Token.define:
            if (!clauseDefine(false, false))
              return false;
            break;
          default:
            addTokenToPostfix(getToken());
          }
        }
      } else if (!clauseOr(allowComma)) {// *expression*        return error(ERROR_badArgumentCount);
      }
    }
    if (!addNextTokenIf(Token.rightparen))
      return error(ERROR_tokenExpected, ")");
    return true;
  }

  private boolean clauseConnected() {
    addNextToken();
    // connected (1,3, single, .....)
    if (!addNextTokenIf(Token.leftparen)) {
      addTokenToPostfix(Token.tokenLeftParen);
      addTokenToPostfix(Token.tokenRightParen);
      return true;
    }
    while (true) {
      if (addNextTokenIf(Token.integer))
        if (!addNextTokenIf(Token.comma))
          break;
      if (addNextTokenIf(Token.integer))
        if (!addNextTokenIf(Token.comma))
          break;
      if (addNextTokenIf(Token.decimal))
        if (!addNextTokenIf(Token.comma))
          break;
      if (addNextTokenIf(Token.decimal))
        if (!addNextTokenIf(Token.comma))
          break;
        String strOrder = (String) getToken().value;
        int intType = JmolConstants.getBondOrderFromString(strOrder);        if (intType == JmolEdge.BOND_ORDER_NULL) {
          returnToken();
        } else {
          addTokenToPostfix(Token.string, strOrder);
          if (!addNextTokenIf(Token.comma))
            break;
        }
      if (addNextTokenIf(Token.rightparen))
        return true;
      if (!clauseOr(tokPeek(Token.leftparen))) // *expression*
        return false;
      if (addNextTokenIf(Token.rightparen))
        return true;
      if (!addNextTokenIf(Token.comma))
        return false;
      if (!clauseOr(tokPeek(Token.leftparen))) // *expression*
        return false;

      break;
    }
    if (!addNextTokenIf(Token.rightparen))
      return error(ERROR_tokenExpected, ")");
    return true;
  }

  private boolean clauseSubstructure() {
    addNextToken();
    if (!addNextTokenIf(Token.leftparen))
      return false;
    if (tokPeek(Token.define)) {
      if (!clauseDefine(false, true))
        return false;
    } else if (!addNextTokenIf(Token.string)) {
      return error(ERROR_tokenExpected, "\"...\"");
    }
    if (addNextTokenIf(Token.comma))
      if (!clauseOr(tokPeek(Token.leftparen))) // *expression*
        return false;
    if (!addNextTokenIf(Token.rightparen))
      return error(ERROR_tokenExpected, ")");
    return true;
  }

  private boolean clauseItemSelector() {
    int tok;
    int nparen = 0;
    while ((tok = tokPeek()) != Token.nada && tok != Token.rightsquare) {
      addNextToken();
      if (tok == Token.leftsquare)
        nparen++;
      if (tokPeek() == Token.rightsquare && nparen-- > 0)
        addNextToken();
    }
    return true;
  }
 
  private boolean clauseComparator(boolean isOptional) {
    Token tokenAtomProperty = tokenNext();
    Token tokenComparator = tokenNext();
    if (!tokenAttr(tokenComparator, Token.comparator)) {
      if (!isOptional)
        return error(ERROR_tokenExpected, "== != < > <= >=");
      if (tokenComparator != null)
        returnToken();
      returnToken();
      return false;
    }
    if (tokenAttr(tokenAtomProperty, Token.strproperty)
        && tokenComparator.tok != Token.opEQ && tokenComparator.tok != Token.opNE)
      return error(ERROR_tokenExpected, "== !=");
    if (getToken() == null)
      return error(ERROR_unrecognizedExpressionToken, "" + valuePeek());
    boolean isNegative = (isToken(Token.minus));
    if (isNegative && getToken() == null)
      return error(ERROR_numberExpected);
    switch (theToken.tok) {
    case Token.integer:
    case Token.decimal:
    case Token.identifier:
    case Token.string:
    case Token.leftbrace:
    case Token.define:
      break;
    default:
      if (!Token.tokAttr(theToken.tok, Token.misc))
        return error(ERROR_numberOrVariableNameExpected);
    }
    addTokenToPostfix(tokenComparator.tok, tokenAtomProperty.tok,
        tokenComparator.value + (isNegative ? " -" : ""));
    if (tokenAtomProperty.tok == Token.property)
      addTokenToPostfix(tokenAtomProperty);
    if (isToken(Token.leftbrace)) {
      returnToken();
      return clausePrimitive();
    }
    addTokenToPostfix(theToken);
    if (theToken.tok == Token.define)
      return clauseDefine(false, false);
    return true;
  }

  private boolean clauseCell() {
    Point3f cell = new Point3f();
    tokenNext(); // CELL
    if (!tokenNext(Token.opEQ)) // =
      return error(ERROR_tokenExpected, "=");
    if (getToken() == null)
      return error(ERROR_coordinateExpected);
    // 555 = {1 1 1}
    //Token coord = tokenNext(); // 555 == {1 1 1}
    if (isToken(Token.integer)) {
      int nnn = theToken.intValue;
      cell.x = nnn / 100 - 4;
      cell.y = (nnn % 100) / 10 - 4;
      cell.z = (nnn % 10) - 4;
      return addTokenToPostfix(Token.cell, cell);
    }
    if (!isToken(Token.leftbrace) || !getNumericalToken())
      return error(ERROR_coordinateExpected); // i
    cell.x = floatValue();
    if (tokPeek(Token.comma)) // ,
      tokenNext();
    if (!getNumericalToken())
      return error(ERROR_coordinateExpected); // j
    cell.y = floatValue();
    if (tokPeek(Token.comma)) // ,
      tokenNext();
    if (!getNumericalToken() || !tokenNext(Token.rightbrace))
      return error(ERROR_coordinateExpected); // k
    cell.z = floatValue();
    return addTokenToPostfix(Token.cell, cell);
  }

  private boolean clauseDefine(boolean haveToken, boolean forceString) {
    if (!haveToken) {
      Token token = tokenNext();
      if (forceString) // we know it is @, this forces string type
        token = Token.tokenDefineString;
      addTokenToPostfix(token);
    }
    if (tokPeek() == Token.nada)
      return error(ERROR_endOfCommandUnexpected);
    // we allow @x[1], which compiles as {@x}[1], not @{x[1]}
    // otherwise [1] gets read as a general atom name selector
    if (!addSubstituteTokenIf(Token.leftbrace, Token.tokenExpressionBegin))
      return addNextToken() && checkForItemSelector(true);
    while (moreTokens() && !tokPeek(Token.rightbrace)) {
      if (tokPeek(Token.leftbrace)) {
        if (!checkForCoordinate(true))
          return false;
      } else {
        addNextToken();
      }
    }
    return addSubstituteTokenIf(Token.rightbrace, Token.tokenExpressionEnd)
        && checkForItemSelector(true);
  }

  private boolean residueSpecCodeGenerated;

  private boolean generateResidueSpecCode(Token token) {
    if (residueSpecCodeGenerated)
      addTokenToPostfix(Token.tokenAND);
    addTokenToPostfix(token);
    residueSpecCodeGenerated = true;
    return true;
  }

  private boolean clauseResidueSpec() {
    int tok = tokPeek();
    residueSpecCodeGenerated = false;
    boolean checkResNameSpec = false;
    switch (tok) {
    case Token.nada:
    case Token.dna:
    case Token.rna:
      return false;
    case Token.colon:
    case Token.integer:
    case Token.percent:
    case Token.seqcode:
      break;
    case Token.times:
    case Token.leftsquare:
    case Token.identifier:
      checkResNameSpec = true;
      break;
    default:
      if (Token.tokAttr(tok, Token.comparator))
        return false;
      String str = "" + valuePeek();
      checkResNameSpec = (str.length() == 2 || str.length() == 3);
      // note: there are many groups that could
      // in principle be here, for example:
      // "AND" "SET" "TO*"
      // these need to have attribute expression to be here
      // but then there are FX FY FZ UX UY UZ ..
      if (!checkResNameSpec)
        return false;
    }
    boolean specSeen = false;
    if (checkResNameSpec) {
      if (!clauseResNameSpec())
        return false;
      specSeen = true;
      tok = tokPeek();
      if (Token.tokAttr(tok, Token.comparator)) {
        returnToken();
        ltokenPostfix.remove(ltokenPostfix.size() - 1);
        return false;
      }

    }
    boolean wasInteger = false;
    if (tok == Token.times || tok == Token.integer || tok == Token.seqcode) {
      wasInteger = (tok == Token.integer);
      if (tokPeek(Token.times))
        getToken();
      else if (!clauseSequenceSpec())
        return false;
      specSeen = true;
      tok = tokPeek();
    }
    if (tok == Token.colon || tok == Token.times || tok == Token.identifier
        || tok == Token.x || tok == Token.y || tok == Token.z
        || tok == Token.integer && !wasInteger) {
      if (!clauseChainSpec(tok))
        return false;
      specSeen = true;
      tok = tokPeek();
    }
    if (tok == Token.per) {
      if (!clauseAtomSpec())
        return false;
      specSeen = true;
      tok = tokPeek();
    }
    if (tok == Token.percent) {
      if (!clauseAlternateSpec())
        return false;
      specSeen = true;
      tok = tokPeek();
    }
    if (tok == Token.colon || tok == Token.divide) {
      if (!clauseModelSpec())
        return false;
      specSeen = true;
      tok = tokPeek();
    }
    if (!specSeen)
      return error(ERROR_residueSpecificationExpected);
    if (!residueSpecCodeGenerated) {
      // nobody generated any code, so everybody was a * (or equivalent)
      addTokenToPostfix(Token.tokenAll);
    }
    return true;
  }

  private boolean clauseResNameSpec() {
    getToken();
    switch (theToken.tok) {
    case Token.times:
      return true;
    case Token.leftsquare:
      String strSpec = "";
      while (getToken() != null && !isToken(Token.rightsquare))
        strSpec += theValue;
      if (!isToken(Token.rightsquare))
        return false;
      if (strSpec == "")
        return true;
      int pt;
      if (strSpec.length() > 0 && (pt = strSpec.indexOf("*")) >= 0
          && pt != strSpec.length() - 1)
        return error(ERROR_residueSpecificationExpected);
      strSpec = strSpec.toUpperCase();
      return generateResidueSpecCode(new Token(Token.spec_name_pattern, strSpec));
    default:
      //check for a * in the next token, which
      //would indicate this must be a name with wildcard

      String res = (String) theValue;
      if (tokPeek(Token.times)) {
        res = theValue + "*";
        getToken();
      }
      return generateResidueSpecCode(new Token(Token.identifier, res));
    }
  }

  private boolean clauseSequenceSpec() {
    Token seqToken = getSequenceCode(false);
    if (seqToken == null)
      return false;
    int tok = tokPeek();
    if (tok == Token.minus || tok == Token.integer && intPeek() < 0) {
      if (tok == Token.minus) {
        tokenNext();
      } else {
         // hyphen masquerading as neg int
          int i = -intPeek();
          tokenNext().intValue = i;
          returnToken();
      }
      seqToken.tok = Token.spec_seqcode_range;
      generateResidueSpecCode(seqToken);
      return addTokenToPostfix(getSequenceCode(true));
    }
    return generateResidueSpecCode(seqToken);
  }

  private Token getSequenceCode(boolean isSecond) {
    int seqcode = Integer.MAX_VALUE;
    int seqvalue = Integer.MAX_VALUE;
    int tokPeek = tokPeek();
    if (tokPeek == Token.seqcode)
      seqcode = tokenNext().intValue;
    else if (tokPeek == Token.integer)
      seqvalue = tokenNext().intValue;
    else if (!isSecond){
      return null;
      // can have open-ended range 
      // select 3-
    }
    return new Token(Token.spec_seqcode, seqvalue, new Integer(seqcode));
  }

  private boolean clauseChainSpec(int tok) {
    if (tok == Token.colon) {
      tokenNext();
      tok = tokPeek();
      if (isSpecTerminator(tok))
        return generateResidueSpecCode(new Token(Token.spec_chain, '\0',
            "spec_chain"));
    }
    char chain;
    switch (tok) {
    case Token.times:
      return (getToken() != null);
    case Token.integer:
      getToken();
      int val = theToken.intValue;
      if (val < 0 || val > 9)
        return error(ERROR_invalidChainSpecification);
      chain = (char) ('0' + val);
      break;
    default:
      String strChain = "" + getToken().value;
      if (strChain.length() != 1)
        return error(ERROR_invalidChainSpecification);
      chain = strChain.charAt(0);
      if (chain == '?')
        return true;
      break;
    }
    return generateResidueSpecCode(new Token(Token.spec_chain, chain,
        "spec_chain"));
  }

  private boolean isSpecTerminator(int tok) {
    switch (tok) {
    case Token.nada:
    case Token.divide:
    case Token.opAnd:
    case Token.opOr:
    case Token.opNot:
    case Token.comma:
    case Token.percent:
    case Token.rightparen:
    case Token.rightbrace:
      return true;
    }
    return false;
  }

  private boolean clauseAlternateSpec() {
    tokenNext();
    int tok = tokPeek();
    if (isSpecTerminator(tok))
      return generateResidueSpecCode(new Token(Token.spec_alternate, null));
    String alternate = (String) getToken().value;
    switch (theToken.tok) {
    case Token.times:
    case Token.string:
    case Token.integer:
    case Token.identifier:
      break;
    default:
      return error(ERROR_invalidModelSpecification);
    }
    //Logger.debug("alternate specification seen:" + alternate);
    return generateResidueSpecCode(new Token(Token.spec_alternate, alternate));
  }

  private boolean clauseModelSpec() {
    getToken();
    if (tokPeek(Token.times)) {
      getToken();
      return true;
    }
    switch (tokPeek()) {
    case Token.integer:
      return generateResidueSpecCode(new Token(Token.spec_model, new Integer(
          getToken().intValue)));
    case Token.decimal:
            return generateResidueSpecCode(new Token(Token.spec_model,
          getToken().intValue, theValue));
    case Token.comma:
    case Token.rightbrace:
    case Token.nada:
      return generateResidueSpecCode(new Token(Token.spec_model, new Integer(1)));
    }
    return error(ERROR_invalidModelSpecification);
  }

  private boolean clauseAtomSpec() {
    if (!tokenNext(Token.per))
      return error(ERROR_invalidAtomSpecification);
    if (getToken() == null)
      return true;
    String atomSpec = "";
    if (isToken(Token.integer)) {
      atomSpec += "" + theToken.intValue;
      if (getToken() == null)
        return error(ERROR_invalidAtomSpecification);
    }
    switch (theToken.tok) {
    case Token.times:
      return true;
      // here we cannot depend upon the atom spec being an identifier
      // in other words, not a known Jmol word. As long as the period
      // was there, we accept whatever is next
      //case Token.opIf:
      //case Token.identifier:
      //break;
      //default:
      //return error(ERROR_invalidAtomSpecification);
    }
    atomSpec += "" + theToken.value;
    if (tokPeek(Token.times)) {
      tokenNext();
      // this one is a '*' as a prime, not a wildcard
      atomSpec += "'";
    }
    int atomID = JmolConstants.lookupSpecialAtomID(atomSpec.toUpperCase());
    return generateResidueSpecCode(new Token(Token.spec_atom, atomID, atomSpec));
  }
 
//----------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------
//------------   ERROR HANDLING   --------------------------------------------------------
//----------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------
 
  protected String errorMessage;
  protected String errorMessageUntranslated;
  protected String errorLine;
  protected String errorType;

  protected final static int ERROR_badArgumentCount  = 0;
  protected final static int ERROR_badContext  = 1;
  protected final static int ERROR_commandExpected = 2;
  protected final static int ERROR_endOfCommandUnexpected  = 4;
  protected final static int ERROR_invalidExpressionToken  = 9;
  protected final static int ERROR_missingEnd  = 11;
  protected final static int ERROR_tokenExpected  = 15;
  protected final static int ERROR_tokenUnexpected  = 16;
  protected final static int ERROR_unrecognizedParameter  = 18;
  protected final static int ERROR_unrecognizedToken  = 19;

  private final static int ERROR_coordinateExpected  = 3;
  private final static int ERROR_endOfExpressionExpected  = 5;
  private final static int ERROR_identifierOrResidueSpecificationExpected  = 6;
  private final static int ERROR_invalidAtomSpecification  = 7;
  private final static int ERROR_invalidChainSpecification  = 8;
  private final static int ERROR_invalidModelSpecification  = 10;
  private final static int ERROR_numberExpected  = 12;
  private final static int ERROR_numberOrVariableNameExpected  = 13;
  private final static int ERROR_residueSpecificationExpected  = 14;
  private final static int ERROR_unrecognizedExpressionToken  = 17;
 
  static String errorString(int iError, String value, String more,
                            boolean translated) {
    boolean doTranslate = false;
    if (!translated && (doTranslate = GT.getDoTranslate()) == true)
      GT.setDoTranslate(false);
    String msg;
    switch (iError) {
    default:
      msg = "Unknown compiler error message number: " + iError;
      break;
    case ERROR_badArgumentCount: // 0;
      msg = GT._("bad argument count"); // 0
      break;
    case ERROR_badContext: // 1;
      msg = GT._("invalid context for {0}"); // 1
      break;
    case ERROR_commandExpected: // 2;
      msg = GT._("command expected"); // 2
      break;
    case ERROR_coordinateExpected: // 3;
      msg = GT._("{ number number number } expected"); // 3
      break;
    case ERROR_endOfCommandUnexpected: // 4;
      msg = GT._("unexpected end of script command"); // 4
      break;
    case ERROR_endOfExpressionExpected: // 5;
      msg = GT._("end of expression expected"); // 5
      break;
    case ERROR_identifierOrResidueSpecificationExpected: // 6;
      msg = GT._("identifier or residue specification expected"); // 6
      break;
    case ERROR_invalidAtomSpecification: // 7;
      msg = GT._("invalid atom specification"); // 7
      break;
    case ERROR_invalidChainSpecification: // 8;
      msg = GT._("invalid chain specification"); // 8
      break;
    case ERROR_invalidExpressionToken: // 9;
      msg = GT._("invalid expression token: {0}"); // 9
      break;
    case ERROR_invalidModelSpecification: // 10;
      msg = GT._("invalid model specification"); // 10
      break;
    case ERROR_missingEnd: // 11;
      msg = GT._("missing END for {0}"); // 11
      break;
    case ERROR_numberExpected: // 12;
      msg = GT._("number expected"); // 12
      break;
    case ERROR_numberOrVariableNameExpected: // 13;
      msg = GT._("number or variable name expected"); // 13
      break;
    case ERROR_residueSpecificationExpected: // 14;
      msg = GT._("residue specification (ALA, AL?, A*) expected"); // 14
      break;
    case ERROR_tokenExpected: // 15;
      msg = GT._("{0} expected"); // 15
      break;
    case ERROR_tokenUnexpected: // 16;
      msg = GT._("{0} unexpected"); // 16
      break;
    case ERROR_unrecognizedExpressionToken: // 17;
      msg = GT._("unrecognized expression token: {0}"); // 17
      break;
    case ERROR_unrecognizedParameter: // 18;
      msg = GT._("unrecognized {0} parameter"); // 18
      break;
    case ERROR_unrecognizedToken: // 19;
      msg = GT._("unrecognized token: {0}"); // 19
      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 (!translated)
      GT.setDoTranslate(doTranslate);
    return msg;
  }
 
  protected boolean commandExpected() {
    ichToken = ichCurrentCommand;
    return error(ERROR_commandExpected);
  }

  protected boolean error(int error) {
    return error(error, null, null);
  }

  protected boolean error(int error, String value) {
    return error(error, value, null);
  }
 
  protected boolean error(int iError, String value, String more) {
    String strError = errorString(iError, value, more, true);
    String strUntranslated = (GT.getDoTranslate() ? errorString(iError, value, more, false) : null);
    return error(strError, strUntranslated);
  }

  private boolean isError() {
    return errorMessage != null;
  }
 
  protected boolean error(String errorMessage, String strUntranslated) {
    this.errorMessage = errorMessage;
    errorMessageUntranslated = strUntranslated;
    return false;
  }

}
TOP

Related Classes of org.jmol.script.ScriptCompilationTokenParser

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.